Skip to content

Commit

Permalink
allow emulator to provide function for reading chunks of memory (#893)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamiras authored May 12, 2022
1 parent 5c7c350 commit 72ea374
Show file tree
Hide file tree
Showing 12 changed files with 164 additions and 49 deletions.
2 changes: 1 addition & 1 deletion RAInterface
6 changes: 6 additions & 0 deletions src/Exports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,12 @@ API void CCONV _RA_InstallMemoryBank(int nBankID, void* pReader, void* pWriter,
static_cast<ra::data::context::EmulatorContext::MemoryWriteFunction*>(pWriter));
}

API void CCONV _RA_InstallMemoryBankBlockReader(int nBankID, void* pReader)
{
ra::services::ServiceLocator::GetMutable<ra::data::context::EmulatorContext>().AddMemoryBlockReader(
nBankID, static_cast<ra::data::context::EmulatorContext::MemoryReadBlockFunction*>(pReader));
}

API void CCONV _RA_ClearMemoryBanks()
{
ra::services::ServiceLocator::GetMutable<ra::data::context::EmulatorContext>().ClearMemoryBlocks();
Expand Down
6 changes: 4 additions & 2 deletions src/Exports.hh
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@ extern "C" {
API int CCONV _RA_OnLoadNewRom(const BYTE* pROM, unsigned int nROMSize);

// On or immediately after a new ROM is loaded, for each memory bank found
// pReader is typedef unsigned char (_RAMByteReadFn)( size_t nOffset );
// pWriter is typedef void (_RAMByteWriteFn)( unsigned int nOffs, unsigned int nVal );
// pReader is typedef unsigned char (_RAMByteReadFn)( unsigned nOffset );
// pBlockReader is typedef unsigned (_RAMBlockReadFn)( unsigned nOffset, unsigned char* pBuffer, unsigned nCount );
// pWriter is typedef void (_RAMByteWriteFn)( unsigned int nOffs, unsigned char nVal );
API void CCONV _RA_InstallMemoryBank(int nBankID, void* pReader, void* pWriter, int nBankSize);
API void CCONV _RA_InstallMemoryBankBlockReader(int nBankID, void* pBlockReader);

// Call before installing any memory banks
API void CCONV _RA_ClearMemoryBanks();
Expand Down
77 changes: 44 additions & 33 deletions src/data/context/EmulatorContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -594,13 +594,21 @@ void EmulatorContext::AddMemoryBlock(gsl::index nIndex, size_t nBytes,
pBlock.size = nBytes;
pBlock.read = pReader;
pBlock.write = pWriter;
pBlock.readBlock = nullptr;

m_nTotalMemorySize += nBytes;

OnTotalMemorySizeChanged();
}
}

void EmulatorContext::AddMemoryBlockReader(gsl::index nIndex,
EmulatorContext::MemoryReadBlockFunction* pReader)
{
if (nIndex < gsl::narrow_cast<gsl::index>(m_vMemoryBlocks.size()))
m_vMemoryBlocks.at(nIndex).readBlock = pReader;
}

void EmulatorContext::OnTotalMemorySizeChanged()
{
// create a copy of the list of pointers in case it's modified by one of the callbacks
Expand Down Expand Up @@ -678,46 +686,49 @@ void EmulatorContext::ReadMemory(ra::ByteAddress nAddress, uint8_t pBuffer[], si
}

const size_t nBlockRemaining = pBlock.size - nAddress;
size_t nToRead = std::min(nCount, nBlockRemaining);
nCount -= nToRead;

if (!pBlock.read)
if (pBlock.readBlock)
{
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().InvalidateAddress(nOriginalAddress);
if (nCount <= nBlockRemaining)
break;
const size_t nRead = pBlock.readBlock(nAddress, pBuffer, gsl::narrow_cast<uint32_t>(nToRead));
if (nRead < nToRead)
memset(pBuffer + nRead, 0, nToRead - nRead);

memset(pBuffer, 0, nBlockRemaining);
pBuffer += nBlockRemaining;
nCount -= nBlockRemaining;
nAddress = 0;
continue;
pBuffer += nToRead;
}

size_t nToRead = std::min(nCount, nBlockRemaining);
nCount -= nToRead;

while (nToRead >= 8) // unrolled loop to read 8-byte chunks
else if (!pBlock.read)
{
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
nToRead -= 8;
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().InvalidateAddress(nOriginalAddress);
memset(pBuffer, 0, nToRead);
pBuffer += nToRead;
}

switch (nToRead) // partial Duff's device to read remaining bytes
else
{
case 7: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 6: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 5: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 4: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 3: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 2: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 1: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
default: break;
while (nToRead >= 8) // unrolled loop to read 8-byte chunks
{
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
*pBuffer++ = pBlock.read(nAddress++);
nToRead -= 8;
}

switch (nToRead) // partial Duff's device to read remaining bytes
{
case 7: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 6: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 5: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 4: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 3: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 2: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
case 1: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
default: break;
}
}

if (nCount == 0)
Expand Down
7 changes: 7 additions & 0 deletions src/data/context/EmulatorContext.hh
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,19 @@ public:
const wchar_t* GetCancelButtonText() const noexcept { return m_sCancelButtonText; }

typedef uint8_t(MemoryReadFunction)(uint32_t nAddress);
typedef uint32_t(MemoryReadBlockFunction)(uint32_t nAddress, uint8_t* pBuffer, uint32_t nBytes);
typedef void (MemoryWriteFunction)(uint32_t nAddress, uint8_t nValue);

/// <summary>
/// Specifies functions to read and write memory in the emulator.
/// </summary>
void AddMemoryBlock(gsl::index nIndex, size_t nBytes, MemoryReadFunction* pReader, MemoryWriteFunction* pWriter);

/// <summary>
/// Specifies functions to read chunks of memory in the emulator.
/// </summary>
void AddMemoryBlockReader(gsl::index nIndex, MemoryReadBlockFunction* pReader);

/// <summary>
/// Clears all registered memory blocks so they can be rebuilt.
/// </summary>
Expand Down Expand Up @@ -301,6 +307,7 @@ protected:
size_t size;
MemoryReadFunction* read;
MemoryWriteFunction* write;
MemoryReadBlockFunction* readBlock;
};

std::vector<MemoryBlock> m_vMemoryBlocks;
Expand Down
2 changes: 1 addition & 1 deletion src/data/context/GameContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ void GameContext::LoadGame(unsigned int nGameId, Mode nMode)
if (pData != nullptr)
{
rapidjson::Document pDocument;
if (LoadDocument(pDocument, *pData) && pDocument.HasMember("RichPresencePatch"))
if (LoadDocument(pDocument, *pData) && pDocument.HasMember("RichPresencePatch") && pDocument["RichPresencePatch"].IsString())
sOldRichPresence = pDocument["RichPresencePatch"].GetString();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/data/models/AssetModelBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ static void WriteEscapedString(ra::services::TextWriter& pWriter, const std::bas
std::basic_string<CharT> sEscaped;
sEscaped.reserve(sText.length() + nToEscape + 2);
sEscaped.push_back('"');
for (CharT c : sText)
for (const CharT c : sText)
{
if (c == '"' || c == '\\')
sEscaped.push_back('\\');
Expand Down
19 changes: 12 additions & 7 deletions src/ui/viewmodels/MemoryViewerViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -872,14 +872,19 @@ void MemoryViewerViewModel::DoFrame()
const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
pEmulatorContext.ReadMemory(nAddress, pMemory, gsl::narrow_cast<size_t>(nVisibleLines) * 16);

for (auto nIndex = 0; nIndex < nVisibleLines * 16; ++nIndex)
constexpr int nStride = 8;
for (int nIndex = 0; nIndex < nVisibleLines * 16; nIndex += nStride)
{
if (m_pMemory[nIndex] != pMemory[nIndex])
{
m_pMemory[nIndex] = pMemory[nIndex];
m_pColor[nIndex] |= STALE_COLOR;
m_nNeedsRedraw |= REDRAW_MEMORY;
}
if (memcmp(&m_pMemory[nIndex], &pMemory[nIndex], nStride) == 0)
continue;

// STALE_COLOR causes the cell to be redrawn even if the color didn't actually change
// use branchless logic for additional performance. the compiler should unroll the loop.
for (int i = 0; i < nStride; i++)
m_pColor[nIndex + i] |= STALE_COLOR * (m_pMemory[nIndex + i] != pMemory[nIndex + i]);

memcpy(&m_pMemory[nIndex], &pMemory[nIndex], nStride);
m_nNeedsRedraw |= REDRAW_MEMORY;
}

if (m_nNeedsRedraw)
Expand Down
2 changes: 2 additions & 0 deletions tests/RA_Interface_Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ TEST_CLASS(RA_Interface_Tests)
Assert::IsNotNull((const void*)_RA_SetConsoleID);
Assert::IsNotNull((const void*)_RA_ClearMemoryBanks);
Assert::IsNotNull((const void*)_RA_InstallMemoryBank);
Assert::IsNotNull((const void*)_RA_InstallMemoryBankBlockReader);
Assert::IsNotNull((const void*)_RA_Shutdown);
Assert::IsNotNull((const void*)_RA_IsOverlayFullyVisible);
Assert::IsNotNull((const void*)_RA_SetPaused);
Expand Down Expand Up @@ -126,6 +127,7 @@ TEST_CLASS(RA_Interface_Tests)
Assert::IsNull((const void*)_RA_SetConsoleID);
Assert::IsNull((const void*)_RA_ClearMemoryBanks);
Assert::IsNull((const void*)_RA_InstallMemoryBank);
Assert::IsNull((const void*)_RA_InstallMemoryBankBlockReader);
Assert::IsNull((const void*)_RA_Shutdown);
Assert::IsNull((const void*)_RA_IsOverlayFullyVisible);
Assert::IsNull((const void*)_RA_SetPaused);
Expand Down
62 changes: 62 additions & 0 deletions tests/data/context/EmulatorContext_Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,18 @@ TEST_CLASS(EmulatorContext_Tests)
static uint8_t ReadMemory2(uint32_t nAddress) noexcept { return memory.at(gsl::narrow_cast<size_t>(nAddress) + 20); }
static uint8_t ReadMemory3(uint32_t nAddress) noexcept { return memory.at(gsl::narrow_cast<size_t>(nAddress) + 30); }

static uint32_t ReadMemoryBlock0(uint32_t nAddress, uint8_t* pBuffer, uint32_t nBytes) noexcept
{
memcpy(pBuffer, &memory.at(nAddress), nBytes);
return nBytes;
}

static uint32_t ReadMemoryBlock1(uint32_t nAddress, uint8_t* pBuffer, uint32_t nBytes) noexcept
{
memcpy(pBuffer, &memory.at(gsl::narrow_cast<size_t>(nAddress)) + 10, nBytes);
return nBytes;
}

static void WriteMemory0(uint32_t nAddress, uint8_t nValue) noexcept { memory.at(nAddress) = nValue; }
static void WriteMemory1(uint32_t nAddress, uint8_t nValue) noexcept { memory.at(gsl::narrow_cast<size_t>(nAddress) + 10) = nValue; }
static void WriteMemory2(uint32_t nAddress, uint8_t nValue) noexcept { memory.at(gsl::narrow_cast<size_t>(nAddress) + 20) = nValue; }
Expand Down Expand Up @@ -1241,6 +1253,56 @@ TEST_CLASS(EmulatorContext_Tests)
Assert::AreEqual(0x57, static_cast<int>(emulator.ReadMemory(4U, MemSize::EightBit)));
}

TEST_METHOD(TestReadMemoryBlock)
{
for (size_t i = 0; i < memory.size(); i++)
memory.at(i) = gsl::narrow_cast<uint8_t>(i);

memory.at(4) = 0xA8;
memory.at(5) = 0x00;
memory.at(6) = 0x37;
memory.at(7) = 0x2E;

memory.at(14) = 0x57;

EmulatorContextHarness emulator;
emulator.AddMemoryBlock(0, 20, &ReadMemory1, &WriteMemory0); // purposefully use ReadMemory1 to detect using byte reader for non-byte reads
emulator.AddMemoryBlockReader(0, &ReadMemoryBlock0);

// ReadMemory calls ReadMemoryByte for small sizes - should call ReadMemory1 ($4 => $14)
Assert::AreEqual(1, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_0)));
Assert::AreEqual(1, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_1)));
Assert::AreEqual(1, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_2)));
Assert::AreEqual(0, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_3)));
Assert::AreEqual(1, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_4)));
Assert::AreEqual(0, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_5)));
Assert::AreEqual(1, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_6)));
Assert::AreEqual(0, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_7)));
Assert::AreEqual(5, static_cast<int>(emulator.ReadMemory(4U, MemSize::BitCount)));
Assert::AreEqual(7, static_cast<int>(emulator.ReadMemory(4U, MemSize::Nibble_Lower)));
Assert::AreEqual(5, static_cast<int>(emulator.ReadMemory(4U, MemSize::Nibble_Upper)));
Assert::AreEqual(0x57, static_cast<int>(emulator.ReadMemory(4U, MemSize::EightBit)));

// sizes larger than 8 bits should use the block reader ($4 => $4)
Assert::AreEqual(0xA8, static_cast<int>(emulator.ReadMemory(4U, MemSize::SixteenBit)));
Assert::AreEqual(0x2E37, static_cast<int>(emulator.ReadMemory(6U, MemSize::SixteenBit)));
Assert::AreEqual(0x2E3700, static_cast<int>(emulator.ReadMemory(5U, MemSize::TwentyFourBit)));
Assert::AreEqual(0x2E3700A8, static_cast<int>(emulator.ReadMemory(4U, MemSize::ThirtyTwoBit)));
Assert::AreEqual(0x372E, static_cast<int>(emulator.ReadMemory(6U, MemSize::SixteenBitBigEndian)));
Assert::AreEqual(0x00372E, static_cast<int>(emulator.ReadMemory(5U, MemSize::TwentyFourBitBigEndian)));
Assert::AreEqual(0xA800372EU, emulator.ReadMemory(4U, MemSize::ThirtyTwoBitBigEndian));

// test the block reader directly
uint8_t buffer[16];
emulator.ReadMemory(0U, buffer, sizeof(buffer));
for (size_t i = 0; i < sizeof(buffer); i++)
Assert::AreEqual(gsl::at(buffer, i), memory.at(i));

emulator.ReadMemory(4U, buffer, 1);
Assert::AreEqual(gsl::at(buffer, 0), memory.at(4));
Assert::AreEqual(gsl::at(buffer, 1), memory.at(1));
}

TEST_METHOD(TestReadMemoryByteInvalidAddressDisablesAchievement)
{
memory.at(4) = 0xA8;
Expand Down
19 changes: 19 additions & 0 deletions tests/data/context/GameContext_Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,25 @@ TEST_CLASS(GameContext_Tests)
Assert::AreEqual(ra::data::models::AssetChanges::Unpublished, pRichPresence->GetChanges());
}

TEST_METHOD(TestLoadGameRichPresenceNotOnServer)
{
GameContextHarness game;
game.mockStorage.MockStoredData(ra::services::StorageItemType::GameData, L"1", "{\"RichPresencePatch\": null}");
game.mockServer.HandleRequest<ra::api::FetchGameData>([](const ra::api::FetchGameData::Request&, ra::api::FetchGameData::Response&)
{
return true;
});

game.LoadGame(1U);

Assert::IsFalse(game.HasRichPresence());
Assert::IsFalse(game.mockStorage.HasStoredData(ra::services::StorageItemType::RichPresence, L"1"));
Assert::IsFalse(game.IsRichPresenceFromFile());

const auto* pRichPresence = game.Assets().FindRichPresence();
Assert::IsNull(pRichPresence);
}

TEST_METHOD(TestLoadGameNoRichPresence)
{
GameContextHarness game;
Expand Down
9 changes: 5 additions & 4 deletions tests/mocks/MockServer.hh
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ public:
std::string(TApi::Name()),
[fHandler = std::move(fHandler)](const void* restrict pRequest, void* restrict pResponse)
{
const gsl::not_null<const typename TApi::Request* const> pTRequest{
gsl::make_not_null(static_cast<const typename TApi::Request*>(pRequest))};
const gsl::not_null<typename TApi::Response* const> pTResponse{
gsl::make_not_null(static_cast<typename TApi::Response*>(pResponse))};
const auto* pTRequest = static_cast<const typename TApi::Request*>(pRequest);
auto* pTResponse = static_cast<typename TApi::Response*>(pResponse);
if (!pTRequest || !pTResponse)
return false;

return fHandler(*pTRequest, *pTResponse);
});
}
Expand Down

0 comments on commit 72ea374

Please sign in to comment.