Skip to content

Commit 72ea374

Browse files
authored
allow emulator to provide function for reading chunks of memory (#893)
1 parent 5c7c350 commit 72ea374

File tree

12 files changed

+164
-49
lines changed

12 files changed

+164
-49
lines changed

RAInterface

src/Exports.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,12 @@ API void CCONV _RA_InstallMemoryBank(int nBankID, void* pReader, void* pWriter,
345345
static_cast<ra::data::context::EmulatorContext::MemoryWriteFunction*>(pWriter));
346346
}
347347

348+
API void CCONV _RA_InstallMemoryBankBlockReader(int nBankID, void* pReader)
349+
{
350+
ra::services::ServiceLocator::GetMutable<ra::data::context::EmulatorContext>().AddMemoryBlockReader(
351+
nBankID, static_cast<ra::data::context::EmulatorContext::MemoryReadBlockFunction*>(pReader));
352+
}
353+
348354
API void CCONV _RA_ClearMemoryBanks()
349355
{
350356
ra::services::ServiceLocator::GetMutable<ra::data::context::EmulatorContext>().ClearMemoryBlocks();

src/Exports.hh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,11 @@ extern "C" {
6565
API int CCONV _RA_OnLoadNewRom(const BYTE* pROM, unsigned int nROMSize);
6666

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

7274
// Call before installing any memory banks
7375
API void CCONV _RA_ClearMemoryBanks();

src/data/context/EmulatorContext.cpp

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -594,13 +594,21 @@ void EmulatorContext::AddMemoryBlock(gsl::index nIndex, size_t nBytes,
594594
pBlock.size = nBytes;
595595
pBlock.read = pReader;
596596
pBlock.write = pWriter;
597+
pBlock.readBlock = nullptr;
597598

598599
m_nTotalMemorySize += nBytes;
599600

600601
OnTotalMemorySizeChanged();
601602
}
602603
}
603604

605+
void EmulatorContext::AddMemoryBlockReader(gsl::index nIndex,
606+
EmulatorContext::MemoryReadBlockFunction* pReader)
607+
{
608+
if (nIndex < gsl::narrow_cast<gsl::index>(m_vMemoryBlocks.size()))
609+
m_vMemoryBlocks.at(nIndex).readBlock = pReader;
610+
}
611+
604612
void EmulatorContext::OnTotalMemorySizeChanged()
605613
{
606614
// 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
678686
}
679687

680688
const size_t nBlockRemaining = pBlock.size - nAddress;
689+
size_t nToRead = std::min(nCount, nBlockRemaining);
690+
nCount -= nToRead;
681691

682-
if (!pBlock.read)
692+
if (pBlock.readBlock)
683693
{
684-
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().InvalidateAddress(nOriginalAddress);
685-
if (nCount <= nBlockRemaining)
686-
break;
694+
const size_t nRead = pBlock.readBlock(nAddress, pBuffer, gsl::narrow_cast<uint32_t>(nToRead));
695+
if (nRead < nToRead)
696+
memset(pBuffer + nRead, 0, nToRead - nRead);
687697

688-
memset(pBuffer, 0, nBlockRemaining);
689-
pBuffer += nBlockRemaining;
690-
nCount -= nBlockRemaining;
691-
nAddress = 0;
692-
continue;
698+
pBuffer += nToRead;
693699
}
694-
695-
size_t nToRead = std::min(nCount, nBlockRemaining);
696-
nCount -= nToRead;
697-
698-
while (nToRead >= 8) // unrolled loop to read 8-byte chunks
700+
else if (!pBlock.read)
699701
{
700-
*pBuffer++ = pBlock.read(nAddress++);
701-
*pBuffer++ = pBlock.read(nAddress++);
702-
*pBuffer++ = pBlock.read(nAddress++);
703-
*pBuffer++ = pBlock.read(nAddress++);
704-
*pBuffer++ = pBlock.read(nAddress++);
705-
*pBuffer++ = pBlock.read(nAddress++);
706-
*pBuffer++ = pBlock.read(nAddress++);
707-
*pBuffer++ = pBlock.read(nAddress++);
708-
nToRead -= 8;
702+
ra::services::ServiceLocator::GetMutable<ra::services::AchievementRuntime>().InvalidateAddress(nOriginalAddress);
703+
memset(pBuffer, 0, nToRead);
704+
pBuffer += nToRead;
709705
}
710-
711-
switch (nToRead) // partial Duff's device to read remaining bytes
706+
else
712707
{
713-
case 7: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
714-
case 6: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
715-
case 5: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
716-
case 4: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
717-
case 3: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
718-
case 2: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
719-
case 1: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
720-
default: break;
708+
while (nToRead >= 8) // unrolled loop to read 8-byte chunks
709+
{
710+
*pBuffer++ = pBlock.read(nAddress++);
711+
*pBuffer++ = pBlock.read(nAddress++);
712+
*pBuffer++ = pBlock.read(nAddress++);
713+
*pBuffer++ = pBlock.read(nAddress++);
714+
*pBuffer++ = pBlock.read(nAddress++);
715+
*pBuffer++ = pBlock.read(nAddress++);
716+
*pBuffer++ = pBlock.read(nAddress++);
717+
*pBuffer++ = pBlock.read(nAddress++);
718+
nToRead -= 8;
719+
}
720+
721+
switch (nToRead) // partial Duff's device to read remaining bytes
722+
{
723+
case 7: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
724+
case 6: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
725+
case 5: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
726+
case 4: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
727+
case 3: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
728+
case 2: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
729+
case 1: *pBuffer++ = pBlock.read(nAddress++); _FALLTHROUGH;
730+
default: break;
731+
}
721732
}
722733

723734
if (nCount == 0)

src/data/context/EmulatorContext.hh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,19 @@ public:
174174
const wchar_t* GetCancelButtonText() const noexcept { return m_sCancelButtonText; }
175175

176176
typedef uint8_t(MemoryReadFunction)(uint32_t nAddress);
177+
typedef uint32_t(MemoryReadBlockFunction)(uint32_t nAddress, uint8_t* pBuffer, uint32_t nBytes);
177178
typedef void (MemoryWriteFunction)(uint32_t nAddress, uint8_t nValue);
178179

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

185+
/// <summary>
186+
/// Specifies functions to read chunks of memory in the emulator.
187+
/// </summary>
188+
void AddMemoryBlockReader(gsl::index nIndex, MemoryReadBlockFunction* pReader);
189+
184190
/// <summary>
185191
/// Clears all registered memory blocks so they can be rebuilt.
186192
/// </summary>
@@ -301,6 +307,7 @@ protected:
301307
size_t size;
302308
MemoryReadFunction* read;
303309
MemoryWriteFunction* write;
310+
MemoryReadBlockFunction* readBlock;
304311
};
305312

306313
std::vector<MemoryBlock> m_vMemoryBlocks;

src/data/context/GameContext.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ void GameContext::LoadGame(unsigned int nGameId, Mode nMode)
9696
if (pData != nullptr)
9797
{
9898
rapidjson::Document pDocument;
99-
if (LoadDocument(pDocument, *pData) && pDocument.HasMember("RichPresencePatch"))
99+
if (LoadDocument(pDocument, *pData) && pDocument.HasMember("RichPresencePatch") && pDocument["RichPresencePatch"].IsString())
100100
sOldRichPresence = pDocument["RichPresencePatch"].GetString();
101101
}
102102
}

src/data/models/AssetModelBase.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ static void WriteEscapedString(ra::services::TextWriter& pWriter, const std::bas
4242
std::basic_string<CharT> sEscaped;
4343
sEscaped.reserve(sText.length() + nToEscape + 2);
4444
sEscaped.push_back('"');
45-
for (CharT c : sText)
45+
for (const CharT c : sText)
4646
{
4747
if (c == '"' || c == '\\')
4848
sEscaped.push_back('\\');

src/ui/viewmodels/MemoryViewerViewModel.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -872,14 +872,19 @@ void MemoryViewerViewModel::DoFrame()
872872
const auto& pEmulatorContext = ra::services::ServiceLocator::Get<ra::data::context::EmulatorContext>();
873873
pEmulatorContext.ReadMemory(nAddress, pMemory, gsl::narrow_cast<size_t>(nVisibleLines) * 16);
874874

875-
for (auto nIndex = 0; nIndex < nVisibleLines * 16; ++nIndex)
875+
constexpr int nStride = 8;
876+
for (int nIndex = 0; nIndex < nVisibleLines * 16; nIndex += nStride)
876877
{
877-
if (m_pMemory[nIndex] != pMemory[nIndex])
878-
{
879-
m_pMemory[nIndex] = pMemory[nIndex];
880-
m_pColor[nIndex] |= STALE_COLOR;
881-
m_nNeedsRedraw |= REDRAW_MEMORY;
882-
}
878+
if (memcmp(&m_pMemory[nIndex], &pMemory[nIndex], nStride) == 0)
879+
continue;
880+
881+
// STALE_COLOR causes the cell to be redrawn even if the color didn't actually change
882+
// use branchless logic for additional performance. the compiler should unroll the loop.
883+
for (int i = 0; i < nStride; i++)
884+
m_pColor[nIndex + i] |= STALE_COLOR * (m_pMemory[nIndex + i] != pMemory[nIndex + i]);
885+
886+
memcpy(&m_pMemory[nIndex], &pMemory[nIndex], nStride);
887+
m_nNeedsRedraw |= REDRAW_MEMORY;
883888
}
884889

885890
if (m_nNeedsRedraw)

tests/RA_Interface_Tests.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ TEST_CLASS(RA_Interface_Tests)
8383
Assert::IsNotNull((const void*)_RA_SetConsoleID);
8484
Assert::IsNotNull((const void*)_RA_ClearMemoryBanks);
8585
Assert::IsNotNull((const void*)_RA_InstallMemoryBank);
86+
Assert::IsNotNull((const void*)_RA_InstallMemoryBankBlockReader);
8687
Assert::IsNotNull((const void*)_RA_Shutdown);
8788
Assert::IsNotNull((const void*)_RA_IsOverlayFullyVisible);
8889
Assert::IsNotNull((const void*)_RA_SetPaused);
@@ -126,6 +127,7 @@ TEST_CLASS(RA_Interface_Tests)
126127
Assert::IsNull((const void*)_RA_SetConsoleID);
127128
Assert::IsNull((const void*)_RA_ClearMemoryBanks);
128129
Assert::IsNull((const void*)_RA_InstallMemoryBank);
130+
Assert::IsNull((const void*)_RA_InstallMemoryBankBlockReader);
129131
Assert::IsNull((const void*)_RA_Shutdown);
130132
Assert::IsNull((const void*)_RA_IsOverlayFullyVisible);
131133
Assert::IsNull((const void*)_RA_SetPaused);

tests/data/context/EmulatorContext_Tests.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,18 @@ TEST_CLASS(EmulatorContext_Tests)
10791079
static uint8_t ReadMemory2(uint32_t nAddress) noexcept { return memory.at(gsl::narrow_cast<size_t>(nAddress) + 20); }
10801080
static uint8_t ReadMemory3(uint32_t nAddress) noexcept { return memory.at(gsl::narrow_cast<size_t>(nAddress) + 30); }
10811081

1082+
static uint32_t ReadMemoryBlock0(uint32_t nAddress, uint8_t* pBuffer, uint32_t nBytes) noexcept
1083+
{
1084+
memcpy(pBuffer, &memory.at(nAddress), nBytes);
1085+
return nBytes;
1086+
}
1087+
1088+
static uint32_t ReadMemoryBlock1(uint32_t nAddress, uint8_t* pBuffer, uint32_t nBytes) noexcept
1089+
{
1090+
memcpy(pBuffer, &memory.at(gsl::narrow_cast<size_t>(nAddress)) + 10, nBytes);
1091+
return nBytes;
1092+
}
1093+
10821094
static void WriteMemory0(uint32_t nAddress, uint8_t nValue) noexcept { memory.at(nAddress) = nValue; }
10831095
static void WriteMemory1(uint32_t nAddress, uint8_t nValue) noexcept { memory.at(gsl::narrow_cast<size_t>(nAddress) + 10) = nValue; }
10841096
static void WriteMemory2(uint32_t nAddress, uint8_t nValue) noexcept { memory.at(gsl::narrow_cast<size_t>(nAddress) + 20) = nValue; }
@@ -1241,6 +1253,56 @@ TEST_CLASS(EmulatorContext_Tests)
12411253
Assert::AreEqual(0x57, static_cast<int>(emulator.ReadMemory(4U, MemSize::EightBit)));
12421254
}
12431255

1256+
TEST_METHOD(TestReadMemoryBlock)
1257+
{
1258+
for (size_t i = 0; i < memory.size(); i++)
1259+
memory.at(i) = gsl::narrow_cast<uint8_t>(i);
1260+
1261+
memory.at(4) = 0xA8;
1262+
memory.at(5) = 0x00;
1263+
memory.at(6) = 0x37;
1264+
memory.at(7) = 0x2E;
1265+
1266+
memory.at(14) = 0x57;
1267+
1268+
EmulatorContextHarness emulator;
1269+
emulator.AddMemoryBlock(0, 20, &ReadMemory1, &WriteMemory0); // purposefully use ReadMemory1 to detect using byte reader for non-byte reads
1270+
emulator.AddMemoryBlockReader(0, &ReadMemoryBlock0);
1271+
1272+
// ReadMemory calls ReadMemoryByte for small sizes - should call ReadMemory1 ($4 => $14)
1273+
Assert::AreEqual(1, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_0)));
1274+
Assert::AreEqual(1, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_1)));
1275+
Assert::AreEqual(1, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_2)));
1276+
Assert::AreEqual(0, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_3)));
1277+
Assert::AreEqual(1, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_4)));
1278+
Assert::AreEqual(0, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_5)));
1279+
Assert::AreEqual(1, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_6)));
1280+
Assert::AreEqual(0, static_cast<int>(emulator.ReadMemory(4U, MemSize::Bit_7)));
1281+
Assert::AreEqual(5, static_cast<int>(emulator.ReadMemory(4U, MemSize::BitCount)));
1282+
Assert::AreEqual(7, static_cast<int>(emulator.ReadMemory(4U, MemSize::Nibble_Lower)));
1283+
Assert::AreEqual(5, static_cast<int>(emulator.ReadMemory(4U, MemSize::Nibble_Upper)));
1284+
Assert::AreEqual(0x57, static_cast<int>(emulator.ReadMemory(4U, MemSize::EightBit)));
1285+
1286+
// sizes larger than 8 bits should use the block reader ($4 => $4)
1287+
Assert::AreEqual(0xA8, static_cast<int>(emulator.ReadMemory(4U, MemSize::SixteenBit)));
1288+
Assert::AreEqual(0x2E37, static_cast<int>(emulator.ReadMemory(6U, MemSize::SixteenBit)));
1289+
Assert::AreEqual(0x2E3700, static_cast<int>(emulator.ReadMemory(5U, MemSize::TwentyFourBit)));
1290+
Assert::AreEqual(0x2E3700A8, static_cast<int>(emulator.ReadMemory(4U, MemSize::ThirtyTwoBit)));
1291+
Assert::AreEqual(0x372E, static_cast<int>(emulator.ReadMemory(6U, MemSize::SixteenBitBigEndian)));
1292+
Assert::AreEqual(0x00372E, static_cast<int>(emulator.ReadMemory(5U, MemSize::TwentyFourBitBigEndian)));
1293+
Assert::AreEqual(0xA800372EU, emulator.ReadMemory(4U, MemSize::ThirtyTwoBitBigEndian));
1294+
1295+
// test the block reader directly
1296+
uint8_t buffer[16];
1297+
emulator.ReadMemory(0U, buffer, sizeof(buffer));
1298+
for (size_t i = 0; i < sizeof(buffer); i++)
1299+
Assert::AreEqual(gsl::at(buffer, i), memory.at(i));
1300+
1301+
emulator.ReadMemory(4U, buffer, 1);
1302+
Assert::AreEqual(gsl::at(buffer, 0), memory.at(4));
1303+
Assert::AreEqual(gsl::at(buffer, 1), memory.at(1));
1304+
}
1305+
12441306
TEST_METHOD(TestReadMemoryByteInvalidAddressDisablesAchievement)
12451307
{
12461308
memory.at(4) = 0xA8;

tests/data/context/GameContext_Tests.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,25 @@ TEST_CLASS(GameContext_Tests)
488488
Assert::AreEqual(ra::data::models::AssetChanges::Unpublished, pRichPresence->GetChanges());
489489
}
490490

491+
TEST_METHOD(TestLoadGameRichPresenceNotOnServer)
492+
{
493+
GameContextHarness game;
494+
game.mockStorage.MockStoredData(ra::services::StorageItemType::GameData, L"1", "{\"RichPresencePatch\": null}");
495+
game.mockServer.HandleRequest<ra::api::FetchGameData>([](const ra::api::FetchGameData::Request&, ra::api::FetchGameData::Response&)
496+
{
497+
return true;
498+
});
499+
500+
game.LoadGame(1U);
501+
502+
Assert::IsFalse(game.HasRichPresence());
503+
Assert::IsFalse(game.mockStorage.HasStoredData(ra::services::StorageItemType::RichPresence, L"1"));
504+
Assert::IsFalse(game.IsRichPresenceFromFile());
505+
506+
const auto* pRichPresence = game.Assets().FindRichPresence();
507+
Assert::IsNull(pRichPresence);
508+
}
509+
491510
TEST_METHOD(TestLoadGameNoRichPresence)
492511
{
493512
GameContextHarness game;

tests/mocks/MockServer.hh

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ public:
3434
std::string(TApi::Name()),
3535
[fHandler = std::move(fHandler)](const void* restrict pRequest, void* restrict pResponse)
3636
{
37-
const gsl::not_null<const typename TApi::Request* const> pTRequest{
38-
gsl::make_not_null(static_cast<const typename TApi::Request*>(pRequest))};
39-
const gsl::not_null<typename TApi::Response* const> pTResponse{
40-
gsl::make_not_null(static_cast<typename TApi::Response*>(pResponse))};
37+
const auto* pTRequest = static_cast<const typename TApi::Request*>(pRequest);
38+
auto* pTResponse = static_cast<typename TApi::Response*>(pResponse);
39+
if (!pTRequest || !pTResponse)
40+
return false;
41+
4142
return fHandler(*pTRequest, *pTResponse);
4243
});
4344
}

0 commit comments

Comments
 (0)