allow emulator to provide function for reading chunks of memory (#893)

This commit is contained in:
Jamiras
2022-05-11 20:18:50 -06:00
committed by GitHub
parent 5c7c350dc4
commit 72ea374d09
12 changed files with 165 additions and 50 deletions
+6
View File
@@ -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();
+4 -2
View File
@@ -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();
+45 -34
View File
@@ -594,6 +594,7 @@ void EmulatorContext::AddMemoryBlock(gsl::index nIndex, size_t nBytes,
pBlock.size = nBytes;
pBlock.read = pReader;
pBlock.write = pWriter;
pBlock.readBlock = nullptr;
m_nTotalMemorySize += nBytes;
@@ -601,6 +602,13 @@ void EmulatorContext::AddMemoryBlock(gsl::index nIndex, size_t nBytes,
}
}
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
@@ -678,46 +686,49 @@ void EmulatorContext::ReadMemory(ra::ByteAddress nAddress, uint8_t pBuffer[], si
}
const size_t nBlockRemaining = pBlock.size - nAddress;
if (!pBlock.read)
{
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().InvalidateAddress(nOriginalAddress);
if (nCount <= nBlockRemaining)
break;
memset(pBuffer, 0, nBlockRemaining);
pBuffer += nBlockRemaining;
nCount -= nBlockRemaining;
nAddress = 0;
continue;
}
size_t nToRead = std::min(nCount, nBlockRemaining);
nCount -= nToRead;
while (nToRead >= 8) // unrolled loop to read 8-byte chunks
if (pBlock.readBlock)
{
*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;
}
const size_t nRead = pBlock.readBlock(nAddress, pBuffer, gsl::narrow_cast<uint32_t>(nToRead));
if (nRead < nToRead)
memset(pBuffer + nRead, 0, nToRead - nRead);
switch (nToRead) // partial Duff's device to read remaining bytes
pBuffer += nToRead;
}
else if (!pBlock.read)
{
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;
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().InvalidateAddress(nOriginalAddress);
memset(pBuffer, 0, nToRead);
pBuffer += nToRead;
}
else
{
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)
+7
View File
@@ -174,6 +174,7 @@ 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>
@@ -181,6 +182,11 @@ public:
/// </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>
@@ -301,6 +307,7 @@ protected:
size_t size;
MemoryReadFunction* read;
MemoryWriteFunction* write;
MemoryReadBlockFunction* readBlock;
};
std::vector<MemoryBlock> m_vMemoryBlocks;
+1 -1
View File
@@ -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();
}
}
+1 -1
View File
@@ -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('\\');
+12 -7
View File
@@ -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)
+2
View File
@@ -83,6 +83,7 @@ public:
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);
@@ -126,6 +127,7 @@ public:
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);
@@ -1079,6 +1079,18 @@ public:
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; }
@@ -1241,6 +1253,56 @@ public:
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;
+19
View File
@@ -488,6 +488,25 @@ public:
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;
+5 -4
View File
@@ -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);
});
}