<commit_before>/*
 * Copyright 2011 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkBlurImageFilter.h"
#include "SkColorPriv.h"

SkBlurImageFilter::SkBlurImageFilter(SkFlattenableReadBuffer& buffer)
  : INHERITED(buffer) {
    fSigma.fWidth = buffer.readScalar();
    fSigma.fHeight = buffer.readScalar();
}

SkBlurImageFilter::SkBlurImageFilter(SkScalar sigmaX, SkScalar sigmaY)
    : fSigma(SkSize::Make(sigmaX, sigmaY)) {
    SkASSERT(sigmaX >= 0 && sigmaY >= 0);
}

bool SkBlurImageFilter::asABlur(SkSize* sigma) const {
    *sigma = fSigma;
    return true;
}

void SkBlurImageFilter::flatten(SkFlattenableWriteBuffer& buffer) {
    this->INHERITED::flatten(buffer);
    buffer.writeScalar(fSigma.fWidth);
    buffer.writeScalar(fSigma.fHeight);
}

static void boxBlurX(const SkBitmap& src, SkBitmap* dst, int kernelSize,
                     int leftOffset, int rightOffset)
{
    int width = src.width(), height = src.height();
    int rightBorder = SkMin32(rightOffset + 1, width);
    for (int y = 0; y < height; ++y) {
        int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
        SkPMColor* p = src.getAddr32(0, y);
        for (int i = 0; i < rightBorder; ++i) {
            sumA += SkGetPackedA32(*p);
            sumR += SkGetPackedR32(*p);
            sumG += SkGetPackedG32(*p);
            sumB += SkGetPackedB32(*p);
            p++;
        }

        const SkColor* sptr = src.getAddr32(0, y);
        SkColor* dptr = dst->getAddr32(0, y);
        for (int x = 0; x < width; ++x) {
            *dptr = SkPackARGB32(sumA / kernelSize,
                                 sumR / kernelSize,
                                 sumG / kernelSize,
                                 sumB / kernelSize);
            if (x >= leftOffset) {
                SkColor l = *(sptr - leftOffset);
                sumA -= SkGetPackedA32(l);
                sumR -= SkGetPackedR32(l);
                sumG -= SkGetPackedG32(l);
                sumB -= SkGetPackedB32(l);
            }
            if (x + rightOffset + 1 < width) {
                SkColor r = *(sptr + rightOffset + 1);
                sumA += SkGetPackedA32(r);
                sumR += SkGetPackedR32(r);
                sumG += SkGetPackedG32(r);
                sumB += SkGetPackedB32(r);
            }
            sptr++;
            dptr++;
        }
    }
}

static void boxBlurY(const SkBitmap& src, SkBitmap* dst, int kernelSize,
                     int topOffset, int bottomOffset)
{
    int width = src.width(), height = src.height();
    int bottomBorder = SkMin32(bottomOffset + 1, height);
    int srcStride = src.rowBytesAsPixels();
    int dstStride = dst->rowBytesAsPixels();
    for (int x = 0; x < width; ++x) {
        int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
        SkColor* p = src.getAddr32(x, 0);
        for (int i = 0; i < bottomBorder; ++i) {
            sumA += SkGetPackedA32(*p);
            sumR += SkGetPackedR32(*p);
            sumG += SkGetPackedG32(*p);
            sumB += SkGetPackedB32(*p);
            p += srcStride;
        }

        const SkColor* sptr = src.getAddr32(x, 0);
        SkColor* dptr = dst->getAddr32(x, 0);
        for (int y = 0; y < height; ++y) {
            *dptr = SkPackARGB32(sumA / kernelSize,
                                 sumR / kernelSize,
                                 sumG / kernelSize,
                                 sumB / kernelSize);
            if (y >= topOffset) {
                SkColor l = *(sptr - topOffset * srcStride);
                sumA -= SkGetPackedA32(l);
                sumR -= SkGetPackedR32(l);
                sumG -= SkGetPackedG32(l);
                sumB -= SkGetPackedB32(l);
            }
            if (y + bottomOffset + 1 < height) {
                SkColor r = *(sptr + (bottomOffset + 1) * srcStride);
                sumA += SkGetPackedA32(r);
                sumR += SkGetPackedR32(r);
                sumG += SkGetPackedG32(r);
                sumB += SkGetPackedB32(r);
            }
            sptr += srcStride;
            dptr += dstStride;
        }
    }
}

static void getBox3Params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset, int *highOffset)
{
    int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * M_PI) / 4.0f + 0.5f));
    *kernelSize = d;
    if (d % 2 == 1) {
        *lowOffset = *highOffset = (d - 1) / 2;
        *kernelSize3 = d;
    } else {
        *highOffset = d / 2;
        *lowOffset = *highOffset - 1;
        *kernelSize3 = d + 1;
    }
}

bool SkBlurImageFilter::onFilterImage(const SkBitmap& src, const SkMatrix&,
                                      SkBitmap* dst, SkIPoint*) {
    if (src.config() != SkBitmap::kARGB_8888_Config) {
        return false;
    }

    dst->setConfig(src.config(), src.width(), src.height());
    dst->allocPixels();
    int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
    int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
    getBox3Params(fSigma.width(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
    getBox3Params(fSigma.height(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);

    if (kernelSizeX < 0 || kernelSizeY < 0) {
        return false;
    }

    if (kernelSizeX == 0 && kernelSizeY == 0) {
        src.copyTo(dst, dst->config());
        return true;
    }

    SkBitmap temp;
    temp.setConfig(dst->config(), dst->width(), dst->height());
    if (!temp.allocPixels()) {
        return false;
    }

    if (kernelSizeX > 0 && kernelSizeY > 0) {
        boxBlurX(src,  &temp, kernelSizeX,  lowOffsetX, highOffsetX);
        boxBlurY(temp, dst,   kernelSizeY,  lowOffsetY, highOffsetY);
        boxBlurX(*dst, &temp, kernelSizeX,  highOffsetX,  lowOffsetX);
        boxBlurY(temp, dst,   kernelSizeY,  highOffsetY,  lowOffsetY);
        boxBlurX(*dst, &temp, kernelSizeX3, highOffsetX, highOffsetX);
        boxBlurY(temp, dst,   kernelSizeY3, highOffsetY, highOffsetY);
    } else if (kernelSizeX > 0) {
        boxBlurX(src,  dst,   kernelSizeX,  lowOffsetX, highOffsetX);
        boxBlurX(*dst, &temp, kernelSizeX,  highOffsetX,  lowOffsetX);
        boxBlurX(temp, dst,   kernelSizeX3, highOffsetX, highOffsetX);
    } else if (kernelSizeY > 0) {
        boxBlurY(src,  dst,   kernelSizeY,  lowOffsetY, highOffsetY);
        boxBlurY(*dst, &temp, kernelSizeY,  highOffsetY, lowOffsetY);
        boxBlurY(temp, dst,   kernelSizeY3, highOffsetY, highOffsetY);
    }
    return true;
}

SK_DEFINE_FLATTENABLE_REGISTRAR(SkBlurImageFilter)
<commit_msg>Unreviewed. Fixing the Windows build, due to a non definition of M_PI.<commit_after>/*
 * Copyright 2011 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "SkBlurImageFilter.h"
#include "SkColorPriv.h"

SkBlurImageFilter::SkBlurImageFilter(SkFlattenableReadBuffer& buffer)
  : INHERITED(buffer) {
    fSigma.fWidth = buffer.readScalar();
    fSigma.fHeight = buffer.readScalar();
}

SkBlurImageFilter::SkBlurImageFilter(SkScalar sigmaX, SkScalar sigmaY)
    : fSigma(SkSize::Make(sigmaX, sigmaY)) {
    SkASSERT(sigmaX >= 0 && sigmaY >= 0);
}

bool SkBlurImageFilter::asABlur(SkSize* sigma) const {
    *sigma = fSigma;
    return true;
}

void SkBlurImageFilter::flatten(SkFlattenableWriteBuffer& buffer) {
    this->INHERITED::flatten(buffer);
    buffer.writeScalar(fSigma.fWidth);
    buffer.writeScalar(fSigma.fHeight);
}

static void boxBlurX(const SkBitmap& src, SkBitmap* dst, int kernelSize,
                     int leftOffset, int rightOffset)
{
    int width = src.width(), height = src.height();
    int rightBorder = SkMin32(rightOffset + 1, width);
    for (int y = 0; y < height; ++y) {
        int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
        SkPMColor* p = src.getAddr32(0, y);
        for (int i = 0; i < rightBorder; ++i) {
            sumA += SkGetPackedA32(*p);
            sumR += SkGetPackedR32(*p);
            sumG += SkGetPackedG32(*p);
            sumB += SkGetPackedB32(*p);
            p++;
        }

        const SkColor* sptr = src.getAddr32(0, y);
        SkColor* dptr = dst->getAddr32(0, y);
        for (int x = 0; x < width; ++x) {
            *dptr = SkPackARGB32(sumA / kernelSize,
                                 sumR / kernelSize,
                                 sumG / kernelSize,
                                 sumB / kernelSize);
            if (x >= leftOffset) {
                SkColor l = *(sptr - leftOffset);
                sumA -= SkGetPackedA32(l);
                sumR -= SkGetPackedR32(l);
                sumG -= SkGetPackedG32(l);
                sumB -= SkGetPackedB32(l);
            }
            if (x + rightOffset + 1 < width) {
                SkColor r = *(sptr + rightOffset + 1);
                sumA += SkGetPackedA32(r);
                sumR += SkGetPackedR32(r);
                sumG += SkGetPackedG32(r);
                sumB += SkGetPackedB32(r);
            }
            sptr++;
            dptr++;
        }
    }
}

static void boxBlurY(const SkBitmap& src, SkBitmap* dst, int kernelSize,
                     int topOffset, int bottomOffset)
{
    int width = src.width(), height = src.height();
    int bottomBorder = SkMin32(bottomOffset + 1, height);
    int srcStride = src.rowBytesAsPixels();
    int dstStride = dst->rowBytesAsPixels();
    for (int x = 0; x < width; ++x) {
        int sumA = 0, sumR = 0, sumG = 0, sumB = 0;
        SkColor* p = src.getAddr32(x, 0);
        for (int i = 0; i < bottomBorder; ++i) {
            sumA += SkGetPackedA32(*p);
            sumR += SkGetPackedR32(*p);
            sumG += SkGetPackedG32(*p);
            sumB += SkGetPackedB32(*p);
            p += srcStride;
        }

        const SkColor* sptr = src.getAddr32(x, 0);
        SkColor* dptr = dst->getAddr32(x, 0);
        for (int y = 0; y < height; ++y) {
            *dptr = SkPackARGB32(sumA / kernelSize,
                                 sumR / kernelSize,
                                 sumG / kernelSize,
                                 sumB / kernelSize);
            if (y >= topOffset) {
                SkColor l = *(sptr - topOffset * srcStride);
                sumA -= SkGetPackedA32(l);
                sumR -= SkGetPackedR32(l);
                sumG -= SkGetPackedG32(l);
                sumB -= SkGetPackedB32(l);
            }
            if (y + bottomOffset + 1 < height) {
                SkColor r = *(sptr + (bottomOffset + 1) * srcStride);
                sumA += SkGetPackedA32(r);
                sumR += SkGetPackedR32(r);
                sumG += SkGetPackedG32(r);
                sumB += SkGetPackedB32(r);
            }
            sptr += srcStride;
            dptr += dstStride;
        }
    }
}

static void getBox3Params(SkScalar s, int *kernelSize, int* kernelSize3, int *lowOffset, int *highOffset)
{
    float pi = SkScalarToFloat(SK_ScalarPI);
    int d = static_cast<int>(floorf(SkScalarToFloat(s) * 3.0f * sqrtf(2.0f * pi) / 4.0f + 0.5f));
    *kernelSize = d;
    if (d % 2 == 1) {
        *lowOffset = *highOffset = (d - 1) / 2;
        *kernelSize3 = d;
    } else {
        *highOffset = d / 2;
        *lowOffset = *highOffset - 1;
        *kernelSize3 = d + 1;
    }
}

bool SkBlurImageFilter::onFilterImage(const SkBitmap& src, const SkMatrix&,
                                      SkBitmap* dst, SkIPoint*) {
    if (src.config() != SkBitmap::kARGB_8888_Config) {
        return false;
    }

    dst->setConfig(src.config(), src.width(), src.height());
    dst->allocPixels();
    int kernelSizeX, kernelSizeX3, lowOffsetX, highOffsetX;
    int kernelSizeY, kernelSizeY3, lowOffsetY, highOffsetY;
    getBox3Params(fSigma.width(), &kernelSizeX, &kernelSizeX3, &lowOffsetX, &highOffsetX);
    getBox3Params(fSigma.height(), &kernelSizeY, &kernelSizeY3, &lowOffsetY, &highOffsetY);

    if (kernelSizeX < 0 || kernelSizeY < 0) {
        return false;
    }

    if (kernelSizeX == 0 && kernelSizeY == 0) {
        src.copyTo(dst, dst->config());
        return true;
    }

    SkBitmap temp;
    temp.setConfig(dst->config(), dst->width(), dst->height());
    if (!temp.allocPixels()) {
        return false;
    }

    if (kernelSizeX > 0 && kernelSizeY > 0) {
        boxBlurX(src,  &temp, kernelSizeX,  lowOffsetX, highOffsetX);
        boxBlurY(temp, dst,   kernelSizeY,  lowOffsetY, highOffsetY);
        boxBlurX(*dst, &temp, kernelSizeX,  highOffsetX,  lowOffsetX);
        boxBlurY(temp, dst,   kernelSizeY,  highOffsetY,  lowOffsetY);
        boxBlurX(*dst, &temp, kernelSizeX3, highOffsetX, highOffsetX);
        boxBlurY(temp, dst,   kernelSizeY3, highOffsetY, highOffsetY);
    } else if (kernelSizeX > 0) {
        boxBlurX(src,  dst,   kernelSizeX,  lowOffsetX, highOffsetX);
        boxBlurX(*dst, &temp, kernelSizeX,  highOffsetX,  lowOffsetX);
        boxBlurX(temp, dst,   kernelSizeX3, highOffsetX, highOffsetX);
    } else if (kernelSizeY > 0) {
        boxBlurY(src,  dst,   kernelSizeY,  lowOffsetY, highOffsetY);
        boxBlurY(*dst, &temp, kernelSizeY,  highOffsetY, lowOffsetY);
        boxBlurY(temp, dst,   kernelSizeY3, highOffsetY, highOffsetY);
    }
    return true;
}

SK_DEFINE_FLATTENABLE_REGISTRAR(SkBlurImageFilter)
<|endoftext|>


<commit_before>// Copyright (c) 2019 The Zcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "../property.h"
#include "../sigmadb.h"
#include "../sigmaprimitives.h"
#include "../wallet.h"
#include "../walletmodels.h"

#include "../../wallet/wallet.h"
#include "../../wallet/walletexcept.h"

#include "../../wallet/test/wallet_test_fixture.h"

#include <boost/optional/optional_io.hpp>
#include <boost/test/unit_test.hpp>

#include <iterator>
#include <ostream>
#include <stdexcept>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

namespace std {

template<class Char, class Traits, unsigned Size>
basic_ostream<Char, Traits>& operator<<(basic_ostream<Char, Traits>& os, const base_blob<Size>& v)
{
    return os << v.GetHex();
}

} // namespace std

namespace elysium {

struct WalletTestingSetup : ::WalletTestingSetup
{
    WalletTestingSetup()
    {
        sigmaDb = new SigmaDatabase(pathTemp / "elysium_sigma_tests", true, 10);
        wallet = new Wallet(pwalletMain->strWalletFile);
        wallet->ReloadMasterKey();
    }

    ~WalletTestingSetup()
    {
        delete wallet; wallet = nullptr;
        delete sigmaDb; sigmaDb = nullptr;
    }

    SigmaMint CreateSigmaMint(PropertyId property, SigmaDenomination denomination)
    {
        auto id = wallet->CreateSigmaMint(property, denomination);
        return wallet->GetSigmaMint(id);
    }
};

BOOST_FIXTURE_TEST_SUITE(elysium_wallet_tests, WalletTestingSetup)

BOOST_AUTO_TEST_CASE(sigma_mint_create_one)
{
    auto id = wallet->CreateSigmaMint(1, 2);
    auto mint = wallet->GetSigmaMint(id);

    BOOST_CHECK(id.pubKey.IsValid());
    BOOST_CHECK_EQUAL(1, id.property);
    BOOST_CHECK_EQUAL(2, id.denomination);
    BOOST_CHECK_EQUAL(id.property, mint.property);
    BOOST_CHECK_EQUAL(id.denomination, mint.denomination);

    BOOST_CHECK(!mint.IsSpent());
    BOOST_CHECK_EQUAL(mint.chainState, SigmaMintChainState());

    auto priv = wallet->GetKey(mint);
    SigmaPublicKey pub(priv, DefaultSigmaParams);
    BOOST_CHECK_EQUAL(id.pubKey, pub);

    auto another = CreateSigmaMint(1, 2);

    BOOST_CHECK_NE(another, mint);
}

BOOST_AUTO_TEST_CASE(sigma_mint_create_multi)
{
    std::vector<SigmaDenomination> denominations = {0, 1, 0, 2};
    std::vector<SigmaMintId> ids(5);
    std::unordered_set<SigmaMint> mints;

    auto next = wallet->CreateSigmaMints(1, denominations.begin(), denominations.end(), ids.begin());

    BOOST_CHECK_EQUAL(std::distance(ids.begin(), next), 4);

    BOOST_CHECK_EQUAL(ids[0].denomination, 0);
    BOOST_CHECK_EQUAL(ids[1].denomination, 1);
    BOOST_CHECK_EQUAL(ids[2].denomination, 0);
    BOOST_CHECK_EQUAL(ids[3].denomination, 2);

    for (auto it = ids.begin(); it != next; it++) {
        auto& id = *it;
        auto mint = wallet->GetSigmaMint(id);

        BOOST_CHECK_EQUAL(id.property, 1);
        BOOST_CHECK_EQUAL(mint.property, id.property);
        BOOST_CHECK_EQUAL(id.denomination, mint.denomination);
        BOOST_CHECK(id.pubKey.IsValid());

        BOOST_CHECK_NE(id.pubKey, SigmaPublicKey());

        BOOST_CHECK(!mint.IsSpent());
        BOOST_CHECK_EQUAL(mint.chainState, SigmaMintChainState());

        BOOST_CHECK(mints.insert(std::move(mint)).second);

        auto priv = wallet->GetKey(mint);
        SigmaPublicKey pub(priv, DefaultSigmaParams);
        BOOST_CHECK_EQUAL(pub, id.pubKey);
    }
}

BOOST_AUTO_TEST_CASE(sigma_spend_create_no_spendable_mint)
{
    // No any mints.
    BOOST_CHECK_THROW(wallet->CreateSigmaSpendV1(3, 0, false), InsufficientFunds);

    // Different denomination and property type.
    auto mintId = wallet->CreateSigmaMint(3, 0);

    BOOST_CHECK_THROW(wallet->CreateSigmaSpendV1(3, 1, false), InsufficientFunds);
    BOOST_CHECK_THROW(wallet->CreateSigmaSpendV1(4, 0, false), InsufficientFunds);

    // Pending mint.
    BOOST_CHECK_THROW(wallet->CreateSigmaSpendV1(3, 0, false), InsufficientFunds);

    // Already spent.
    sigmaDb->RecordMint(3, 0, mintId.pubKey, 100);
    wallet->SetSigmaMintUsedTransaction(mintId, uint256S("890e968f9b65dbacd576100c9b1c446f06471ed27df845ab7a24931cb640b388"));

    BOOST_CHECK_THROW(wallet->CreateSigmaSpendV1(3, 0, false), InsufficientFunds);
}

BOOST_AUTO_TEST_CASE(sigma_spend_create_with_spendable_mints)
{
    // Create first full group and one mint in a next group.
    auto expectedMintId = wallet->CreateSigmaMint(3, 0);
    sigmaDb->RecordMint(3, 0, expectedMintId.pubKey, 100);

    for (unsigned i = 1; i <= sigmaDb->groupSize; i++) {
        auto mintid = wallet->CreateSigmaMint(3, 0);
        sigmaDb->RecordMint(3, 0, mintid.pubKey, 100 + i);
    }

    auto spend = wallet->CreateSigmaSpendV1(3, 0, false);

    BOOST_CHECK_EQUAL(spend.mint, expectedMintId);
    BOOST_CHECK_EQUAL(spend.group, 0);
    BOOST_CHECK_EQUAL(spend.groupSize, sigmaDb->groupSize);
}

BOOST_AUTO_TEST_CASE(sigma_spend_create_not_enough_anonimity)
{
    auto mintId = wallet->CreateSigmaMint(3, 0);
    sigmaDb->RecordMint(3, 0, mintId.pubKey, 100);

    BOOST_CHECK_EXCEPTION(wallet->CreateSigmaSpendV1(3, 0, false), WalletError, [] (const WalletError& e) {
        return e.what() == std::string("Amount of coins in anonimity set is not enough to spend");
    });
}

BOOST_AUTO_TEST_CASE(sigma_mint_listing_all)
{
    // Create mints.
    std::unordered_set<SigmaMintId> ids;

    ids.insert(wallet->CreateSigmaMint(1, 0));
    ids.insert(wallet->CreateSigmaMint(2, 0));
    ids.insert(wallet->CreateSigmaMint(1, 1));
    ids.insert(wallet->CreateSigmaMint(2, 0));

    BOOST_CHECK_EQUAL(ids.size(), 4);

    // List mints.
    std::unordered_map<SigmaMintId, SigmaMint> mints;

    wallet->ListSigmaMintsV1(std::inserter(mints, mints.begin()));

    BOOST_CHECK_EQUAL(mints.size(), ids.size());

    for (auto& mint : mints) {
        auto it = ids.find(mint.first);

        BOOST_CHECK(it != ids.end());
        BOOST_CHECK_EQUAL(mint.second, wallet->GetSigmaMint(*it));

        ids.erase(it);
    }

    BOOST_CHECK_EQUAL(ids.size(), 0);
}

BOOST_AUTO_TEST_CASE(sigma_mint_check_existence)
{
    auto owned = wallet->CreateSigmaMint(1, 1);
    SigmaPrivateKey priv;
    SigmaPublicKey pub;

    priv.Generate();
    pub.Generate(priv, DefaultSigmaParams);

    SigmaMintId other(1, 1, pub);

    BOOST_CHECK_EQUAL(wallet->HasSigmaMint(owned), true);
    BOOST_CHECK_EQUAL(wallet->HasSigmaMint(other), false);
}

BOOST_AUTO_TEST_CASE(sigma_mint_get)
{
    // Get existence.
    auto owned = wallet->CreateSigmaMint(1, 1);
    auto mint = wallet->GetSigmaMint(owned);

    SigmaPublicKey pub(wallet->GetKey(mint), DefaultSigmaParams);
    BOOST_CHECK_EQUAL(owned, SigmaMintId(mint.property, mint.denomination, pub));

    // Get non-existence.
    SigmaPrivateKey otherPriv;
    SigmaPublicKey otherPub;

    otherPriv.Generate();
    otherPub.Generate(otherPriv, DefaultSigmaParams);

    SigmaMintId other(1, 1, otherPub);

    BOOST_CHECK_THROW(wallet->GetSigmaMint(other), std::runtime_error);
}

BOOST_AUTO_TEST_CASE(sigma_mint_set_used)
{
    auto tx = uint256S("64c4c22a45ad449be61c52a431d11e81f7fd0ee2f2235bf02944fb0b3dd07adb");
    auto id = wallet->CreateSigmaMint(1, 1);
    SigmaMint mint;

    wallet->SetSigmaMintUsedTransaction(id, tx);
    mint = wallet->GetSigmaMint(id);
    BOOST_CHECK_EQUAL(mint.spendTx, tx);

    wallet->SetSigmaMintUsedTransaction(id, uint256());
    mint = wallet->GetSigmaMint(id);
    BOOST_CHECK(!mint.IsSpent());
}

BOOST_AUTO_TEST_CASE(sigma_mint_chainstate_owned)
{
    auto id = wallet->CreateSigmaMint(1, 0);
    SigmaMintGroup group;
    SigmaMintIndex index;
    SigmaMint mint;

    // Add.
    std::tie(group, index) = sigmaDb->RecordMint(1, 0, id.pubKey, 100);
    mint = wallet->GetSigmaMint(id);

    BOOST_CHECK_EQUAL(mint.chainState.block, 100);
    BOOST_CHECK_EQUAL(mint.chainState.group, group);
    BOOST_CHECK_EQUAL(mint.chainState.index, index);

    // Remove.
    sigmaDb->DeleteAll(100);
    mint = wallet->GetSigmaMint(id);

    BOOST_CHECK_EQUAL(mint.chainState, SigmaMintChainState());
}

BOOST_AUTO_TEST_CASE(sigma_mint_chainstate_not_owned)
{
    // Add our mint first so we can test if the other mint does not alter our mint state.
    auto id = wallet->CreateSigmaMint(1, 0);
    SigmaMintGroup group;
    SigmaMintIndex index;

    std::tie(group, index) = sigmaDb->RecordMint(1, 0, id.pubKey, 100);

    // Add other mint.
    SigmaPrivateKey otherPriv;
    SigmaPublicKey otherPub;

    otherPriv.Generate();
    otherPub.Generate(otherPriv, DefaultSigmaParams);

    sigmaDb->RecordMint(1, 0, otherPub, 101);

    // Our chain state should not updated.
    SigmaMint mint;

    mint = wallet->GetSigmaMint(id);

    BOOST_CHECK_EQUAL(mint.chainState.block, 100);
    BOOST_CHECK_EQUAL(mint.chainState.group, group);
    BOOST_CHECK_EQUAL(mint.chainState.index, index);

    // Other mint should not added to our wallet.
    BOOST_CHECK_THROW(
        wallet->GetSigmaMint(SigmaMintId(1, 0, otherPub)),
        std::runtime_error
    );

    // Remove other mint and our chain state should not updated.
    sigmaDb->DeleteAll(101);

    mint = wallet->GetSigmaMint(id);

    BOOST_CHECK_EQUAL(mint.chainState.block, 100);
    BOOST_CHECK_EQUAL(mint.chainState.group, group);
    BOOST_CHECK_EQUAL(mint.chainState.index, index);
}

BOOST_AUTO_TEST_SUITE_END()

}
<commit_msg>Fix invalid assertion<commit_after>// Copyright (c) 2019 The Zcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "../property.h"
#include "../sigmadb.h"
#include "../sigmaprimitives.h"
#include "../wallet.h"
#include "../walletmodels.h"

#include "../../wallet/wallet.h"
#include "../../wallet/walletexcept.h"

#include "../../wallet/test/wallet_test_fixture.h"

#include <boost/optional/optional_io.hpp>
#include <boost/test/unit_test.hpp>

#include <iterator>
#include <ostream>
#include <stdexcept>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

namespace std {

template<class Char, class Traits, unsigned Size>
basic_ostream<Char, Traits>& operator<<(basic_ostream<Char, Traits>& os, const base_blob<Size>& v)
{
    return os << v.GetHex();
}

} // namespace std

namespace elysium {

struct WalletTestingSetup : ::WalletTestingSetup
{
    WalletTestingSetup()
    {
        sigmaDb = new SigmaDatabase(pathTemp / "elysium_sigma_tests", true, 10);
        wallet = new Wallet(pwalletMain->strWalletFile);
        wallet->ReloadMasterKey();
    }

    ~WalletTestingSetup()
    {
        delete wallet; wallet = nullptr;
        delete sigmaDb; sigmaDb = nullptr;
    }

    SigmaMint CreateSigmaMint(PropertyId property, SigmaDenomination denomination)
    {
        auto id = wallet->CreateSigmaMint(property, denomination);
        return wallet->GetSigmaMint(id);
    }
};

BOOST_FIXTURE_TEST_SUITE(elysium_wallet_tests, WalletTestingSetup)

BOOST_AUTO_TEST_CASE(sigma_mint_create_one)
{
    auto id = wallet->CreateSigmaMint(1, 2);
    auto mint = wallet->GetSigmaMint(id);

    BOOST_CHECK(id.pubKey.IsValid());
    BOOST_CHECK_EQUAL(1, id.property);
    BOOST_CHECK_EQUAL(2, id.denomination);
    BOOST_CHECK_EQUAL(id.property, mint.property);
    BOOST_CHECK_EQUAL(id.denomination, mint.denomination);

    BOOST_CHECK(!mint.IsSpent());
    BOOST_CHECK_EQUAL(mint.chainState, SigmaMintChainState());

    auto priv = wallet->GetKey(mint);
    SigmaPublicKey pub(priv, DefaultSigmaParams);
    BOOST_CHECK_EQUAL(id.pubKey, pub);

    auto another = CreateSigmaMint(1, 2);

    BOOST_CHECK_NE(another, mint);
}

BOOST_AUTO_TEST_CASE(sigma_mint_create_multi)
{
    std::vector<SigmaDenomination> denominations = {0, 1, 0, 2};
    std::vector<SigmaMintId> ids(5);
    std::unordered_set<SigmaMint> mints;

    auto next = wallet->CreateSigmaMints(1, denominations.begin(), denominations.end(), ids.begin());

    BOOST_CHECK_EQUAL(std::distance(ids.begin(), next), 4);

    BOOST_CHECK_EQUAL(ids[0].denomination, 0);
    BOOST_CHECK_EQUAL(ids[1].denomination, 1);
    BOOST_CHECK_EQUAL(ids[2].denomination, 0);
    BOOST_CHECK_EQUAL(ids[3].denomination, 2);

    for (auto it = ids.begin(); it != next; it++) {
        auto& id = *it;
        auto mint = wallet->GetSigmaMint(id);

        BOOST_CHECK_EQUAL(id.property, 1);
        BOOST_CHECK_EQUAL(mint.property, id.property);
        BOOST_CHECK_EQUAL(id.denomination, mint.denomination);
        BOOST_CHECK(id.pubKey.IsValid());

        BOOST_CHECK_NE(id.pubKey, SigmaPublicKey());

        BOOST_CHECK(!mint.IsSpent());
        BOOST_CHECK_EQUAL(mint.chainState, SigmaMintChainState());

        BOOST_CHECK(mints.insert(std::move(mint)).second);

        auto priv = wallet->GetKey(mint);
        SigmaPublicKey pub(priv, DefaultSigmaParams);
        BOOST_CHECK_EQUAL(pub, id.pubKey);
    }
}

BOOST_AUTO_TEST_CASE(sigma_spend_create_no_spendable_mint)
{
    // No any mints.
    BOOST_CHECK_THROW(wallet->CreateSigmaSpendV1(3, 0, false), InsufficientFunds);

    // Different denomination and property type.
    auto mintId = wallet->CreateSigmaMint(3, 0);

    BOOST_CHECK_THROW(wallet->CreateSigmaSpendV1(3, 1, false), InsufficientFunds);
    BOOST_CHECK_THROW(wallet->CreateSigmaSpendV1(4, 0, false), InsufficientFunds);

    // Pending mint.
    BOOST_CHECK_THROW(wallet->CreateSigmaSpendV1(3, 0, false), InsufficientFunds);

    // Already spent.
    sigmaDb->RecordMint(3, 0, mintId.pubKey, 100);
    wallet->SetSigmaMintUsedTransaction(mintId, uint256S("890e968f9b65dbacd576100c9b1c446f06471ed27df845ab7a24931cb640b388"));

    BOOST_CHECK_THROW(wallet->CreateSigmaSpendV1(3, 0, false), InsufficientFunds);
}

BOOST_AUTO_TEST_CASE(sigma_spend_create_with_spendable_mints)
{
    // Create first full group and one mint in a next group.
    auto expectedMintId = wallet->CreateSigmaMint(3, 0);
    sigmaDb->RecordMint(3, 0, expectedMintId.pubKey, 100);

    for (unsigned i = 1; i <= sigmaDb->groupSize; i++) {
        auto mintid = wallet->CreateSigmaMint(3, 0);
        sigmaDb->RecordMint(3, 0, mintid.pubKey, 100 + i);
    }

    auto spend = wallet->CreateSigmaSpendV1(3, 0, false);

    BOOST_CHECK_EQUAL(spend.mint, expectedMintId);
    BOOST_CHECK_EQUAL(spend.group, 0);
    BOOST_CHECK_EQUAL(spend.groupSize, sigmaDb->groupSize);
}

BOOST_AUTO_TEST_CASE(sigma_spend_create_not_enough_anonimity)
{
    auto mintId = wallet->CreateSigmaMint(3, 0);
    sigmaDb->RecordMint(3, 0, mintId.pubKey, 100);

    BOOST_CHECK_EXCEPTION(wallet->CreateSigmaSpendV1(3, 0, false), WalletError, [] (const WalletError& e) {
        return e.what() == std::string("Amount of coins in anonimity set is not enough to spend");
    });
}

BOOST_AUTO_TEST_CASE(sigma_mint_listing_all)
{
    // Create mints.
    std::unordered_set<SigmaMintId> ids;

    ids.insert(wallet->CreateSigmaMint(1, 0));
    ids.insert(wallet->CreateSigmaMint(2, 0));
    ids.insert(wallet->CreateSigmaMint(1, 1));
    ids.insert(wallet->CreateSigmaMint(2, 0));

    BOOST_CHECK_EQUAL(ids.size(), 4);

    // List mints.
    std::unordered_map<SigmaMintId, SigmaMint> mints;

    wallet->ListSigmaMintsV1(std::inserter(mints, mints.begin()));

    BOOST_CHECK_EQUAL(mints.size(), ids.size());

    for (auto& mint : mints) {
        auto it = ids.find(mint.first);

        BOOST_CHECK(it != ids.end());
        BOOST_CHECK_EQUAL(mint.second, wallet->GetSigmaMint(*it));

        ids.erase(it);
    }

    BOOST_CHECK_EQUAL(ids.size(), 0);
}

BOOST_AUTO_TEST_CASE(sigma_mint_check_existence)
{
    auto owned = wallet->CreateSigmaMint(1, 1);
    SigmaPrivateKey priv;
    SigmaPublicKey pub;

    priv.Generate();
    pub.Generate(priv, DefaultSigmaParams);

    SigmaMintId other(1, 1, pub);

    BOOST_CHECK_EQUAL(wallet->HasSigmaMint(owned), true);
    BOOST_CHECK_EQUAL(wallet->HasSigmaMint(other), false);
}

BOOST_AUTO_TEST_CASE(sigma_mint_get)
{
    // Get existence.
    auto owned = wallet->CreateSigmaMint(1, 1);
    auto mint = wallet->GetSigmaMint(owned);

    SigmaPublicKey pub(wallet->GetKey(mint), DefaultSigmaParams);
    BOOST_CHECK_EQUAL(owned, SigmaMintId(mint.property, mint.denomination, pub));

    // Get non-existence.
    SigmaPrivateKey otherPriv;
    SigmaPublicKey otherPub;

    otherPriv.Generate();
    otherPub.Generate(otherPriv, DefaultSigmaParams);

    SigmaMintId other(1, 1, otherPub);

    BOOST_CHECK_THROW(wallet->GetSigmaMint(other), std::invalid_argument);
}

BOOST_AUTO_TEST_CASE(sigma_mint_set_used)
{
    auto tx = uint256S("64c4c22a45ad449be61c52a431d11e81f7fd0ee2f2235bf02944fb0b3dd07adb");
    auto id = wallet->CreateSigmaMint(1, 1);
    SigmaMint mint;

    wallet->SetSigmaMintUsedTransaction(id, tx);
    mint = wallet->GetSigmaMint(id);
    BOOST_CHECK_EQUAL(mint.spendTx, tx);

    wallet->SetSigmaMintUsedTransaction(id, uint256());
    mint = wallet->GetSigmaMint(id);
    BOOST_CHECK(!mint.IsSpent());
}

BOOST_AUTO_TEST_CASE(sigma_mint_chainstate_owned)
{
    auto id = wallet->CreateSigmaMint(1, 0);
    SigmaMintGroup group;
    SigmaMintIndex index;
    SigmaMint mint;

    // Add.
    std::tie(group, index) = sigmaDb->RecordMint(1, 0, id.pubKey, 100);
    mint = wallet->GetSigmaMint(id);

    BOOST_CHECK_EQUAL(mint.chainState.block, 100);
    BOOST_CHECK_EQUAL(mint.chainState.group, group);
    BOOST_CHECK_EQUAL(mint.chainState.index, index);

    // Remove.
    sigmaDb->DeleteAll(100);
    mint = wallet->GetSigmaMint(id);

    BOOST_CHECK_EQUAL(mint.chainState, SigmaMintChainState());
}

BOOST_AUTO_TEST_CASE(sigma_mint_chainstate_not_owned)
{
    // Add our mint first so we can test if the other mint does not alter our mint state.
    auto id = wallet->CreateSigmaMint(1, 0);
    SigmaMintGroup group;
    SigmaMintIndex index;

    std::tie(group, index) = sigmaDb->RecordMint(1, 0, id.pubKey, 100);

    // Add other mint.
    SigmaPrivateKey otherPriv;
    SigmaPublicKey otherPub;

    otherPriv.Generate();
    otherPub.Generate(otherPriv, DefaultSigmaParams);

    sigmaDb->RecordMint(1, 0, otherPub, 101);

    // Our chain state should not updated.
    SigmaMint mint;

    mint = wallet->GetSigmaMint(id);

    BOOST_CHECK_EQUAL(mint.chainState.block, 100);
    BOOST_CHECK_EQUAL(mint.chainState.group, group);
    BOOST_CHECK_EQUAL(mint.chainState.index, index);

    // Other mint should not added to our wallet.
    BOOST_CHECK_THROW(
        wallet->GetSigmaMint(SigmaMintId(1, 0, otherPub)),
        std::invalid_argument
    );

    // Remove other mint and our chain state should not updated.
    sigmaDb->DeleteAll(101);

    mint = wallet->GetSigmaMint(id);

    BOOST_CHECK_EQUAL(mint.chainState.block, 100);
    BOOST_CHECK_EQUAL(mint.chainState.group, group);
    BOOST_CHECK_EQUAL(mint.chainState.index, index);
}

BOOST_AUTO_TEST_SUITE_END()

}
<|endoftext|>


<commit_before>/*
    MIT License

    Copyright (c) 2018 namreeb http://github.com/namreeb legal@namreeb.org

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.

*/

#pragma comment(lib, "asmjit.lib")
#pragma comment(lib, "udis86.lib")

#include "InitializeHooks.hpp"

#include <hadesmem/patcher.hpp>

#include <cstdint>

namespace
{
enum class Version
{
    Classic = 0,
    TBC,
    WotLK,
    Cata32,
    Cata64,
    Total
};

enum class Offset
{
    CVar__Set = 0,
    RealmListCVar,
    Initialize,
    Login,
    FoV,
    Total
};

PVOID GetAddress(Version version, Offset offset)
{
    static constexpr std::uint32_t offsets[static_cast<int>(Version::Total)][static_cast<int>(Offset::Total)] =
    {
        // Classic
        {
            0x23DF50,
            0x82812C,
            0x2AD0,
            0x6AFB0,
            0x4089B4
        },
        // TBC
        {
            0x23F6C0,
            0x943330,
            0x77AC0,
            0x6E560,
            0x4B5A04
        },
        // WotLK
        {
            0x3668C0,
            0x879D00,
            0xE5940,
            0xD8A30,
            0x5E8D88
        },
        // Cata32
        {
            0x2553B0,
            0x9BE800,
            0x40F8F0,
            0x400240,
            0x00, // not supported.  let me know if anyone actually wants this
        },
        // Cata64
        {
            0x2F61D0,
            0xCA4328,
            0x51C0F0,
            0x514100,
            0x00, // not supported.  let me know if anyone actually wants this
        }
    };

    auto const baseAddress = reinterpret_cast<std::uint8_t *>(::GetModuleHandle(nullptr));

    return baseAddress + offsets[static_cast<int>(version)][static_cast<int>(offset)];
}
}

namespace Classic
{
class CVar {};

using SetT = bool(__thiscall CVar::*)(const char *, char, char, char, char);
using InitializeT = void (__cdecl *)();
using LoginT = void(__fastcall *)(char *, char *);

void InitializeHook(hadesmem::PatchDetourBase *detour, GameSettings *settings)
{
    auto const initialize = detour->GetTrampolineT<InitializeT>();
    initialize();

    auto const cvar = *reinterpret_cast<CVar **>(GetAddress(Version::Classic, Offset::RealmListCVar));
    auto const set = hadesmem::detail::AliasCast<SetT>(GetAddress(Version::Classic, Offset::CVar__Set));

    (cvar->*set)(settings->AuthServer, 1, 0, 1, 0);

    detour->Remove();

    if (settings->CredentialsSet)
    {
        auto const login = hadesmem::detail::AliasCast<LoginT>(GetAddress(Version::Classic, Offset::Login));
        login(settings->Username, settings->Password);
    }

    settings->LoadComplete = true;
}

void ApplyClientInitHook(GameSettings *settings)
{
    auto const proc = hadesmem::Process(::GetCurrentProcessId());
    auto const initializeOrig = hadesmem::detail::AliasCast<InitializeT>(GetAddress(Version::Classic, Offset::Initialize));
    auto initializeDetour = new hadesmem::PatchDetour<InitializeT>(proc, initializeOrig,
        [settings] (hadesmem::PatchDetourBase *detour)
        {
            InitializeHook(detour, settings);
        });

    initializeDetour->Apply();

    if (settings->FoVSet)
    {
        auto const pFov = GetAddress(Version::Classic, Offset::FoV);
        std::vector<std::uint8_t> patchData(sizeof(settings->FoV));
        memcpy(&patchData[0], &settings->FoV, sizeof(settings->FoV));
        auto patch = new hadesmem::PatchRaw(proc, pFov, patchData);
        patch->Apply();
    }
}
}

namespace TBC
{
class CVar {};

using SetT = bool(__thiscall CVar::*)(const char *, char, char, char, char);
using InitializeT = void(__cdecl*)();
using LoginT = void(__cdecl *)(char *, char *);

void InitializeHook(hadesmem::PatchDetourBase *detour, GameSettings *settings)
{
    auto const initialize = detour->GetTrampolineT<InitializeT>();
    initialize();

    auto const cvar = *reinterpret_cast<CVar **>(GetAddress(Version::TBC, Offset::RealmListCVar));
    auto const set = hadesmem::detail::AliasCast<SetT>(GetAddress(Version::TBC, Offset::CVar__Set));

    (cvar->*set)(settings->AuthServer, 1, 0, 1, 0);

    detour->Remove();

    if (settings->CredentialsSet)
    {
        auto const login = hadesmem::detail::AliasCast<LoginT>(GetAddress(Version::TBC, Offset::Login));
        login(settings->Username, settings->Password);
    }

    settings->LoadComplete = true;
}

void ApplyClientInitHook(GameSettings *settings)
{
    auto const proc = hadesmem::Process(::GetCurrentProcessId());
    auto const initializeOrig = hadesmem::detail::AliasCast<InitializeT>(GetAddress(Version::TBC, Offset::Initialize));
    auto initializeDetour = new hadesmem::PatchDetour<InitializeT>(proc, initializeOrig,
        [settings] (hadesmem::PatchDetourBase *detour)
        {
            InitializeHook(detour, settings);
        });

    initializeDetour->Apply();

    if (settings->FoVSet)
    {
        auto const pFov = GetAddress(Version::TBC, Offset::FoV);
        std::vector<std::uint8_t> patchData(sizeof(settings->FoV));
        memcpy(&patchData[0], &settings->FoV, sizeof(settings->FoV));
        auto patch = new hadesmem::PatchRaw(proc, pFov, patchData);
        patch->Apply();
    }
}
}

namespace WOTLK
{
class CVar {};

using SetT = bool(__thiscall CVar::*)(const char *, char, char, char, char);
using InitializeT = void (*)();
using LoginT = void(__cdecl *)(char *, char *);

void InitializeHook(hadesmem::PatchDetourBase *detour, GameSettings *settings)
{
    auto const initialize = detour->GetTrampolineT<InitializeT>();
    initialize();

    auto const cvar = *reinterpret_cast<CVar **>(GetAddress(Version::WotLK, Offset::RealmListCVar));
    auto const set = hadesmem::detail::AliasCast<SetT>(GetAddress(Version::WotLK, Offset::CVar__Set));

    (cvar->*set)(settings->AuthServer, 1, 0, 1, 0);

    detour->Remove();

    if (settings->CredentialsSet)
    {
        auto const login = hadesmem::detail::AliasCast<LoginT>(GetAddress(Version::WotLK, Offset::Login));
        login(settings->Username, settings->Password);
    }

    settings->LoadComplete = true;
}

void ApplyClientInitHook(GameSettings *settings)
{
    auto const proc = hadesmem::Process(::GetCurrentProcessId());
    auto const initializeOrig = hadesmem::detail::AliasCast<InitializeT>(GetAddress(Version::WotLK, Offset::Initialize));
    auto initializeDetour = new hadesmem::PatchDetour<InitializeT>(proc, initializeOrig,
        [settings] (hadesmem::PatchDetourBase *detour)
        {
            InitializeHook(detour, settings);
        });

    initializeDetour->Apply();

    if (settings->FoVSet)
    {
        auto const pFov = GetAddress(Version::WotLK, Offset::FoV);
        std::vector<std::uint8_t> patchData(sizeof(settings->FoV));
        memcpy(&patchData[0], &settings->FoV, sizeof(settings->FoV));
        auto patch = new hadesmem::PatchRaw(proc, pFov, patchData);
        patch->Apply();
    }
}
}

namespace Cata32
{
class CVar {};

using SetT = bool(__thiscall CVar::*)(const char *, char, char, char, char);
using InitializeT = void(__cdecl *)();
using LoginT = void(__cdecl *)(char *, char *);

void InitializeHook(hadesmem::PatchDetourBase *detour, GameSettings *settings)
{
    auto const initialize = detour->GetTrampolineT<InitializeT>();
    initialize();

    auto const cvar = *reinterpret_cast<CVar **>(GetAddress(Version::Cata32, Offset::RealmListCVar));
    auto const set = hadesmem::detail::AliasCast<SetT>(GetAddress(Version::Cata32, Offset::CVar__Set));

    (cvar->*set)(settings->AuthServer, 1, 0, 1, 0);

    detour->Remove();

    if (settings->CredentialsSet)
    {
        auto const login = hadesmem::detail::AliasCast<LoginT>(GetAddress(Version::Cata32, Offset::Login));
        login(settings->Username, settings->Password);
    }

    settings->LoadComplete = true;
}

void ApplyClientInitHook(GameSettings *settings)
{
    auto const proc = hadesmem::Process(::GetCurrentProcessId());
    auto const initializeOrig = hadesmem::detail::AliasCast<InitializeT>(GetAddress(Version::Cata32, Offset::Initialize));
    auto initializeDetour = new hadesmem::PatchDetour<InitializeT>(proc, initializeOrig,
        [settings](hadesmem::PatchDetourBase *detour)
        {
            InitializeHook(detour, settings);
        });

    initializeDetour->Apply();
}
}

namespace Cata64
{
class CVar {};

using SetT = bool(__fastcall CVar::*)(const char *, char, char, char, char);
using InitializeT = int (__stdcall *)();
using LoginT = void(__fastcall *)(char *, char *);

int InitializeHook(hadesmem::PatchDetourBase *detour, GameSettings *settings)
{
    auto const initialize = detour->GetTrampolineT<InitializeT>();
    auto const ret = initialize();

    auto const cvar = *reinterpret_cast<CVar **>(GetAddress(Version::Cata64, Offset::RealmListCVar));
    auto const set = hadesmem::detail::AliasCast<SetT>(GetAddress(Version::Cata64, Offset::CVar__Set));

    (cvar->*set)(settings->AuthServer, 1, 0, 1, 0);

    detour->Remove();

    if (settings->CredentialsSet)
    {
        auto const login = hadesmem::detail::AliasCast<LoginT>(GetAddress(Version::Cata64, Offset::Login));
        login(settings->Username, settings->Password);
    }

    settings->LoadComplete = true;

    return ret;
}

void ApplyClientInitHook(GameSettings *settings)
{
    auto const proc = hadesmem::Process(::GetCurrentProcessId());
    auto const initializeOrig = hadesmem::detail::AliasCast<InitializeT>(GetAddress(Version::Cata64, Offset::Initialize));
    auto initializeDetour = new hadesmem::PatchDetour<InitializeT>(proc, initializeOrig,
        [settings] (hadesmem::PatchDetourBase *detour)
        {
            return InitializeHook(detour, settings);
        });

    initializeDetour->Apply();
}
}<commit_msg>Update automatic login to only proceed once the alert url is rendered, when present.  This should fix issue #10.<commit_after>/*
    MIT License

    Copyright (c) 2018 namreeb http://github.com/namreeb legal@namreeb.org

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.

*/

#pragma comment(lib, "asmjit.lib")
#pragma comment(lib, "udis86.lib")

#include "InitializeHooks.hpp"

#include <hadesmem/patcher.hpp>

#include <cstdint>

namespace
{
enum class Version
{
    Classic = 0,
    TBC,
    WotLK,
    Cata32,
    Cata64,
    Total
};

enum class Offset
{
    CVar__Set = 0,
    RealmListCVar,
    Idle,
    CGlueMgr__m_pendingServerAlert,
    Login,
    FoV,
    Total
};

PVOID GetAddress(Version version, Offset offset)
{
    static constexpr std::uint32_t offsets[static_cast<int>(Version::Total)][static_cast<int>(Offset::Total)] =
    {
        // Classic
        {
            0x23DF50,
            0x82812C,
            0x6B930,
            0x741E28,
            0x6AFB0,
            0x4089B4
        },
        // TBC
        {
            0x23F6C0,
            0x943330,
            0x70160,
            0x807DB8,
            0x6E560,
            0x4B5A04
        },
        // WotLK
        {
            0x3668C0,
            0x879D00,
            0xDAB40,
            0x76AF88,
            0xD8A30,
            0x5E8D88
        },
        // Cata32
        {
            0x2553B0,
            0x9BE800,
            0x405310,
            0xABBF04,
            0x400240,
            0x00,   // not supported.  let me know if anyone actually wants this
        },
        // Cata64
        {
            0x2F61D0,
            0xCA4328,
            0x51A7C0,
            0xDACCA8,
            0x514100,
            0x00,   // not supported.  let me know if anyone actually wants this
        }
    };

    auto const baseAddress = reinterpret_cast<std::uint8_t *>(::GetModuleHandle(nullptr));

    return baseAddress + offsets[static_cast<int>(version)][static_cast<int>(offset)];
}
}

namespace Classic
{
class CVar {};

using SetT = bool(__thiscall CVar::*)(const char *, char, char, char, char);
using IdleT = int(__cdecl *)();
using LoginT = void(__fastcall *)(char *, char *);

int IdleHook(hadesmem::PatchDetourBase *detour, GameSettings *settings)
{
    auto const idle = detour->GetTrampolineT<IdleT>();
    auto const ret = idle();

    // if we are no longer waiting for the server alert, proceed with configuration
    if (!*reinterpret_cast<std::uint32_t *>(GetAddress(Version::Classic, Offset::CGlueMgr__m_pendingServerAlert)))
    {
        auto const cvar = *reinterpret_cast<CVar **>(GetAddress(Version::Classic, Offset::RealmListCVar));
        auto const set = hadesmem::detail::AliasCast<SetT>(GetAddress(Version::Classic, Offset::CVar__Set));

        (cvar->*set)(settings->AuthServer, 1, 0, 1, 0);

        detour->Remove();

        if (settings->CredentialsSet)
        {
            auto const login = hadesmem::detail::AliasCast<LoginT>(GetAddress(Version::Classic, Offset::Login));
            login(settings->Username, settings->Password);
        }

        settings->LoadComplete = true;
    }

    return ret;
}

void ApplyClientInitHook(GameSettings *settings)
{
    // just to make sure the value is initialized
    *reinterpret_cast<std::uint32_t *>(GetAddress(Version::Classic, Offset::CGlueMgr__m_pendingServerAlert)) = 1;

    auto const proc = hadesmem::Process(::GetCurrentProcessId());
    auto const idleOrig = hadesmem::detail::AliasCast<IdleT>(GetAddress(Version::Classic, Offset::Idle));
    auto idleDetour = new hadesmem::PatchDetour<IdleT>(proc, idleOrig,
        [settings] (hadesmem::PatchDetourBase *detour)
        {
            return IdleHook(detour, settings);
        });

    idleDetour->Apply();

    if (settings->FoVSet)
    {
        auto const pFov = GetAddress(Version::Classic, Offset::FoV);
        std::vector<std::uint8_t> patchData(sizeof(settings->FoV));
        memcpy(&patchData[0], &settings->FoV, sizeof(settings->FoV));
        auto patch = new hadesmem::PatchRaw(proc, pFov, patchData);
        patch->Apply();
    }
}
}

namespace TBC
{
class CVar {};

using SetT = bool(__thiscall CVar::*)(const char *, char, char, char, char);
using IdleT = int (__cdecl*)();
using LoginT = void(__cdecl *)(char *, char *);

int IdleHook(hadesmem::PatchDetourBase *detour, GameSettings *settings)
{
    auto const idle = detour->GetTrampolineT<IdleT>();
    auto const ret = idle();

    // if we are no longer waiting for the server alert, proceed with configuration
    if (!*reinterpret_cast<std::uint32_t *>(GetAddress(Version::TBC, Offset::CGlueMgr__m_pendingServerAlert)))
    {
        auto const cvar = *reinterpret_cast<CVar **>(GetAddress(Version::TBC, Offset::RealmListCVar));
        auto const set = hadesmem::detail::AliasCast<SetT>(GetAddress(Version::TBC, Offset::CVar__Set));

        (cvar->*set)(settings->AuthServer, 1, 0, 1, 0);

        detour->Remove();

        if (settings->CredentialsSet)
        {
            auto const login = hadesmem::detail::AliasCast<LoginT>(GetAddress(Version::TBC, Offset::Login));
            login(settings->Username, settings->Password);
        }

        settings->LoadComplete = true;
    }

    return ret;
}

void ApplyClientInitHook(GameSettings *settings)
{
    // just to make sure the value is initialized
    *reinterpret_cast<std::uint32_t *>(GetAddress(Version::TBC, Offset::CGlueMgr__m_pendingServerAlert)) = 1;

    auto const proc = hadesmem::Process(::GetCurrentProcessId());
    auto const idleOrig = hadesmem::detail::AliasCast<IdleT>(GetAddress(Version::TBC, Offset::Idle));
    auto idleDetour = new hadesmem::PatchDetour<IdleT>(proc, idleOrig,
        [settings] (hadesmem::PatchDetourBase *detour)
        {
            return IdleHook(detour, settings);
        });

    idleDetour->Apply();

    if (settings->FoVSet)
    {
        auto const pFov = GetAddress(Version::TBC, Offset::FoV);
        std::vector<std::uint8_t> patchData(sizeof(settings->FoV));
        memcpy(&patchData[0], &settings->FoV, sizeof(settings->FoV));
        auto patch = new hadesmem::PatchRaw(proc, pFov, patchData);
        patch->Apply();
    }
}
}

namespace WOTLK
{
class CVar {};

using SetT = bool(__thiscall CVar::*)(const char *, char, char, char, char);
using IdleT = int (__cdecl *)();
using LoginT = void(__cdecl *)(char *, char *);

int IdleHook(hadesmem::PatchDetourBase *detour, GameSettings *settings)
{
    auto const idle = detour->GetTrampolineT<IdleT>();
    auto const ret = idle();

    // if we are no longer waiting for the server alert, proceed with configuration
    if (!*reinterpret_cast<std::uint32_t *>(GetAddress(Version::WotLK, Offset::CGlueMgr__m_pendingServerAlert)))
    {
        auto const cvar = *reinterpret_cast<CVar **>(GetAddress(Version::WotLK, Offset::RealmListCVar));
        auto const set = hadesmem::detail::AliasCast<SetT>(GetAddress(Version::WotLK, Offset::CVar__Set));

        (cvar->*set)(settings->AuthServer, 1, 0, 1, 0);

        detour->Remove();

        if (settings->CredentialsSet)
        {
            auto const login = hadesmem::detail::AliasCast<LoginT>(GetAddress(Version::WotLK, Offset::Login));
            login(settings->Username, settings->Password);
        }

        settings->LoadComplete = true;
    }

    return ret;
}

void ApplyClientInitHook(GameSettings *settings)
{
    // just to make sure the value is initialized
    *reinterpret_cast<std::uint32_t *>(GetAddress(Version::WotLK, Offset::CGlueMgr__m_pendingServerAlert)) = 1;

    auto const proc = hadesmem::Process(::GetCurrentProcessId());
    auto const idleOrig = hadesmem::detail::AliasCast<IdleT>(GetAddress(Version::WotLK, Offset::Idle));
    auto idleDetour = new hadesmem::PatchDetour<IdleT>(proc, idleOrig,
        [settings] (hadesmem::PatchDetourBase *detour)
        {
            return IdleHook(detour, settings);
        });

    idleDetour->Apply();

    if (settings->FoVSet)
    {
        auto const pFov = GetAddress(Version::WotLK, Offset::FoV);
        std::vector<std::uint8_t> patchData(sizeof(settings->FoV));
        memcpy(&patchData[0], &settings->FoV, sizeof(settings->FoV));
        auto patch = new hadesmem::PatchRaw(proc, pFov, patchData);
        patch->Apply();
    }
}
}

namespace Cata32
{
class CVar {};

using SetT = bool(__thiscall CVar::*)(const char *, char, char, char, char);
using IdleT = int(__cdecl *)();
using LoginT = void(__cdecl *)(char *, char *);

int IdleHook(hadesmem::PatchDetourBase *detour, GameSettings *settings)
{
    auto const idle = detour->GetTrampolineT<IdleT>();
    auto const ret = idle();

    // if we are no longer waiting for the server alert, proceed with configuration
    if (!*reinterpret_cast<std::uint32_t *>(GetAddress(Version::Cata32, Offset::CGlueMgr__m_pendingServerAlert)))
    {
        auto const cvar = *reinterpret_cast<CVar **>(GetAddress(Version::Cata32, Offset::RealmListCVar));
        auto const set = hadesmem::detail::AliasCast<SetT>(GetAddress(Version::Cata32, Offset::CVar__Set));

        (cvar->*set)(settings->AuthServer, 1, 0, 1, 0);

        detour->Remove();

        if (settings->CredentialsSet)
        {
            auto const login = hadesmem::detail::AliasCast<LoginT>(GetAddress(Version::Cata32, Offset::Login));
            login(settings->Username, settings->Password);
        }

        settings->LoadComplete = true;
    }

    return ret;
}

void ApplyClientInitHook(GameSettings *settings)
{
    // just to make sure the value is initialized
    *reinterpret_cast<std::uint32_t *>(GetAddress(Version::Cata32, Offset::CGlueMgr__m_pendingServerAlert)) = 1;

    auto const proc = hadesmem::Process(::GetCurrentProcessId());
    auto const idleOrig = hadesmem::detail::AliasCast<IdleT>(GetAddress(Version::Cata32, Offset::Idle));
    auto idleDetour = new hadesmem::PatchDetour<IdleT>(proc, idleOrig,
        [settings](hadesmem::PatchDetourBase *detour)
        {
            return IdleHook(detour, settings);
        });

    idleDetour->Apply();
}
}

namespace Cata64
{
class CVar {};

using SetT = bool(__fastcall CVar::*)(const char *, char, char, char, char);
using IdleT = int (__stdcall *)();
using LoginT = void(__fastcall *)(char *, char *);

int IdleHook(hadesmem::PatchDetourBase *detour, GameSettings *settings)
{
    auto const idle = detour->GetTrampolineT<IdleT>();
    auto const ret = idle();

    // if we are no longer waiting for the server alert, proceed with configuration
    if (!*reinterpret_cast<std::uint32_t *>(GetAddress(Version::Cata64, Offset::CGlueMgr__m_pendingServerAlert)))
    {
        auto const cvar = *reinterpret_cast<CVar **>(GetAddress(Version::Cata64, Offset::RealmListCVar));
        auto const set = hadesmem::detail::AliasCast<SetT>(GetAddress(Version::Cata64, Offset::CVar__Set));

        (cvar->*set)(settings->AuthServer, 1, 0, 1, 0);

        detour->Remove();

        if (settings->CredentialsSet)
        {
            auto const login = hadesmem::detail::AliasCast<LoginT>(GetAddress(Version::Cata64, Offset::Login));
            login(settings->Username, settings->Password);
        }

        settings->LoadComplete = true;
    }

    return ret;
}

void ApplyClientInitHook(GameSettings *settings)
{
    // just to make sure the value is initialized
    *reinterpret_cast<std::uint32_t *>(GetAddress(Version::Cata64, Offset::CGlueMgr__m_pendingServerAlert)) = 1;

    auto const proc = hadesmem::Process(::GetCurrentProcessId());
    auto const idleOrig = hadesmem::detail::AliasCast<IdleT>(GetAddress(Version::Cata64, Offset::Idle));
    auto idleDetour = new hadesmem::PatchDetour<IdleT>(proc, idleOrig,
        [settings] (hadesmem::PatchDetourBase *detour)
        {
            return IdleHook(detour, settings);
        });

    idleDetour->Apply();
}
}<|endoftext|>


<commit_before>#pragma once

#include <Gamma/Envelope.h>
#include <Gamma/Filter.h>
#include <Gamma/Oscillator.h>
#include <Gamma/Noise.h>
#include "util/dsp/overdrive.hpp"

#include "core/voices/voice_manager.hpp"
#include "goss.hpp"

//TODO: draw model

namespace otto::engines::goss {

  struct Voice : voices::VoiceBase<Voice> {
    Voice(Audio& a) noexcept;

    float operator()() noexcept;

    void on_note_on(float) noexcept;
    void on_note_off() noexcept;

    /// Use actions from base class
    using VoiceBase::action;

    void action(itc::prop_change<&Props::click>, float c) noexcept;

    void action(itc::prop_change<&Props::model>, int m) noexcept;

    void action(voices::attack_tag::action, float a) noexcept
    {
      env_.attack(a * a * 8.f + 0.01f);
    }
    void action(voices::decay_tag::action, float d) noexcept
    {
      env_.decay(d * d * 4.f + 0.01f);
    }
    void action(voices::sustain_tag::action, float s) noexcept
    {
      env_.sustain(s);
    }
    void action(voices::release_tag::action, float r) noexcept
    {
      env_.release(r * r * 8.f + 0.01f);
    }

  private:
    Audio& audio;

    /// Does not allocate tables. Reads from models in Audio.
    gam::Osc<> voice_player;
    gam::Osc<> percussion_player;

    gam::NoiseBrown<> noise;
    gam::AD<> perc_env{0.01, 0.08};
    gam::ADSR<> env_ = {0.1f, 0.1f, 0.7f, 2.0f, 1.f, -4.f};
  };

  struct Audio {
    Audio() noexcept;

    void action(Actions::rotation_variable, std::atomic<float>&) noexcept;

    void action(itc::prop_change<&Props::drive>, float d) noexcept;

    void action(itc::prop_change<&Props::leslie>, float l) noexcept;

    template<typename Tag, typename... Args>
    auto action(itc::Action<Tag, Args...> a, Args... args) noexcept
      -> std::enable_if_t<itc::ActionReciever::is<voices::VoiceManager<Voice, 6>, itc::Action<Tag, Args...>>>
    {
      voice_mgr_.action(a, args...);
    }

    float operator()() noexcept;

    audio::ProcessData<1> process(audio::ProcessData<1>) noexcept;

  private:
    friend Voice;

    std::atomic<float>* shared_rotation = nullptr;

    void generate_model(gam::Osc<>&, model_type);

    std::array<gam::Osc<>, number_of_models> models;

    gam::Osc<> percussion = {1, 0, 2048};

    float gain = 0.f;
    float output_scaling = 0.f;

    float leslie = 0.f;

    float leslie_speed_hi = 0.f;
    float leslie_speed_lo = 0.f;
    float leslie_amount_hi = 0.f;
    float leslie_amount_lo = 0.f;

    gam::LFO<> leslie_filter_hi;
    gam::LFO<> leslie_filter_lo;
    gam::LFO<> pitch_modulation_hi;

    gam::AccumPhase<> rotation;

    gam::Biquad<> lpf;
    gam::Biquad<> hpf;

    util::dsp::HammondPreamp overdrive;

    voices::VoiceManager<Voice, 6> voice_mgr_ = {*this};
  };
} // namespace otto::engines::goss
<commit_msg>Goss: removing deprecated overdrive<commit_after>#pragma once

#include <Gamma/Envelope.h>
#include <Gamma/Filter.h>
#include <Gamma/Oscillator.h>
#include <Gamma/Noise.h>
#include "util/dsp/overdrive.hpp"

#include "core/voices/voice_manager.hpp"
#include "goss.hpp"

//TODO: draw model

namespace otto::engines::goss {

  struct Voice : voices::VoiceBase<Voice> {
    Voice(Audio& a) noexcept;

    float operator()() noexcept;

    void on_note_on(float) noexcept;
    void on_note_off() noexcept;

    /// Use actions from base class
    using VoiceBase::action;

    void action(itc::prop_change<&Props::click>, float c) noexcept;

    void action(itc::prop_change<&Props::model>, int m) noexcept;

    void action(voices::attack_tag::action, float a) noexcept
    {
      env_.attack(a * a * 8.f + 0.01f);
    }
    void action(voices::decay_tag::action, float d) noexcept
    {
      env_.decay(d * d * 4.f + 0.01f);
    }
    void action(voices::sustain_tag::action, float s) noexcept
    {
      env_.sustain(s);
    }
    void action(voices::release_tag::action, float r) noexcept
    {
      env_.release(r * r * 8.f + 0.01f);
    }

  private:
    Audio& audio;

    /// Does not allocate tables. Reads from models in Audio.
    gam::Osc<> voice_player;
    gam::Osc<> percussion_player;

    gam::NoiseBrown<> noise;
    gam::AD<> perc_env{0.01, 0.08};
    gam::ADSR<> env_ = {0.1f, 0.1f, 0.7f, 2.0f, 1.f, -4.f};
  };

  struct Audio {
    Audio() noexcept;

    void action(Actions::rotation_variable, std::atomic<float>&) noexcept;

    void action(itc::prop_change<&Props::drive>, float d) noexcept;

    void action(itc::prop_change<&Props::leslie>, float l) noexcept;

    template<typename Tag, typename... Args>
    auto action(itc::Action<Tag, Args...> a, Args... args) noexcept
      -> std::enable_if_t<itc::ActionReciever::is<voices::VoiceManager<Voice, 6>, itc::Action<Tag, Args...>>>
    {
      voice_mgr_.action(a, args...);
    }

    float operator()() noexcept;

    audio::ProcessData<1> process(audio::ProcessData<1>) noexcept;

  private:
    friend Voice;

    std::atomic<float>* shared_rotation = nullptr;

    void generate_model(gam::Osc<>&, model_type);

    std::array<gam::Osc<>, number_of_models> models;

    gam::Osc<> percussion = {1, 0, 2048};

    float gain = 0.f;
    float output_scaling = 0.f;

    float leslie = 0.f;

    float leslie_speed_hi = 0.f;
    float leslie_speed_lo = 0.f;
    float leslie_amount_hi = 0.f;
    float leslie_amount_lo = 0.f;

    gam::LFO<> leslie_filter_hi;
    gam::LFO<> leslie_filter_lo;
    gam::LFO<> pitch_modulation_hi;

    gam::AccumPhase<> rotation;

    gam::Biquad<> lpf;
    gam::Biquad<> hpf;

    voices::VoiceManager<Voice, 6> voice_mgr_ = {*this};
  };
} // namespace otto::engines::goss
<|endoftext|>


<commit_before>#include "estimator/world_estimator.hpp"

#include <cstdio>
#include "hal.h"

#include "unit_config.hpp"

WorldEstimator::WorldEstimator(
    LocationEstimator& locEstimator,
    AttitudeEstimator& attEstimator,
    Communicator& communicator)
  : locEstimator(locEstimator),
    attEstimator(attEstimator),
    worldMessageStream(communicator, 10) {
}

WorldEstimate WorldEstimator::update(const SensorMeasurements& meas) {
  // TODO: get GPS data

  WorldEstimate estimate {
    .loc = locEstimator.update(meas),
    .att = attEstimator.update(meas)
  };

  if(worldMessageStream.ready()) {
    // protocol::message::log_message_t m;
    // sprintf(m.data, "world estimate test");
    //
    // worldMessageStream.publish(m);
  }

  return estimate;
}
<commit_msg>Reduce debug rate.<commit_after>#include "estimator/world_estimator.hpp"

#include <cstdio>
#include "hal.h"

#include "unit_config.hpp"

WorldEstimator::WorldEstimator(
    LocationEstimator& locEstimator,
    AttitudeEstimator& attEstimator,
    Communicator& communicator)
  : locEstimator(locEstimator),
    attEstimator(attEstimator),
    worldMessageStream(communicator, 1) {
}

WorldEstimate WorldEstimator::update(const SensorMeasurements& meas) {
  // TODO: get GPS data

  WorldEstimate estimate {
    .loc = locEstimator.update(meas),
    .att = attEstimator.update(meas)
  };

  if(worldMessageStream.ready() && meas.gps) {
    // protocol::message::log_message_t m;
    // sprintf(m.data, "gps: %d %d", (int) (*meas.gps).lat * 1000, (int) (*meas.gps).lon * 1000);
    //
    // worldMessageStream.publish(m);
  }

  return estimate;
}
<|endoftext|>


<commit_before>//
// Created by Kevin on 15/10/2016.
//

#include <stdexcept>
#include "GPU/GPU.h"

using namespace std;
using namespace Gameboy::GPU;
using namespace Gameboy::General;

GPU::GPU(Gameboy::CPU::IInterruptible &p_interruptible, IReadable& p_readableMemoryMappedIO, IVideoOutputDevice& p_outputDevice)
    : interruptible (p_interruptible),
      readableMemoryMappedIO (p_readableMemoryMappedIO),
      outputDevice (p_outputDevice),
      frameBuffer (WIDTH * HEIGHT),
      clock (),
      gpuReg (),
      colors {COLOUR0, COLOUR1, COLOUR2, COLOUR3},
      windowYPosition (),
      lastWindowYPosition ()
{
    gpuReg[OffSTAT] = OAMUsed;
    videoRam.initialise(Memory::MemoryType(8192));
    spriteRam.initialise(Memory::MemoryType(160));
}

void GPU::next(uint32_t ticks) {
    if (!isLcdOn()) {
        return;
    }
    clock += ticks;
    Mode mode = getMode();
    switch (mode) {
        case HBlank:
            if (clock >= 204) {
                // next mode is either mode 1 - VBlank or mode 2 - OAMUsed
                clock -= 204;
                incrementLY();
                if (gpuReg[OffLY] == HEIGHT) {
                    outputDevice.render(frameBuffer.data());
                    setMode(VBlank);
                    // Frame is ready, commit it
                } else {
                    setMode(OAMUsed);
                }
            }
            break;
        case VBlank:
            if (clock >= 456) {
                clock -= 456;
                incrementLY();
                if (gpuReg[OffLY] == 0) {
                    windowYPosition = gpuReg[OffWY];
                    lastWindowYPosition = 0;
                    setMode(OAMUsed);
                }
            }
            break;
        case OAMUsed:
            if (clock >= 80) {
                // next mode is mode 3 - no interrupts to raise
                clock -= 80;
                setMode(OAMVRamUsed);
            }
            break;
        case OAMVRamUsed:
            if (clock >= 172) {
                // render scanLine
                renderScanLine();
                // next mode is mode 0 - HBlank
                clock -= 172;
                setMode(HBlank);
            }
            break;
    }
}

// LCDC
bool GPU::isLcdOn() const {
    return (gpuReg[OffLCDC] & (1 << 7)) != 0;
}

uint16_t GPU::getWindowTileMapOffset() const {
    return static_cast<uint16_t>((gpuReg[OffLCDC] & (1 << 6)) ? 0x9C00 : 0x9800) - VideoRam;
}

bool GPU::isWindowDisplayOn() const {
    return (gpuReg[OffLCDC] & (1 << 5)) != 0;
}

uint16_t GPU::getBgWindowTileDataOffset() const {
    // When 9000, signed addressing is used
    return static_cast<uint16_t>((gpuReg[OffLCDC] & (1 << 4)) ? 0x8000 : 0x9000) - VideoRam;
}

uint16_t GPU::getBgTileMapOffset() const {
    return static_cast<uint16_t>((gpuReg[OffLCDC] & (1 << 3)) ? 0x9C00 : 0x9800) - VideoRam;
}

uint8_t GPU::getSpriteHeight() const {
    return static_cast<uint8_t>((gpuReg[OffLCDC] & (1 << 2)) ? 16 : 8);
}

bool GPU::isSpriteDisplayOn() const {
    return (gpuReg[OffLCDC] & (1 << 1)) != 0;
}

bool GPU::isBgDisplayOn() const {
    return (gpuReg[OffLCDC] & (1 << 0)) != 0;
}

// STAT
void GPU::setMode(Mode mode) {
    gpuReg[OffSTAT] &= 0xFC;
    gpuReg[OffSTAT] |= mode;
    if ((mode != OAMVRamUsed) && (gpuReg[OffSTAT] & (1 << (3 + mode)))) {
        interruptible.requestInterrupt(StatIrq);
    }
    if (mode == VBlank) {
        interruptible.requestInterrupt(VBlankIrq);
    }
}

void GPU::incrementLY() {
    if (++gpuReg[OffLY] == 154) {
        gpuReg[OffLY] = 0;
    }
    // check coincidence flag
    if (gpuReg[OffLY] == gpuReg[OffLYC]) {
        gpuReg[OffSTAT] |= (1 << 2);
        // Check and raise coincidence interrupt
        if (gpuReg[OffSTAT] & (1 << 6)) {
            interruptible.requestInterrupt(StatIrq);
        }
    } else {
        gpuReg[OffSTAT] &= ~(1 << 2);
    }
}

Mode GPU::getMode() const {
    return static_cast<Mode>(gpuReg[OffSTAT] & 0x03);
}

uint8_t GPU::read(uint16_t address) const {
    if (address >= VideoRam && address < RamBankN) {
        // VRam
        if (isLcdOn() && (getMode() == Mode::OAMVRamUsed)) {
            throw runtime_error ("Cannot read from VRAM, it is in use by the GPU");
        }
        return videoRam.readExt(address - VideoRam);
    } else if (address >= SpriteRam && address < UnusableIO1) {
        // Sprite Ram
        if (isLcdOn() && (getMode() & 0x02)) {
            throw runtime_error ("Cannot read from Sprite Ram, it is in use by the GPU");
        }
        return spriteRam.readExt(address - SpriteRam);
    } else if (address >= LCDC && address <= WX) {
        // Registers
        return gpuReg[address - LCDC];
    }
    throw runtime_error ("GPU read out of range: " + to_string(address));
}

void GPU::write(uint16_t address, uint8_t datum) {
    if (address >= VideoRam && address < RamBankN) {
        // VRam
        if (isLcdOn() && (getMode() == Mode::OAMVRamUsed)) {
            throw runtime_error ("Cannot write to VRAM, it is in use by the GPU");
        }
        return videoRam.writeExt(address - VideoRam, datum);
    } else if (address >= SpriteRam && address < UnusableIO1) {
        // Sprite Ram
        if (isLcdOn() && (getMode() & 0x02)) {
            throw runtime_error ("Cannot write to Sprite Ram, it is in use by the GPU");
        }
        return spriteRam.writeExt(address - SpriteRam, datum);
    } else if (address >= LCDC && address <= WX) {
        // Registers
        const RegisterOffset offset = static_cast<RegisterOffset>(address - LCDC);
        switch (offset) {
            case OffLCDC: // 0xFF40
                gpuReg[offset] = datum;
                break;
            case OffSTAT: // 0xFF41
                gpuReg[offset] &= 0x03;
                gpuReg[offset] |= datum & 0xFC;
                break;
            case OffSCY:  // 0xFF42
            case OffSCX:  // 0xFF43
                gpuReg[offset] = datum;
                break;
            case OffLY:   // 0xFF44
                throw runtime_error("Attempted write to LY Register, datum:" + to_string(static_cast<uint32_t>(datum)));
            case OffLYC:  // 0xFF45
                gpuReg[offset] = datum;
                break;
            case OffDMA:  // 0xFF46
                // Perform DMA transfer
            {
                const uint16_t baseAddress = static_cast<uint16_t>(datum) << 8;
                for (uint8_t i = 0; i < (UnusableIO1 - SpriteRam); ++i) {
                    //write(SpriteRam + i, readableMemoryMappedIO.read(baseAddress + i));
                    // Rough implementation, need to emulate more
                    // TODO: This takes 671 ticks, data wriging should be probably emulated
                    // during this course of time. Or at the very least, lock the whole memory map
                    // except High Ram.
                    spriteRam.writeExt(i, readableMemoryMappedIO.read(baseAddress + i));
                }
            }
                break;
            case OffBGP:  // 0xFF47
            case OffOBP0: // 0xFF48
            case OffOBP1: // 0xFF49
            case OffWY:   // 0xFF4A
            case OffWX:   // 0xFF4B
                gpuReg[offset] = datum;
            break;
        }
        return;
    }
    throw runtime_error ("GPU write out of range: " + to_string(address) + ", datum: " + to_string(static_cast<uint32_t>(datum)));
}

void GPU::renderScanLine() {

    const bool bgEnabled = isBgDisplayOn();
    const bool windowEnabled = isWindowDisplayOn()
                               && (/*gpuReg[OffWY]*/ windowYPosition <= gpuReg[OffLY])
                               && (gpuReg[OffWX] <= 166);

    // TODO: Check if keeping value as unsigned becomes a problem with comparisons
    const int16_t alteredWX = static_cast<int16_t>(gpuReg[OffWX] - 7);

    // Data offset for both background and window
    const uint16_t dataOffset = getBgWindowTileDataOffset();
    const bool negativeAddressing = dataOffset != 0;

    // Get the background and Window Map
    const uint16_t backgroundMapOffset = getBgTileMapOffset();
    const uint16_t windowMapOffset = getWindowTileMapOffset();

    // Render Background and Window
    for (uint8_t x = 0; x < WIDTH; ++x) {

        // Determine whether to draw using window or background
        uint16_t mapOffset;

        // Pixel offsets
        uint8_t xOffset;
        uint8_t yOffset;

        // In below comparison, x and alteredWX are both promoted to 'int'
        if (windowEnabled && (x >= alteredWX) && (x <= alteredWX)) {
            // Use window
            mapOffset = windowMapOffset;

            // Both values are promoted to 'int' before subtraction takes place
            xOffset = static_cast<uint8_t>(x - alteredWX);

            // Handle case where window is interrupted and resumed at a later line
            // This value is updated to WY upon exiting VBLANK
            yOffset = lastWindowYPosition++;
        } else if (bgEnabled) {
            // Use background
            mapOffset = backgroundMapOffset;
            xOffset = gpuReg[OffSCX] + x;
            yOffset = gpuReg[OffSCY] + gpuReg[OffLY];
        } else {
            // Commit to frame buffer the colour 0 - White
            frameBuffer[gpuReg[OffLY] * WIDTH + x] = colors[0];
            continue;
        }

        // Tile offsets - Divide by 8 since tiles are 8x8 and division will transform
        // view from pixel coordinates to tile coordinates
        const uint8_t yTileOffset = yOffset >> 3; // Divide by 8

        // Select one of the 8 rows found in the tile
        const uint8_t tileLineIndex = yOffset & static_cast<uint8_t>(0x07);

        // Divide by 8 to transform from pixel coordinates to tile coordinates
        const uint8_t xTileOffset = xOffset >> 3;

        // Get tile number from tile map - multiply yTileOffset by 32 as there are 32 tiles within one row
        const uint8_t tileNumber = videoRam.readExt(mapOffset +
                                          // & ~0x400 is the same as % 1024
                                          static_cast<size_t>(((yTileOffset << 5) + xTileOffset) & ~0x400));

        // Get tile address from the tile index, this address is used to compose the final address
        // to read data from the tile data region
        const uint16_t tileAddress =
                dataOffset + (negativeAddressing ? (static_cast<int8_t>(tileNumber) << 4) : tileNumber << 4);

        // Tile x-Pixel Index
        const uint8_t tileXPixelIndex = xOffset & static_cast<uint8_t>(0x07);

        // Get 2-bit value - multiply tileLineIndex by two as each line is composed of 2-bytes
        const uint16_t finalAddress = tileAddress + (tileLineIndex << 1);
        uint8_t pixelValue = static_cast<uint8_t>((videoRam.readExt(finalAddress + 1) &
                                                    (1 << (7 - tileXPixelIndex))) ? 2 : 0);
        pixelValue |= (videoRam.readExt(finalAddress) >> (7 - tileXPixelIndex)) & 1;

        // Get palette colour value
        const uint8_t colorIndexValue = static_cast<uint8_t>((gpuReg[OffBGP] >> (pixelValue << 1)) & 0x03);

        // Commit to frame buffer
        frameBuffer[gpuReg[OffLY] * WIDTH + x] = colors[colorIndexValue];
    }

    if (isSpriteDisplayOn()) {

        /*uint8_t spritesAdded = 0;
        struct {
            uint8_t index;
            int16_t x;
        } spritesToRender[10];*/

        const uint8_t spriteHeight = getSpriteHeight();

        // Render Sprites (max 10 per line)
        for (uint8_t sprite = 0; sprite < 40; ++sprite) {

            // Determine the ten highest priority sprites to survive

            // Check whether it is part of this scan-line
            const int16_t spriteY1 = static_cast<int16_t>(spriteRam.readExt((sprite << 2)) - 16);
            const int16_t spriteY2 = spriteY1 + spriteHeight;
            if (gpuReg[OffLY] >= spriteY1 && gpuReg[OffLY] <= spriteY2) {

                // Get the x-coordinates
                const uint8_t spriteX2 = spriteRam.readExt((sprite << 2) + 1);
                const int16_t spriteX1 = static_cast<int16_t>(spriteX2 - 8);

                // Check if in screen coordinates
                if (spriteX1 >= WIDTH || static_cast<int16_t>(spriteX2) < 0) {
                    continue;
                }

                /*
                // Check whether this sprite has priority over the other sprites
                bool collision = false;
                for (size_t i = 0; (!collision) && (i < spritesAdded); ++i) {
                    // Check if we collide, then check if we have priority
                    if ((spriteX1 <= spritesToRender[i].x) && (spritesToRender[i].x - spriteX1) < 8) {
                        spritesToRender[i].x = spriteX1;
                        spritesToRender[i].index = sprite;
                        collision = true;
                    }
                }

                // We did not collide, add if possible
                if (!collision && (spritesAdded < 10)) {
                    spritesToRender[spritesAdded].x = spriteX1;
                    spritesToRender[spritesAdded].index = sprite;
                    ++spritesAdded;
                }
                 */

            }
        }
    }
}

<commit_msg>Add first working version of sprites<commit_after>//
// Created by Kevin on 15/10/2016.
//

#include <stdexcept>
#include "GPU/GPU.h"

using namespace std;
using namespace Gameboy::GPU;
using namespace Gameboy::General;

GPU::GPU(Gameboy::CPU::IInterruptible &p_interruptible, IReadable& p_readableMemoryMappedIO, IVideoOutputDevice& p_outputDevice)
    : interruptible (p_interruptible),
      readableMemoryMappedIO (p_readableMemoryMappedIO),
      outputDevice (p_outputDevice),
      frameBuffer (WIDTH * HEIGHT),
      clock (),
      gpuReg (),
      colors {COLOUR0, COLOUR1, COLOUR2, COLOUR3},
      windowYPosition (),
      lastWindowYPosition ()
{
    gpuReg[OffSTAT] = OAMUsed;
    videoRam.initialise(Memory::MemoryType(8192));
    spriteRam.initialise(Memory::MemoryType(160));
}

void GPU::next(uint32_t ticks) {
    if (!isLcdOn()) {
        return;
    }
    clock += ticks;
    Mode mode = getMode();
    switch (mode) {
        case HBlank:
            if (clock >= 204) {
                // next mode is either mode 1 - VBlank or mode 2 - OAMUsed
                clock -= 204;
                incrementLY();
                if (gpuReg[OffLY] == HEIGHT) {
                    outputDevice.render(frameBuffer.data());
                    setMode(VBlank);
                    // Frame is ready, commit it
                } else {
                    setMode(OAMUsed);
                }
            }
            break;
        case VBlank:
            if (clock >= 456) {
                clock -= 456;
                incrementLY();
                if (gpuReg[OffLY] == 0) {
                    windowYPosition = gpuReg[OffWY];
                    lastWindowYPosition = 0;
                    setMode(OAMUsed);
                }
            }
            break;
        case OAMUsed:
            if (clock >= 80) {
                // next mode is mode 3 - no interrupts to raise
                clock -= 80;
                setMode(OAMVRamUsed);
            }
            break;
        case OAMVRamUsed:
            if (clock >= 172) {
                // render scanLine
                renderScanLine();
                // next mode is mode 0 - HBlank
                clock -= 172;
                setMode(HBlank);
            }
            break;
    }
}

// LCDC
bool GPU::isLcdOn() const {
    return (gpuReg[OffLCDC] & (1 << 7)) != 0;
}

uint16_t GPU::getWindowTileMapOffset() const {
    return static_cast<uint16_t>((gpuReg[OffLCDC] & (1 << 6)) ? 0x9C00 : 0x9800) - VideoRam;
}

bool GPU::isWindowDisplayOn() const {
    return (gpuReg[OffLCDC] & (1 << 5)) != 0;
}

uint16_t GPU::getBgWindowTileDataOffset() const {
    // When 9000, signed addressing is used
    return static_cast<uint16_t>((gpuReg[OffLCDC] & (1 << 4)) ? 0x8000 : 0x9000) - VideoRam;
}

uint16_t GPU::getBgTileMapOffset() const {
    return static_cast<uint16_t>((gpuReg[OffLCDC] & (1 << 3)) ? 0x9C00 : 0x9800) - VideoRam;
}

uint8_t GPU::getSpriteHeight() const {
    return static_cast<uint8_t>((gpuReg[OffLCDC] & (1 << 2)) ? 16 : 8);
}

bool GPU::isSpriteDisplayOn() const {
    return (gpuReg[OffLCDC] & (1 << 1)) != 0;
}

bool GPU::isBgDisplayOn() const {
    return (gpuReg[OffLCDC] & (1 << 0)) != 0;
}

// STAT
void GPU::setMode(Mode mode) {
    gpuReg[OffSTAT] &= 0xFC;
    gpuReg[OffSTAT] |= mode;
    if ((mode != OAMVRamUsed) && (gpuReg[OffSTAT] & (1 << (3 + mode)))) {
        interruptible.requestInterrupt(StatIrq);
    }
    if (mode == VBlank) {
        interruptible.requestInterrupt(VBlankIrq);
    }
}

void GPU::incrementLY() {
    if (++gpuReg[OffLY] == 154) {
        gpuReg[OffLY] = 0;
    }
    // check coincidence flag
    if (gpuReg[OffLY] == gpuReg[OffLYC]) {
        gpuReg[OffSTAT] |= (1 << 2);
        // Check and raise coincidence interrupt
        if (gpuReg[OffSTAT] & (1 << 6)) {
            interruptible.requestInterrupt(StatIrq);
        }
    } else {
        gpuReg[OffSTAT] &= ~(1 << 2);
    }
}

Mode GPU::getMode() const {
    return static_cast<Mode>(gpuReg[OffSTAT] & 0x03);
}

uint8_t GPU::read(uint16_t address) const {
    if (address >= VideoRam && address < RamBankN) {
        // VRam
        if (isLcdOn() && (getMode() == Mode::OAMVRamUsed)) {
            throw runtime_error ("Cannot read from VRAM, it is in use by the GPU");
        }
        return videoRam.readExt(address - VideoRam);
    } else if (address >= SpriteRam && address < UnusableIO1) {
        // Sprite Ram
        if (isLcdOn() && (getMode() & 0x02)) {
            throw runtime_error ("Cannot read from Sprite Ram, it is in use by the GPU");
        }
        return spriteRam.readExt(address - SpriteRam);
    } else if (address >= LCDC && address <= WX) {
        // Registers
        return gpuReg[address - LCDC];
    }
    throw runtime_error ("GPU read out of range: " + to_string(address));
}

void GPU::write(uint16_t address, uint8_t datum) {
    if (address >= VideoRam && address < RamBankN) {
        // VRam
        if (isLcdOn() && (getMode() == Mode::OAMVRamUsed)) {
            throw runtime_error ("Cannot write to VRAM, it is in use by the GPU");
        }
        return videoRam.writeExt(address - VideoRam, datum);
    } else if (address >= SpriteRam && address < UnusableIO1) {
        // Sprite Ram
        if (isLcdOn() && (getMode() & 0x02)) {
            throw runtime_error ("Cannot write to Sprite Ram, it is in use by the GPU");
        }
        return spriteRam.writeExt(address - SpriteRam, datum);
    } else if (address >= LCDC && address <= WX) {
        // Registers
        const RegisterOffset offset = static_cast<RegisterOffset>(address - LCDC);
        switch (offset) {
            case OffLCDC: // 0xFF40
                gpuReg[offset] = datum;
                break;
            case OffSTAT: // 0xFF41
                gpuReg[offset] &= 0x03;
                gpuReg[offset] |= datum & 0xFC;
                break;
            case OffSCY:  // 0xFF42
            case OffSCX:  // 0xFF43
                gpuReg[offset] = datum;
                break;
            case OffLY:   // 0xFF44
                throw runtime_error("Attempted write to LY Register, datum:" + to_string(static_cast<uint32_t>(datum)));
            case OffLYC:  // 0xFF45
                gpuReg[offset] = datum;
                break;
            case OffDMA:  // 0xFF46
                // Perform DMA transfer
            {
                const uint16_t baseAddress = static_cast<uint16_t>(datum) << 8;
                for (uint8_t i = 0; i < (UnusableIO1 - SpriteRam); ++i) {
                    //write(SpriteRam + i, readableMemoryMappedIO.read(baseAddress + i));
                    // Rough implementation, need to emulate more
                    // TODO: This takes 671 ticks, data wriging should be probably emulated
                    // during this course of time. Or at the very least, lock the whole memory map
                    // except High Ram.
                    spriteRam.writeExt(i, readableMemoryMappedIO.read(baseAddress + i));
                }
            }
                break;
            case OffBGP:  // 0xFF47
            case OffOBP0: // 0xFF48
            case OffOBP1: // 0xFF49
            case OffWY:   // 0xFF4A
            case OffWX:   // 0xFF4B
                gpuReg[offset] = datum;
            break;
        }
        return;
    }
    throw runtime_error ("GPU write out of range: " + to_string(address) + ", datum: " + to_string(static_cast<uint32_t>(datum)));
}

void GPU::renderScanLine() {

    const bool bgEnabled = isBgDisplayOn();
    const bool windowEnabled = isWindowDisplayOn()
                               && (/*gpuReg[OffWY]*/ windowYPosition <= gpuReg[OffLY])
                               && (gpuReg[OffWX] <= 166);

    // TODO: Check if keeping value as unsigned becomes a problem with comparisons
    const int16_t alteredWX = static_cast<int16_t>(gpuReg[OffWX] - 7);

    // Data offset for both background and window
    const uint16_t dataOffset = getBgWindowTileDataOffset();
    const bool negativeAddressing = dataOffset != 0;

    // Get the background and Window Map
    const uint16_t backgroundMapOffset = getBgTileMapOffset();
    const uint16_t windowMapOffset = getWindowTileMapOffset();

    // Render Background and Window
    for (uint8_t x = 0; x < WIDTH; ++x) {

        // Determine whether to draw using window or background
        uint16_t mapOffset;

        // Pixel offsets
        uint8_t xOffset;
        uint8_t yOffset;

        // In below comparison, x and alteredWX are both promoted to 'int'
        if (windowEnabled && (x >= alteredWX) /*&& (x <= (alteredWX + WIDTH))*/) {
            // Use window
            mapOffset = windowMapOffset;

            // Both values are promoted to 'int' before subtraction takes place
            xOffset = static_cast<uint8_t>(x - alteredWX);

            // Handle case where window is interrupted and resumed at a later line
            // This value is updated to WY upon exiting VBLANK
            yOffset = lastWindowYPosition;
        } else if (bgEnabled) {
            // Use background
            mapOffset = backgroundMapOffset;
            xOffset = gpuReg[OffSCX] + x;
            yOffset = gpuReg[OffSCY] + gpuReg[OffLY];
        } else {
            // Commit to frame buffer the colour 0 - White
            frameBuffer[gpuReg[OffLY] * WIDTH + x] = colors[0];
            continue;
        }

        // Tile offsets - Divide by 8 since tiles are 8x8 and division will transform
        // view from pixel coordinates to tile coordinates
        const uint8_t yTileOffset = yOffset >> 3; // Divide by 8

        // Select one of the 8 rows found in the tile
        const uint8_t tileLineIndex = yOffset & static_cast<uint8_t>(0x07);

        // Divide by 8 to transform from pixel coordinates to tile coordinates
        const uint8_t xTileOffset = xOffset >> 3;

        // Get tile number from tile map - multiply yTileOffset by 32 as there are 32 tiles within one row
        const uint8_t tileNumber = videoRam.readExt(mapOffset +
                                          // & ~0x400 is the same as % 1024
                                          static_cast<size_t>(((yTileOffset << 5) + xTileOffset) & ~0x400));

        // Get tile address from the tile index, this address is used to compose the final address
        // to read data from the tile data region
        const uint16_t tileAddress =
                dataOffset + (negativeAddressing ? (static_cast<int8_t>(tileNumber) << 4) : tileNumber << 4);

        // Tile x-Pixel Index
        const uint8_t tileXPixelIndex = xOffset & static_cast<uint8_t>(0x07);

        // Get 2-bit value - multiply tileLineIndex by two as each line is composed of 2-bytes
        const uint16_t finalAddress = tileAddress + (tileLineIndex << 1);
        uint8_t pixelValue = static_cast<uint8_t>((videoRam.readExt(finalAddress + 1) &
                                                    (1 << (7 - tileXPixelIndex))) ? 2 : 0);
        pixelValue |= (videoRam.readExt(finalAddress) >> (7 - tileXPixelIndex)) & 1;

        // Get palette colour value
        const uint8_t colorIndexValue = static_cast<uint8_t>((gpuReg[OffBGP] >> (pixelValue << 1)) & 0x03);

        // Commit to frame buffer
        frameBuffer[gpuReg[OffLY] * WIDTH + x] = colors[colorIndexValue];
    }

    if (windowEnabled) {
        ++lastWindowYPosition;
    }

    if (isSpriteDisplayOn()) {

        /*uint8_t spritesAdded = 0;
        struct {
            uint8_t index;
            int16_t x;
        } spritesToRender[10];*/

        const uint8_t spriteHeight = getSpriteHeight();

        // Render Sprites (max 10 per line)
        for (uint8_t sprite = 0; sprite < 40; ++sprite) {

            // Determine the ten highest priority sprites to survive

            // Check whether it is part of this scan-line
            const int16_t spriteY1 = static_cast<int16_t>(spriteRam.readExt((sprite << 2)) - 16);
            const int16_t spriteY2 = spriteY1 + spriteHeight - 1;
            if (gpuReg[OffLY] >= spriteY1 && gpuReg[OffLY] <= spriteY2) {

                // Get the x-coordinates
                const uint8_t spriteX2 = spriteRam.readExt((sprite << 2) + 1);
                const int16_t spriteX1 = static_cast<int16_t>(spriteX2 - 8);

                // Check if in screen coordinates
                if (spriteX1 >= WIDTH || static_cast<int16_t>(spriteX2) < 0) {
                    continue;
                }

                /*
                // Check whether this sprite has priority over the other sprites
                bool collision = false;
                for (size_t i = 0; (!collision) && (i < spritesAdded); ++i) {
                    // Check if we collide, then check if we have priority
                    if ((spriteX1 <= spritesToRender[i].x) && (spritesToRender[i].x - spriteX1) < 8) {
                        spritesToRender[i].x = spriteX1;
                        spritesToRender[i].index = sprite;
                        collision = true;
                    }
                }

                // We did not collide, add if possible
                if (!collision && (spritesAdded < 10)) {
                    spritesToRender[spritesAdded].x = spriteX1;
                    spritesToRender[spritesAdded].index = sprite;
                    ++spritesAdded;
                }
                 */

                // For the time being, assume no hardware limit and no priority checks
                // and just draw
                // Get tile number from tile map - multiply yTileOffset by 32 as there are 32 tiles within one row
                const uint8_t patternNumber = spriteRam.readExt((sprite << 2) + 2) & (spriteHeight == 16 ? 0xFE : 0xFF);
                const uint8_t options = spriteRam.readExt((sprite << 2) + 3);
                const bool hasPriority = (options & (1 << 7)) == 0;
                const bool yFlip = (options & (1 << 6)) != 0;
                const bool xFlip = (options & (1 << 5)) != 0;
                const uint8_t palette = (options & (1 << 4)) != 0 ? gpuReg[OffOBP1] : gpuReg[OffOBP0];

                // Select correct y-row depending on y-flip
                const uint8_t lineNumber = static_cast<uint8_t>(yFlip ? spriteY1 - gpuReg[OffLY] : spriteY2 - gpuReg[OffLY]);

                // Read the whole line
                // Get 2-bit value - multiply tileLineIndex by two as each line is composed of 2-bytes
                const uint16_t finalAddress = (patternNumber << 4) + (lineNumber << 1);

                // render
                for (int8_t i = 0; i < 8; ++i) {
                    int8_t x = xFlip ? 7 - i : i;
                    uint8_t pixelValue = static_cast<uint8_t>((videoRam.readExt(finalAddress + 1) &
                                                               (1 << (7 - x))) ? 2 : 0);
                    pixelValue |= (videoRam.readExt(finalAddress) >> (7 - x)) & 1;

                    // Get palette colour value
                    const uint8_t color = static_cast<uint8_t>((palette >> (pixelValue << 1)) & 0x03);

                    //
                    int16_t spriteXPixel = spriteX1 + i;
                    if (spriteXPixel >= 0 && spriteXPixel < WIDTH
                        && (color != colors[COLOUR0])
                        && (hasPriority || frameBuffer[gpuReg[OffLY] * WIDTH + spriteXPixel] == colors[COLOUR0])) {
                        frameBuffer[gpuReg[OffLY] * WIDTH + spriteXPixel] = colors[color];
                    }
                }

            }
        }
    }
}

<|endoftext|>