mirror of
https://github.com/RetroAchievements/RAIntegration.git
synced 2026-10-12 02:09:25 +00:00
allow emulator to provide function for reading chunks of memory (#893)
This commit is contained in:
+1
-1
Submodule RAInterface updated: a154fa35f4...054ca2b5dd
@@ -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
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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('\\');
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user