Skip to content

Commit

Permalink
validate memory reads against code note sizes (#1137)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamiras authored Jan 29, 2025
1 parent c120339 commit 8e8c050
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 43 deletions.
2 changes: 1 addition & 1 deletion rcheevos
62 changes: 62 additions & 0 deletions src/data/Types.hh
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,68 @@ constexpr unsigned int MemSizeBytes(MemSize nSize)

std::wstring MemSizeFormat(unsigned nValue, MemSize nSize, MemFormat nFormat);

constexpr const wchar_t* MemSizeString(MemSize nSize)
{
switch (nSize)
{
case MemSize::Bit_0:
return L"Bit0";
case MemSize::Bit_1:
return L"Bit1";
case MemSize::Bit_2:
return L"Bit2";
case MemSize::Bit_3:
return L"Bit3";
case MemSize::Bit_4:
return L"Bit4";
case MemSize::Bit_5:
return L"Bit5";
case MemSize::Bit_6:
return L"Bit6";
case MemSize::Bit_7:
return L"Bit7";
case MemSize::Nibble_Lower:
return L"Lower4";
case MemSize::Nibble_Upper:
return L"Upper4";
case MemSize::EightBit:
return L"8-bit";
case MemSize::SixteenBit:
return L"16-bit";
case MemSize::TwentyFourBit:
return L"24-bit";
case MemSize::ThirtyTwoBit:
return L"32-bit";
case MemSize::BitCount:
return L"BitCount";
case MemSize::SixteenBitBigEndian:
return L"16-bit BE";
case MemSize::TwentyFourBitBigEndian:
return L"24-bit BE";
case MemSize::ThirtyTwoBitBigEndian:
return L"32-bit BE";
case MemSize::Float:
return L"Float";
case MemSize::MBF32:
return L"MBF32";
case MemSize::MBF32LE:
return L"MBF32 LE";
case MemSize::FloatBigEndian:
return L"Float BE";
case MemSize::Double32:
return L"Double32";
case MemSize::Double32BigEndian:
return L"Double32 BE";
case MemSize::Text:
return L"ASCII";
case MemSize::Array:
return L"Array";
default:
case MemSize::Unknown:
return L"Unknown";
}
}

unsigned FloatToU32(float fValue, MemSize nFloatType) noexcept;
float U32ToFloat(unsigned nValue, MemSize nFloatType) noexcept;

Expand Down
2 changes: 1 addition & 1 deletion src/data/models/CodeNotesModel.hh
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public:
/// Returns the number of bytes associated to the code note at the specified address.
/// </summary>
/// <param name="nAddress">Address to query.</param>
/// <returns>Number of bytes associated to the code note, or 0 if no note exists at the address.</returns>
/// <returns>MemSize associated to the code note, or Unknown if no note exists at the address.</returns>
/// <remarks>Only works for the first byte of a multi-byte address.</remarks>
MemSize GetCodeNoteMemSize(ra::ByteAddress nAddress) const
{
Expand Down
130 changes: 130 additions & 0 deletions src/data/models/TriggerValidation.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include "TriggerValidation.hh"

#include "RA_StringUtils.h"
#include "RA_Defs.h"

#include "data/context/ConsoleContext.hh"
#include "data/context/GameContext.hh"
#include "data/context/EmulatorContext.hh"

#include "services/ServiceLocator.hh"
Expand Down Expand Up @@ -89,6 +91,131 @@ static bool ValidateLeaderboardTrigger(const rc_trigger_t* pTrigger, std::wstrin
return true;
}

static bool ValidateCodeNotesOperand(const rc_operand_t& pOperand, const ra::data::models::CodeNotesModel& pNotes,
std::wstring& sError)
{
const auto nMemRefSize = TriggerValidation::MapRcheevosMemSize(pOperand.size);

const auto nAddress = pOperand.value.memref->address;
auto nNoteSize = pNotes.GetCodeNoteMemSize(nAddress);
if (nMemRefSize == nNoteSize)
return true;

const auto nStartAddress = pNotes.FindCodeNoteStart(nAddress);
if (nStartAddress != nAddress)
{
if (nStartAddress == 0xFFFFFFFF)
{
sError = ra::StringPrintf(L"No code note for address %s", ra::ByteAddressToString(nAddress).substr(2));
return false;
}

nNoteSize = pNotes.GetCodeNoteMemSize(nStartAddress);
if (nNoteSize == MemSize::Array)
{
// ignore addresses in the middle of an array
return true;
}
}

if (nNoteSize == MemSize::Unknown)
{
// note exists, but did not specify a size. assume 8-bit
if (nMemRefSize == MemSize::EightBit)
return true;

nNoteSize = MemSize::EightBit;
}

// ignore bit reads inside a known address
if (nMemRefSize == MemSize::BitCount || MemSizeBits(nMemRefSize) < 8)
return true;

sError = ra::StringPrintf(L"%s read of address %s differs from code note size %s",
MemSizeString(nMemRefSize), ra::ByteAddressToString(nAddress).substr(2), MemSizeString(nNoteSize));

if (nStartAddress != nAddress)
{
sError.append(L" at ");
sError.append(ra::Widen(ra::ByteAddressToString(nStartAddress).substr(2)));
}

return false;
}

static bool ValidateCodeNotesCondSet(const rc_condset_t* pCondSet, const ra::data::models::CodeNotesModel& pNotes,
std::wstring& sError)
{
if (!pCondSet)
return true;

bool bIsAddAddressChain = false;
size_t nIndex = 0;
const auto* pCondition = pCondSet->conditions;
for (; pCondition; pCondition = pCondition->next)
{
++nIndex;
if (pCondition->type == RC_CONDITION_ADD_ADDRESS)
{
bIsAddAddressChain = true;
continue;
}

if (bIsAddAddressChain)
{
bIsAddAddressChain = false;
continue;
}

if (rc_operand_is_memref(&pCondition->operand1) &&
!ValidateCodeNotesOperand(pCondition->operand1, pNotes, sError))
{
sError = ra::StringPrintf(L"Condition %u: %s", nIndex, sError);
return false;
}

if (rc_operand_is_memref(&pCondition->operand2) &&
!ValidateCodeNotesOperand(pCondition->operand2, pNotes, sError))
{
sError = ra::StringPrintf(L"Condition %u: %s", nIndex, sError);
return false;
}
}

return true;
}

static bool ValidateCodeNotes(const rc_trigger_t* pTrigger, std::wstring& sError)
{
if (!ra::services::ServiceLocator::Exists<ra::data::context::GameContext>())
return true;

const auto* pNotes = ra::services::ServiceLocator::Get<ra::data::context::GameContext>().Assets().FindCodeNotes();
if (!pNotes)
return true;

if (!ValidateCodeNotesCondSet(pTrigger->requirement, *pNotes, sError))
{
if (pTrigger->alternative)
sError = L"Core " + sError;
return false;
}

size_t nIndex = 0;
const auto* pCondSet = pTrigger->alternative;
for (; pCondSet; pCondSet = pCondSet->next)
{
nIndex++;
if (!ValidateCodeNotesCondSet(pCondSet, *pNotes, sError))
{
sError = ra::StringPrintf(L"Alt%u %s", nIndex, sError);
return false;
}
}

return true;
}

bool TriggerValidation::Validate(const std::string& sTrigger, std::wstring& sError, AssetType nType)
{
const auto nSize = rc_trigger_size(sTrigger.c_str());
Expand Down Expand Up @@ -138,6 +265,9 @@ bool TriggerValidation::Validate(const std::string& sTrigger, std::wstring& sErr
return false;
}

if (!ValidateCodeNotes(pTrigger, sError))
return false;

sError.clear();
return true;
}
Expand Down
33 changes: 17 additions & 16 deletions src/ui/viewmodels/MemoryBookmarksViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "RA_Json.h"
#include "RA_StringUtils.h"

#include "data\Types.hh"
#include "data\context\EmulatorContext.hh"
#include "data\models\TriggerValidation.hh"

Expand Down Expand Up @@ -53,22 +54,22 @@ MemoryBookmarksViewModel::MemoryBookmarksViewModel() noexcept
SetWindowTitle(L"Memory Bookmarks");

m_vSizes.Add(ra::etoi(MemSize::EightBit), L" 8-bit"); // leading space for sort order
m_vSizes.Add(ra::etoi(MemSize::SixteenBit), L"16-bit");
m_vSizes.Add(ra::etoi(MemSize::TwentyFourBit), L"24-bit");
m_vSizes.Add(ra::etoi(MemSize::ThirtyTwoBit), L"32-bit");
m_vSizes.Add(ra::etoi(MemSize::SixteenBitBigEndian), L"16-bit BE");
m_vSizes.Add(ra::etoi(MemSize::TwentyFourBitBigEndian), L"24-bit BE");
m_vSizes.Add(ra::etoi(MemSize::ThirtyTwoBitBigEndian), L"32-bit BE");
m_vSizes.Add(ra::etoi(MemSize::BitCount), L"BitCount");
m_vSizes.Add(ra::etoi(MemSize::Nibble_Lower), L"Lower4");
m_vSizes.Add(ra::etoi(MemSize::Nibble_Upper), L"Upper4");
m_vSizes.Add(ra::etoi(MemSize::Float), L"Float");
m_vSizes.Add(ra::etoi(MemSize::FloatBigEndian), L"Float BE");
m_vSizes.Add(ra::etoi(MemSize::Double32), L"Double32");
m_vSizes.Add(ra::etoi(MemSize::Double32BigEndian), L"Double32 BE");
m_vSizes.Add(ra::etoi(MemSize::MBF32), L"MBF32");
m_vSizes.Add(ra::etoi(MemSize::MBF32LE), L"MBF32 LE");
m_vSizes.Add(ra::etoi(MemSize::Text), L"ASCII");
m_vSizes.Add(ra::etoi(MemSize::SixteenBit), ra::data::MemSizeString(MemSize::SixteenBit));
m_vSizes.Add(ra::etoi(MemSize::TwentyFourBit), ra::data::MemSizeString(MemSize::TwentyFourBit));
m_vSizes.Add(ra::etoi(MemSize::ThirtyTwoBit), ra::data::MemSizeString(MemSize::ThirtyTwoBit));
m_vSizes.Add(ra::etoi(MemSize::SixteenBitBigEndian), ra::data::MemSizeString(MemSize::SixteenBitBigEndian));
m_vSizes.Add(ra::etoi(MemSize::TwentyFourBitBigEndian), ra::data::MemSizeString(MemSize::TwentyFourBitBigEndian));
m_vSizes.Add(ra::etoi(MemSize::ThirtyTwoBitBigEndian), ra::data::MemSizeString(MemSize::ThirtyTwoBitBigEndian));
m_vSizes.Add(ra::etoi(MemSize::BitCount), ra::data::MemSizeString(MemSize::BitCount));
m_vSizes.Add(ra::etoi(MemSize::Nibble_Lower), ra::data::MemSizeString(MemSize::Nibble_Lower));
m_vSizes.Add(ra::etoi(MemSize::Nibble_Upper), ra::data::MemSizeString(MemSize::Nibble_Upper));
m_vSizes.Add(ra::etoi(MemSize::Float), ra::data::MemSizeString(MemSize::Float));
m_vSizes.Add(ra::etoi(MemSize::FloatBigEndian), ra::data::MemSizeString(MemSize::FloatBigEndian));
m_vSizes.Add(ra::etoi(MemSize::Double32), ra::data::MemSizeString(MemSize::Double32));
m_vSizes.Add(ra::etoi(MemSize::Double32BigEndian), ra::data::MemSizeString(MemSize::Double32BigEndian));
m_vSizes.Add(ra::etoi(MemSize::MBF32), ra::data::MemSizeString(MemSize::MBF32));
m_vSizes.Add(ra::etoi(MemSize::MBF32LE), ra::data::MemSizeString(MemSize::MBF32LE));
m_vSizes.Add(ra::etoi(MemSize::Text), ra::data::MemSizeString(MemSize::Text));

m_vFormats.Add(ra::etoi(MemFormat::Hex), L"Hex");
m_vFormats.Add(ra::etoi(MemFormat::Dec), L"Dec");
Expand Down
48 changes: 24 additions & 24 deletions src/ui/viewmodels/TriggerViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,30 +53,30 @@ TriggerViewModel::TriggerViewModel() noexcept
m_vOperandTypes.Add(ra::etoi(TriggerOperandType::Inverted), L"Invert");
m_vOperandTypes.Add(ra::etoi(TriggerOperandType::Recall), L"Recall");

m_vOperandSizes.Add(ra::etoi(MemSize::Bit_0), L"Bit0");
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_1), L"Bit1");
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_2), L"Bit2");
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_3), L"Bit3");
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_4), L"Bit4");
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_5), L"Bit5");
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_6), L"Bit6");
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_7), L"Bit7");
m_vOperandSizes.Add(ra::etoi(MemSize::Nibble_Lower), L"Lower4");
m_vOperandSizes.Add(ra::etoi(MemSize::Nibble_Upper), L"Upper4");
m_vOperandSizes.Add(ra::etoi(MemSize::EightBit), L"8-bit");
m_vOperandSizes.Add(ra::etoi(MemSize::SixteenBit), L"16-bit");
m_vOperandSizes.Add(ra::etoi(MemSize::TwentyFourBit), L"24-bit");
m_vOperandSizes.Add(ra::etoi(MemSize::ThirtyTwoBit), L"32-bit");
m_vOperandSizes.Add(ra::etoi(MemSize::SixteenBitBigEndian), L"16-bit BE");
m_vOperandSizes.Add(ra::etoi(MemSize::TwentyFourBitBigEndian), L"24-bit BE");
m_vOperandSizes.Add(ra::etoi(MemSize::ThirtyTwoBitBigEndian), L"32-bit BE");
m_vOperandSizes.Add(ra::etoi(MemSize::BitCount), L"BitCount");
m_vOperandSizes.Add(ra::etoi(MemSize::Float), L"Float");
m_vOperandSizes.Add(ra::etoi(MemSize::FloatBigEndian), L"Float BE");
m_vOperandSizes.Add(ra::etoi(MemSize::Double32), L"Double32");
m_vOperandSizes.Add(ra::etoi(MemSize::Double32BigEndian), L"Double32 BE");
m_vOperandSizes.Add(ra::etoi(MemSize::MBF32), L"MBF32");
m_vOperandSizes.Add(ra::etoi(MemSize::MBF32LE), L"MBF32 LE");
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_0), ra::data::MemSizeString(MemSize::Bit_0));
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_1), ra::data::MemSizeString(MemSize::Bit_1));
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_2), ra::data::MemSizeString(MemSize::Bit_2));
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_3), ra::data::MemSizeString(MemSize::Bit_3));
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_4), ra::data::MemSizeString(MemSize::Bit_4));
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_5), ra::data::MemSizeString(MemSize::Bit_5));
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_6), ra::data::MemSizeString(MemSize::Bit_6));
m_vOperandSizes.Add(ra::etoi(MemSize::Bit_7), ra::data::MemSizeString(MemSize::Bit_7));
m_vOperandSizes.Add(ra::etoi(MemSize::Nibble_Lower), ra::data::MemSizeString(MemSize::Nibble_Lower));
m_vOperandSizes.Add(ra::etoi(MemSize::Nibble_Upper), ra::data::MemSizeString(MemSize::Nibble_Upper));
m_vOperandSizes.Add(ra::etoi(MemSize::EightBit), ra::data::MemSizeString(MemSize::EightBit));
m_vOperandSizes.Add(ra::etoi(MemSize::SixteenBit), ra::data::MemSizeString(MemSize::SixteenBit));
m_vOperandSizes.Add(ra::etoi(MemSize::TwentyFourBit), ra::data::MemSizeString(MemSize::TwentyFourBit));
m_vOperandSizes.Add(ra::etoi(MemSize::ThirtyTwoBit), ra::data::MemSizeString(MemSize::ThirtyTwoBit));
m_vOperandSizes.Add(ra::etoi(MemSize::SixteenBitBigEndian), ra::data::MemSizeString(MemSize::SixteenBitBigEndian));
m_vOperandSizes.Add(ra::etoi(MemSize::TwentyFourBitBigEndian), ra::data::MemSizeString(MemSize::TwentyFourBitBigEndian));
m_vOperandSizes.Add(ra::etoi(MemSize::ThirtyTwoBitBigEndian), ra::data::MemSizeString(MemSize::ThirtyTwoBitBigEndian));
m_vOperandSizes.Add(ra::etoi(MemSize::BitCount), ra::data::MemSizeString(MemSize::BitCount));
m_vOperandSizes.Add(ra::etoi(MemSize::Float), ra::data::MemSizeString(MemSize::Float));
m_vOperandSizes.Add(ra::etoi(MemSize::FloatBigEndian), ra::data::MemSizeString(MemSize::FloatBigEndian));
m_vOperandSizes.Add(ra::etoi(MemSize::Double32), ra::data::MemSizeString(MemSize::Double32));
m_vOperandSizes.Add(ra::etoi(MemSize::Double32BigEndian), ra::data::MemSizeString(MemSize::Double32BigEndian));
m_vOperandSizes.Add(ra::etoi(MemSize::MBF32), ra::data::MemSizeString(MemSize::MBF32));
m_vOperandSizes.Add(ra::etoi(MemSize::MBF32LE), ra::data::MemSizeString(MemSize::MBF32LE));

m_vOperatorTypes.Add(ra::etoi(TriggerOperatorType::Equals), L"=");
m_vOperatorTypes.Add(ra::etoi(TriggerOperatorType::LessThan), L"<");
Expand Down
45 changes: 44 additions & 1 deletion tests/data/models/TriggerValidation_Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#include "data\models\TriggerValidation.hh"

#include "tests\mocks\MockConsoleContext.hh"
#include "tests\mocks\MockGameContext.hh"
#include "tests\mocks\MockEmulatorContext.hh"
#include "tests\mocks\MockUserContext.hh"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

Expand Down Expand Up @@ -188,8 +190,49 @@ TEST_CLASS(TriggerValidation_Tests)
AssertLeaderboardValidation("N:0xH1234<99_0x2345<50", L"");
AssertLeaderboardValidation("O:0xH1234<99_0x2345<50", L"");
}
};

TEST_METHOD(TestCodeNoteSizeComparisons)
{
ra::data::context::mocks::MockUserContext mockUserContext;
ra::data::context::mocks::MockGameContext mockGameContext;
mockGameContext.SetCodeNote(0x0008, L"[8-bit] Byte address");
mockGameContext.SetCodeNote(0x0010, L"[16-bit] Word address");
mockGameContext.SetCodeNote(0x0018, L"[24-bit] TByte address");
mockGameContext.SetCodeNote(0x0020, L"[32-bit] DWord address");
mockGameContext.SetCodeNote(0x0028, L"Unsized Byte address");
mockGameContext.SetCodeNote(0x0030, L"[16-bit BE] BE Word address");
mockGameContext.SetCodeNote(0x0040, L"[40-bytes] array of words");


// valid reads
AssertValidation("0xH0008>8", L"");
AssertValidation("0x 0010>8", L"");
AssertValidation("0xW0018>8", L"");
AssertValidation("0xX0020>8", L"");
AssertValidation("0xH0028>8", L"");
AssertValidation("0xI0030>8", L"");

// size mismatch
AssertValidation("0xH0001>8", L"Condition 1: No code note for address 0001");
AssertValidation("0xH0010>8", L"Condition 1: 8-bit read of address 0010 differs from code note size 16-bit");
AssertValidation("0x 0030>8", L"Condition 1: 16-bit read of address 0030 differs from code note size 16-bit BE");
AssertValidation("0x 0010>0x 0008", L"Condition 1: 16-bit read of address 0008 differs from code note size 8-bit");
AssertValidation("0x 0028>8", L"Condition 1: 16-bit read of address 0028 differs from code note size 8-bit");

// bit sizes only require a note exist
AssertValidation("0xN0008=1", L"");
AssertValidation("0xS0018=1", L"");
AssertValidation("0xK0020=1", L"");
AssertValidation("0xN0004=1", L"Condition 1: No code note for address 0004");
AssertValidation("0xK0004=1", L"Condition 1: No code note for address 0004");

// sub-note addresses
AssertValidation("0xH0009>8", L"Condition 1: No code note for address 0009");
AssertValidation("0xH0011>8", L"Condition 1: 8-bit read of address 0011 differs from code note size 16-bit at 0010");
AssertValidation("0xH0052>8", L""); // in array
}

};

} // namespace tests
} // namespace models
Expand Down

0 comments on commit 8e8c050

Please sign in to comment.