diff --git a/src/RA_Integration.vcxproj b/src/RA_Integration.vcxproj
index 5744e6ac..5d083dfb 100644
--- a/src/RA_Integration.vcxproj
+++ b/src/RA_Integration.vcxproj
@@ -86,6 +86,7 @@
+
@@ -232,6 +233,7 @@
+
diff --git a/src/RA_Integration.vcxproj.filters b/src/RA_Integration.vcxproj.filters
index 3bea2687..e1c34658 100644
--- a/src/RA_Integration.vcxproj.filters
+++ b/src/RA_Integration.vcxproj.filters
@@ -411,6 +411,9 @@
Data\Models
+
+ Services
+
@@ -977,6 +980,9 @@
Data\Models
+
+ Services
+
diff --git a/src/RA_StringUtils.cpp b/src/RA_StringUtils.cpp
index 3ec7e2d6..bbd08a68 100644
--- a/src/RA_StringUtils.cpp
+++ b/src/RA_StringUtils.cpp
@@ -161,6 +161,15 @@ bool ParseHex(const std::wstring& sValue, unsigned int nMaximumValue, unsigned i
return false;
}
+_Use_decl_annotations_
+bool ParseNumeric(const std::wstring& sValue, _Out_ unsigned int& nValue, _Out_ std::wstring& sError)
+{
+ if (sValue.length() > 2 && sValue.at(1) == 'x')
+ return ra::ParseHex(sValue, 0xFFFFFFFF, nValue, sError);
+
+ return ra::ParseUnsignedInt(sValue, 0xFFFFFFFF, nValue, sError);
+}
+
_Use_decl_annotations_
bool ParseFloat(const std::wstring& sValue, float& fValue, std::wstring& sError)
{
diff --git a/src/RA_StringUtils.h b/src/RA_StringUtils.h
index e8301a77..4549e75f 100644
--- a/src/RA_StringUtils.h
+++ b/src/RA_StringUtils.h
@@ -22,6 +22,7 @@ _NODISCARD std::string Narrow(_In_ const std::string& wstr);
bool ParseUnsignedInt(const std::wstring& sValue, unsigned int nMaximumValue, _Out_ unsigned int& nValue, _Out_ std::wstring& sError);
bool ParseHex(const std::wstring& sValue, unsigned int nMaximumValue, _Out_ unsigned int& nValue, _Out_ std::wstring& sError);
+bool ParseNumeric(const std::wstring& sValue, _Out_ unsigned int& nValue, _Out_ std::wstring& sError);
bool ParseFloat(const std::wstring& sValue, _Out_ float& fValue, _Out_ std::wstring& sError);
///
diff --git a/src/data/context/ConsoleContext.cpp b/src/data/context/ConsoleContext.cpp
index c4fbdab4..814767f4 100644
--- a/src/data/context/ConsoleContext.cpp
+++ b/src/data/context/ConsoleContext.cpp
@@ -147,6 +147,71 @@ ra::ByteAddress ConsoleContext::ByteAddressFromRealAddress(ra::ByteAddress nReal
return 0xFFFFFFFF;
}
+ra::ByteAddress ConsoleContext::RealAddressFromByteAddress(ra::ByteAddress nByteAddress) const noexcept
+{
+ for (const auto& pRegion : m_vRegions)
+ {
+ if (pRegion.EndAddress >= nByteAddress && pRegion.StartAddress <= nByteAddress)
+ return nByteAddress + pRegion.RealAddress;
+ }
+
+ return 0xFFFFFFFF;
+}
+
+bool ConsoleContext::GetRealAddressConversion(MemSize* nReadSize, uint32_t* nMask, uint32_t* nOffset) const
+{
+ Expects(nReadSize != nullptr);
+ Expects(nMask != nullptr);
+ Expects(nOffset != nullptr);
+
+ switch (m_nId)
+ {
+ case ConsoleID::Dreamcast:
+ case ConsoleID::DSi:
+ case ConsoleID::PlayStation:
+ *nReadSize = MemSize::TwentyFourBit;
+ *nMask = 0xFFFFFFFF;
+ *nOffset = 0;
+ return true;
+
+ case ConsoleID::GameCube:
+ case ConsoleID::WII:
+ *nReadSize = MemSize::ThirtyTwoBitBigEndian;
+ *nMask = 0x01FFFFFF;
+ *nOffset = 0;
+ return true;
+
+ case ConsoleID::PlayStation2:
+ case ConsoleID::PSP:
+ *nReadSize = MemSize::ThirtyTwoBit;
+ *nMask = 0x01FFFFFF;
+ *nOffset = 0;
+ return true;
+
+ default:
+ for (const auto& pRegion : m_vRegions)
+ {
+ if (pRegion.Type == AddressType::SystemRAM)
+ {
+ *nOffset = (pRegion.RealAddress - pRegion.StartAddress);
+ *nMask = 0xFFFFFFFF;
+
+ if (m_nMaxAddress > 0x00FFFFFF)
+ *nReadSize = MemSize::ThirtyTwoBit;
+ else if (m_nMaxAddress > 0x0000FFFF)
+ *nReadSize = MemSize::TwentyFourBit;
+ else if (m_nMaxAddress > 0x000000FF)
+ *nReadSize = MemSize::SixteenBit;
+ else
+ *nReadSize = MemSize::EightBit;
+
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
} // namespace context
} // namespace data
} // namespace ra
diff --git a/src/data/context/ConsoleContext.hh b/src/data/context/ConsoleContext.hh
index b2a54bf4..96e19248 100644
--- a/src/data/context/ConsoleContext.hh
+++ b/src/data/context/ConsoleContext.hh
@@ -101,6 +101,22 @@ public:
/// Converted address, or 0xFFFFFFFF if conversion could not be completed.
ra::ByteAddress ByteAddressFromRealAddress(ra::ByteAddress nRealAddress) const noexcept;
+ ///
+ /// Converts an "RetroAchievements" address into a real address where the data might be found.
+ ///
+ /// Converted address, or 0xFFFFFFFF if conversion could not be completed.
+ ra::ByteAddress RealAddressFromByteAddress(ra::ByteAddress nRealAddress) const noexcept;
+
+ ///
+ /// Gets the read size and mask to use for reading a pointer from memory and converting it to
+ /// a RetroAchievements address.
+ ///
+ /// [out] the size to read.
+ /// [out] the mask to apply (if 0xFFFFFFFF, no mask is necessary).
+ /// [out] the offset to apply (if 0, no offset is necessary).
+ /// true if a mapping was found, false if not.
+ bool GetRealAddressConversion(MemSize* nReadSize, uint32_t* nMask, uint32_t* nOffset) const;
+
///
/// Gets the maximum valid address for the console.
///
diff --git a/src/data/models/CodeNoteModel.cpp b/src/data/models/CodeNoteModel.cpp
index 43eba9c7..8d89fb97 100644
--- a/src/data/models/CodeNoteModel.cpp
+++ b/src/data/models/CodeNoteModel.cpp
@@ -16,6 +16,7 @@ struct CodeNoteModel::PointerData
{
uint32_t RawPointerValue = 0xFFFFFFFF; // last raw value of pointer captured
ra::ByteAddress PointerAddress = 0xFFFFFFFF; // raw pointer value converted to RA address
+ ra::ByteAddress NoteAddress = 0xFFFFFFFF; // RA address where RawPointerValue was read from
unsigned int OffsetRange = 0; // highest offset captured within pointer block
unsigned int HeaderLength = 0; // length of note text not associated to OffsetNotes
bool HasPointers = false; // true if there are nested pointers
@@ -28,12 +29,7 @@ struct CodeNoteModel::PointerData
};
OffsetType OffsetType = OffsetType::None;
- struct OffsetCodeNote
- {
- CodeNoteModel CodeNote;
- int Offset = 0;
- };
- std::vector OffsetNotes;
+ std::vector OffsetNotes;
};
// these must be defined here because of forward declaration of PointerData in header file.
@@ -43,6 +39,7 @@ CodeNoteModel::CodeNoteModel(CodeNoteModel&& pOther) noexcept
: m_sAuthor(std::move(pOther.m_sAuthor)),
m_sNote(std::move(pOther.m_sNote)),
m_nBytes(pOther.m_nBytes),
+ m_nAddress(pOther.m_nAddress),
m_nMemSize(pOther.m_nMemSize),
m_pPointerData(std::move(pOther.m_pPointerData))
{
@@ -52,6 +49,7 @@ CodeNoteModel& CodeNoteModel::operator=(CodeNoteModel&& pOther) noexcept
m_sAuthor = std::move(pOther.m_sAuthor);
m_sNote = std::move(pOther.m_sNote);
m_nBytes = pOther.m_nBytes;
+ m_nAddress = pOther.m_nAddress;
m_nMemSize = pOther.m_nMemSize;
m_pPointerData = std::move(pOther.m_pPointerData);
return *this;
@@ -88,6 +86,8 @@ void CodeNoteModel::UpdateRawPointerValue(ra::ByteAddress nAddress, const ra::da
if (m_pPointerData == nullptr)
return;
+ m_pPointerData->NoteAddress = nAddress;
+
const uint32_t nValue = pEmulatorContext.ReadMemory(nAddress, GetMemSize());
if (nValue != m_pPointerData->RawPointerValue)
{
@@ -104,8 +104,8 @@ void CodeNoteModel::UpdateRawPointerValue(ra::ByteAddress nAddress, const ra::da
{
for (const auto& pNote : m_pPointerData->OffsetNotes)
{
- if (!pNote.CodeNote.IsPointer())
- fNoteMovedCallback(nOldAddress + pNote.Offset, nNewAddress + pNote.Offset, pNote.CodeNote);
+ if (!pNote.IsPointer())
+ fNoteMovedCallback(nOldAddress + pNote.GetAddress(), nNewAddress + pNote.GetAddress(), pNote);
}
}
}
@@ -115,10 +115,10 @@ void CodeNoteModel::UpdateRawPointerValue(ra::ByteAddress nAddress, const ra::da
{
for (auto& pNote : m_pPointerData->OffsetNotes)
{
- if (pNote.CodeNote.IsPointer())
+ if (pNote.IsPointer())
{
- pNote.CodeNote.UpdateRawPointerValue(m_pPointerData->PointerAddress + pNote.Offset,
- pEmulatorContext, fNoteMovedCallback);
+ pNote.UpdateRawPointerValue(m_pPointerData->PointerAddress + pNote.GetAddress(),
+ pEmulatorContext, fNoteMovedCallback);
}
}
}
@@ -132,8 +132,8 @@ const CodeNoteModel* CodeNoteModel::GetPointerNoteAtOffset(int nOffset) const
// look for explicit offset match
for (const auto& pOffsetNote : m_pPointerData->OffsetNotes)
{
- if (pOffsetNote.Offset == nOffset)
- return &pOffsetNote.CodeNote;
+ if (ra::to_signed(pOffsetNote.GetAddress()) == nOffset)
+ return &pOffsetNote;
}
if (m_pPointerData->OffsetType == PointerData::OffsetType::Overflow)
@@ -144,8 +144,8 @@ const CodeNoteModel* CodeNoteModel::GetPointerNoteAtOffset(int nOffset) const
for (const auto& pOffsetNote : m_pPointerData->OffsetNotes)
{
- if (pOffsetNote.Offset == nOffset)
- return &pOffsetNote.CodeNote;
+ if (ra::to_signed(pOffsetNote.GetAddress()) == nOffset)
+ return &pOffsetNote;
}
}
@@ -169,23 +169,23 @@ std::pair CodeNoteModel::GetPointerNoteAt
// if address is in the struct, look for a matching field
if (bAddressValid)
{
- auto nOffset = ra::to_signed(nAddress - nPointerAddress);
+ const auto nOffset = nAddress - nPointerAddress;
// check for exact matches first
for (const auto& pOffsetNote : m_pPointerData->OffsetNotes)
{
- if (nOffset == pOffsetNote.Offset)
- return {pOffsetNote.Offset + nPointerAddress, &pOffsetNote.CodeNote};
+ if (nOffset == pOffsetNote.GetAddress())
+ return {nPointerAddress + pOffsetNote.GetAddress(), &pOffsetNote};
}
// check for trailing bytes in a multi-byte note
for (const auto& pOffsetNote : m_pPointerData->OffsetNotes)
{
- if (nOffset > pOffsetNote.Offset)
+ if (nOffset > pOffsetNote.GetAddress())
{
- const auto nBytes = ra::to_signed(pOffsetNote.CodeNote.GetBytes());
- if (nBytes > 1 && nOffset < pOffsetNote.Offset + nBytes)
- return {pOffsetNote.Offset + nPointerAddress, &pOffsetNote.CodeNote};
+ const auto nBytes = ra::to_signed(pOffsetNote.GetBytes());
+ if (nBytes > 1 && nOffset < pOffsetNote.GetAddress() + nBytes)
+ return {nPointerAddress + pOffsetNote.GetAddress(), &pOffsetNote};
}
}
}
@@ -195,9 +195,9 @@ std::pair CodeNoteModel::GetPointerNoteAt
{
for (const auto& pOffsetNote : m_pPointerData->OffsetNotes)
{
- if (pOffsetNote.CodeNote.IsPointer())
+ if (pOffsetNote.IsPointer())
{
- auto pNestedObject = pOffsetNote.CodeNote.GetPointerNoteAtAddress(nAddress);
+ auto pNestedObject = pOffsetNote.GetPointerNoteAtAddress(nAddress);
if (pNestedObject.second)
return pNestedObject;
}
@@ -208,6 +208,44 @@ std::pair CodeNoteModel::GetPointerNoteAt
return {0, nullptr};
}
+bool CodeNoteModel::GetPointerChain(std::vector& vChain, const CodeNoteModel& pRootNote) const
+{
+ if (!pRootNote.IsPointer())
+ return false;
+
+ vChain.push_back(&pRootNote);
+
+ if (&pRootNote == this)
+ return true;
+
+ return GetPointerChainRecursive(vChain, pRootNote);
+}
+
+bool CodeNoteModel::GetPointerChainRecursive(std::vector& vChain,
+ const CodeNoteModel& pParentNote) const
+{
+ for (auto& pNote : pParentNote.m_pPointerData->OffsetNotes)
+ {
+ if (&pNote == this)
+ {
+ vChain.push_back(this);
+ return true;
+ }
+
+ if (pNote.IsPointer())
+ {
+ vChain.push_back(&pNote);
+
+ if (GetPointerChainRecursive(vChain, pNote))
+ return true;
+
+ vChain.pop_back();
+ }
+ }
+
+ return false;
+}
+
bool CodeNoteModel::GetPreviousAddress(ra::ByteAddress nBeforeAddress, ra::ByteAddress& nPreviousAddress) const
{
if (m_pPointerData == nullptr)
@@ -224,7 +262,7 @@ bool CodeNoteModel::GetPreviousAddress(ra::ByteAddress nBeforeAddress, ra::ByteA
nPreviousAddress = 0;
for (const auto& pOffset : m_pPointerData->OffsetNotes)
{
- const auto nOffsetAddress = nPointerAddress + pOffset.Offset;
+ const auto nOffsetAddress = nPointerAddress + pOffset.GetAddress();
if (nOffsetAddress < nBeforeAddress && nOffsetAddress > nPreviousAddress)
{
nPreviousAddress = nOffsetAddress;
@@ -251,7 +289,7 @@ bool CodeNoteModel::GetNextAddress(ra::ByteAddress nAfterAddress, ra::ByteAddres
nNextAddress = 0xFFFFFFFF;
for (const auto& pOffset : m_pPointerData->OffsetNotes)
{
- const auto nOffsetAddress = nPointerAddress + pOffset.Offset;
+ const auto nOffsetAddress = nPointerAddress + pOffset.GetAddress();
if (nOffsetAddress > nAfterAddress && nOffsetAddress < nNextAddress)
{
nNextAddress = nOffsetAddress;
@@ -555,8 +593,8 @@ void CodeNoteModel::ProcessIndirectNotes(const std::wstring& sNote, size_t nInde
do
{
- PointerData::OffsetCodeNote offsetNote;
- offsetNote.CodeNote.SetAuthor(m_sAuthor);
+ CodeNoteModel offsetNote;
+ offsetNote.SetAuthor(m_sAuthor);
// the next note starts when we find a '+' at the start of a line.
auto nNextIndex = sNote.find(L"\n+", nIndex);
@@ -591,12 +629,13 @@ void CodeNoteModel::ProcessIndirectNotes(const std::wstring& sNote, size_t nInde
// extract the offset
wchar_t* pEnd = nullptr;
+ int nOffset = 0;
try
{
if (sNextNote.length() > 2 && sNextNote.at(1) == 'x')
- offsetNote.Offset = gsl::narrow_cast(std::wcstoll(sNextNote.c_str() + 2, &pEnd, 16));
+ nOffset = gsl::narrow_cast(std::wcstoll(sNextNote.c_str() + 2, &pEnd, 16));
else
- offsetNote.Offset = gsl::narrow_cast(std::wcstoll(sNextNote.c_str(), &pEnd, 10));
+ nOffset = gsl::narrow_cast(std::wcstoll(sNextNote.c_str(), &pEnd, 10));
} catch (const std::exception&)
{
break;
@@ -617,10 +656,12 @@ void CodeNoteModel::ProcessIndirectNotes(const std::wstring& sNote, size_t nInde
pEnd++;
}
- offsetNote.CodeNote.SetNote(sNextNote.substr(pEnd - sNextNote.c_str()));
- pointerData->HasPointers |= offsetNote.CodeNote.IsPointer();
+ offsetNote.SetNote(sNextNote.substr(pEnd - sNextNote.c_str()));
+ pointerData->HasPointers |= offsetNote.IsPointer();
+
+ offsetNote.SetAddress(gsl::narrow_cast(nOffset));
- const auto nRangeOffset = offsetNote.Offset + offsetNote.CodeNote.GetBytes();
+ const auto nRangeOffset = nOffset + offsetNote.GetBytes();
pointerData->OffsetRange = std::max(pointerData->OffsetRange, nRangeOffset);
pointerData->OffsetNotes.push_back(std::move(offsetNote));
@@ -644,7 +685,7 @@ void CodeNoteModel::ProcessIndirectNotes(const std::wstring& sNote, size_t nInde
// overflow math instead of masking, and don't attempt to translate the addresses.
for (const auto& pNote : pointerData->OffsetNotes)
{
- if (ra::to_unsigned(pNote.Offset) >= nMaxAddress)
+ if (pNote.GetAddress() >= nMaxAddress)
{
pointerData->OffsetType = PointerData::OffsetType::Overflow;
break;
@@ -675,7 +716,7 @@ void CodeNoteModel::EnumeratePointerNotes(ra::ByteAddress nPointerAddress,
for (const auto& pNote : m_pPointerData->OffsetNotes)
{
- if (!fCallback(nPointerAddress + pNote.Offset, pNote.CodeNote))
+ if (!fCallback(nPointerAddress + pNote.GetAddress(), pNote))
break;
}
}
diff --git a/src/data/models/CodeNoteModel.hh b/src/data/models/CodeNoteModel.hh
index 80e522ca..507bbb44 100644
--- a/src/data/models/CodeNoteModel.hh
+++ b/src/data/models/CodeNoteModel.hh
@@ -22,10 +22,12 @@ public:
const std::string& GetAuthor() const noexcept { return m_sAuthor; }
const std::wstring& GetNote() const noexcept { return m_sNote; }
+ const ra::ByteAddress GetAddress() const noexcept { return m_nAddress; }
const unsigned int GetBytes() const noexcept { return m_nBytes; }
const MemSize GetMemSize() const noexcept { return m_nMemSize; }
void SetAuthor(const std::string& sAuthor) { m_sAuthor = sAuthor; }
+ void SetAddress(ra::ByteAddress nAddress) noexcept { m_nAddress = nAddress; }
void SetNote(const std::wstring& sNote);
bool IsPointer() const noexcept { return m_pPointerData != nullptr; }
@@ -35,6 +37,8 @@ public:
const CodeNoteModel* GetPointerNoteAtOffset(int nOffset) const;
std::pair GetPointerNoteAtAddress(ra::ByteAddress nAddress) const;
+ bool GetPointerChain(std::vector& vChain, const CodeNoteModel& pRootNote) const;
+
typedef std::function NoteMovedFunction;
void UpdateRawPointerValue(ra::ByteAddress nAddress, const ra::data::context::EmulatorContext& pEmulatorContext, NoteMovedFunction fNoteMovedCallback);
@@ -48,6 +52,7 @@ public:
private:
std::string m_sAuthor;
std::wstring m_sNote;
+ ra::ByteAddress m_nAddress = 0; // address of root nodes, offset to indirect nodes
unsigned int m_nBytes = 1;
MemSize m_nMemSize = MemSize::Unknown;
@@ -55,6 +60,8 @@ private:
std::unique_ptr m_pPointerData;
private:
+ bool GetPointerChainRecursive(std::vector& vChain, const CodeNoteModel& pParentNote) const;
+
void ProcessIndirectNotes(const std::wstring& sNote, size_t nIndex);
void ExtractSize(const std::wstring& sNote);
};
diff --git a/src/data/models/CodeNotesModel.cpp b/src/data/models/CodeNotesModel.cpp
index a44346e0..40ed579f 100644
--- a/src/data/models/CodeNotesModel.cpp
+++ b/src/data/models/CodeNotesModel.cpp
@@ -25,7 +25,7 @@ CodeNotesModel::CodeNotesModel() noexcept
void CodeNotesModel::Refresh(unsigned int nGameId, CodeNoteChangedFunction fCodeNoteChanged, std::function callback)
{
m_nGameId = nGameId;
- m_mCodeNotes.clear();
+ m_vCodeNotes.clear();
if (nGameId == 0)
{
@@ -86,11 +86,17 @@ void CodeNotesModel::Refresh(unsigned int nGameId, CodeNoteChangedFunction fCode
});
}
+static int CompareNoteAddresses(const ra::data::models::CodeNoteModel& left, const ra::data::models::CodeNoteModel& right) noexcept
+{
+ return left.GetAddress() < right.GetAddress();
+}
+
void CodeNotesModel::AddCodeNote(ra::ByteAddress nAddress, const std::string& sAuthor, const std::wstring& sNote)
{
CodeNoteModel note;
note.SetAuthor(sAuthor);
note.SetNote(sNote);
+ note.SetAddress(nAddress);
const bool bIsPointer = note.IsPointer();
if (bIsPointer)
@@ -104,7 +110,11 @@ void CodeNotesModel::AddCodeNote(ra::ByteAddress nAddress, const std::string& sA
{
std::unique_lock lock(m_oMutex);
- m_mCodeNotes.insert_or_assign(nAddress, std::move(note));
+ auto iter = std::lower_bound(m_vCodeNotes.begin(), m_vCodeNotes.end(), note, CompareNoteAddresses);
+ if (iter != m_vCodeNotes.end() && iter->GetAddress() == note.GetAddress())
+ *iter = std::move(note);
+ else
+ m_vCodeNotes.insert(iter, std::move(note));
}
OnCodeNoteChanged(nAddress, sNote);
@@ -112,11 +122,14 @@ void CodeNotesModel::AddCodeNote(ra::ByteAddress nAddress, const std::string& sA
// also raise CodeNoteChanged events for each indirect child note
if (bIsPointer && m_fCodeNoteChanged)
{
- const auto& pNote = m_mCodeNotes[nAddress];
- pNote.EnumeratePointerNotes([this](ra::ByteAddress nAddress, const CodeNoteModel& pOffsetNote) {
- m_fCodeNoteChanged(nAddress, pOffsetNote.GetNote());
- return true;
- });
+ const auto* pCodeNote = FindCodeNoteModel(nAddress);
+ if (pCodeNote != nullptr)
+ {
+ pCodeNote->EnumeratePointerNotes([this](ra::ByteAddress nAddress, const CodeNoteModel& pOffsetNote) {
+ m_fCodeNoteChanged(nAddress, pOffsetNote.GetNote());
+ return true;
+ });
+ }
}
}
@@ -133,33 +146,35 @@ void CodeNotesModel::OnCodeNoteChanged(ra::ByteAddress nAddress, const std::wstr
ra::ByteAddress CodeNotesModel::FindCodeNoteStart(ra::ByteAddress nAddress) const
{
- auto pIter = m_mCodeNotes.lower_bound(nAddress);
+ CodeNoteModel searchNote;
+ searchNote.SetAddress(nAddress);
+ auto pCodeNote = std::lower_bound(m_vCodeNotes.begin(), m_vCodeNotes.end(), searchNote, CompareNoteAddresses);
// exact match, return it
- if (pIter != m_mCodeNotes.end() && pIter->first == nAddress)
+ if (pCodeNote != m_vCodeNotes.end() && pCodeNote->GetAddress() == nAddress)
return nAddress;
// lower_bound returns the first item _after_ the search value. scan all items before
// the found item to see if any of them contain the target address. have to scan
// all items because a singular note may exist within a range.
- if (pIter != m_mCodeNotes.begin())
+ if (pCodeNote != m_vCodeNotes.begin())
{
do
{
- --pIter;
+ --pCodeNote;
- if (pIter->second.GetBytes() > 1 && pIter->second.GetBytes() + pIter->first > nAddress)
- return pIter->first;
+ if (pCodeNote->GetBytes() > 1 && pCodeNote->GetBytes() + pCodeNote->GetAddress() > nAddress)
+ return pCodeNote->GetAddress();
- } while (pIter != m_mCodeNotes.begin());
+ } while (pCodeNote != m_vCodeNotes.begin());
}
// also check for derived code notes
if (m_bHasPointers)
{
- for (const auto& pCodeNote : m_mCodeNotes)
+ for (const auto& pNote : m_vCodeNotes)
{
- const auto pair = pCodeNote.second.GetPointerNoteAtAddress(nAddress);
+ const auto pair = pNote.GetPointerNoteAtAddress(nAddress);
if (pair.second != nullptr)
return pair.first;
}
@@ -206,46 +221,48 @@ std::wstring CodeNotesModel::FindCodeNote(ra::ByteAddress nAddress, MemSize nSiz
const unsigned int nCheckBytes = ra::data::MemSizeBytes(nSize);
// lower_bound will return the item if it's an exact match, or the *next* item otherwise
- auto pIter = m_mCodeNotes.lower_bound(nAddress);
- if (pIter != m_mCodeNotes.end())
+ CodeNoteModel searchNote;
+ searchNote.SetAddress(nAddress);
+ auto pCodeNote = std::lower_bound(m_vCodeNotes.begin(), m_vCodeNotes.end(), searchNote, CompareNoteAddresses);
+ if (pCodeNote != m_vCodeNotes.end())
{
- if (nAddress == pIter->first)
+ if (nAddress == pCodeNote->GetAddress())
{
// exact match
- return BuildCodeNoteSized(nAddress, nCheckBytes, pIter->first, pIter->second);
+ return BuildCodeNoteSized(nAddress, nCheckBytes, pCodeNote->GetAddress(), *pCodeNote);
}
- else if (nAddress + nCheckBytes - 1 >= pIter->first)
+ else if (nAddress + nCheckBytes - 1 >= pCodeNote->GetAddress())
{
// requested number of bytes will overlap with the next item
- return BuildCodeNoteSized(nAddress, nCheckBytes, pIter->first, pIter->second);
+ return BuildCodeNoteSized(nAddress, nCheckBytes, pCodeNote->GetAddress(), *pCodeNote);
}
}
// did not match/overlap with the found item, check the item before the found item
- if (pIter != m_mCodeNotes.begin())
+ if (pCodeNote != m_vCodeNotes.begin())
{
- --pIter;
- if (pIter->first + pIter->second.GetBytes() - 1 >= nAddress)
+ --pCodeNote;
+ if (pCodeNote->GetAddress() + pCodeNote->GetBytes() - 1 >= nAddress)
{
// previous item overlaps with requested address
- return BuildCodeNoteSized(nAddress, nCheckBytes, pIter->first, pIter->second);
+ return BuildCodeNoteSized(nAddress, nCheckBytes, pCodeNote->GetAddress(), *pCodeNote);
}
}
// no code note on the address, check for pointers
if (m_bHasPointers)
{
- for (const auto& pIter2 : m_mCodeNotes)
+ for (const auto& pCodeNote2 : m_vCodeNotes)
{
- const auto pair = pIter2.second.GetPointerNoteAtAddress(nAddress);
+ const auto pair = pCodeNote2.GetPointerNoteAtAddress(nAddress);
if (pair.second != nullptr)
return BuildCodeNoteSized(nAddress, nCheckBytes, pair.first, *pair.second) + L" [indirect]";
}
const auto nLastAddress = nAddress + nCheckBytes - 1;
- for (const auto& pIter2 : m_mCodeNotes)
+ for (const auto& pCodeNote2 : m_vCodeNotes)
{
- const auto pair = pIter2.second.GetPointerNoteAtAddress(nLastAddress);
+ const auto pair = pCodeNote2.GetPointerNoteAtAddress(nLastAddress);
if (pair.second != nullptr)
return BuildCodeNoteSized(nAddress, nCheckBytes, pair.first, *pair.second) + L" [indirect]";
}
@@ -256,11 +273,13 @@ std::wstring CodeNotesModel::FindCodeNote(ra::ByteAddress nAddress, MemSize nSiz
const std::wstring* CodeNotesModel::FindCodeNote(ra::ByteAddress nAddress, _Inout_ std::string& sAuthor) const
{
- const auto pIter = m_mCodeNotes.find(nAddress);
- if (pIter != m_mCodeNotes.end())
+ CodeNoteModel searchNote;
+ searchNote.SetAddress(nAddress);
+ const auto pCodeNote = std::lower_bound(m_vCodeNotes.begin(), m_vCodeNotes.end(), searchNote, CompareNoteAddresses);
+ if (pCodeNote != m_vCodeNotes.end() && pCodeNote->GetAddress() == nAddress)
{
- sAuthor = pIter->second.GetAuthor();
- return &pIter->second.GetNote();
+ sAuthor = pCodeNote->GetAuthor();
+ return &pCodeNote->GetNote();
}
return nullptr;
@@ -279,10 +298,12 @@ void CodeNotesModel::SetCodeNote(ra::ByteAddress nAddress, const std::wstring& s
return;
}
- const auto pIter = m_mCodeNotes.find(nAddress);
- if (pIter != m_mCodeNotes.end())
+ CodeNoteModel searchNote;
+ searchNote.SetAddress(nAddress);
+ const auto pCodeNote = std::lower_bound(m_vCodeNotes.begin(), m_vCodeNotes.end(), searchNote, CompareNoteAddresses);
+ if (pCodeNote != m_vCodeNotes.end() && pCodeNote->GetAddress() == nAddress)
{
- if (pIter->second.GetNote() == sNote)
+ if (pCodeNote->GetNote() == sNote)
{
// the note at this address is unchanged
return;
@@ -298,11 +319,11 @@ void CodeNotesModel::SetCodeNote(ra::ByteAddress nAddress, const std::wstring& s
if (pIter2 == m_mOriginalCodeNotes.end())
{
// note wasn't previously modified
- if (pIter != m_mCodeNotes.end())
+ if (pCodeNote != m_vCodeNotes.end() && pCodeNote->GetAddress() == nAddress)
{
// capture the original value
m_mOriginalCodeNotes.insert_or_assign(nAddress,
- std::make_pair(pIter->second.GetAuthor(), pIter->second.GetNote()));
+ std::make_pair(pCodeNote->GetAuthor(), pCodeNote->GetNote()));
}
else
{
@@ -322,7 +343,7 @@ void CodeNotesModel::SetCodeNote(ra::ByteAddress nAddress, const std::wstring& s
{
// note didn't originally exist, don't keep a modification record if the
// changes were discarded.
- m_mCodeNotes.erase(nAddress);
+ m_vCodeNotes.erase(pCodeNote);
OnCodeNoteChanged(nAddress, sNote);
return;
}
@@ -342,9 +363,12 @@ void CodeNotesModel::SetCodeNote(ra::ByteAddress nAddress, const std::wstring& s
const CodeNoteModel* CodeNotesModel::FindCodeNoteModel(ra::ByteAddress nAddress) const
{
- const auto pIter = m_mCodeNotes.find(nAddress);
- if (pIter != m_mCodeNotes.end())
- return &pIter->second;
+ CodeNoteModel searchNote;
+ searchNote.SetAddress(nAddress);
+
+ const auto pCodeNote = std::lower_bound(m_vCodeNotes.begin(), m_vCodeNotes.end(), searchNote, CompareNoteAddresses);
+ if (pCodeNote != m_vCodeNotes.end() && pCodeNote->GetAddress() == nAddress)
+ return &(*pCodeNote);
if (m_bHasPointers)
return FindIndirectCodeNoteInternal(nAddress).second;
@@ -355,11 +379,11 @@ const CodeNoteModel* CodeNotesModel::FindCodeNoteModel(ra::ByteAddress nAddress)
std::pair
CodeNotesModel::FindIndirectCodeNoteInternal(ra::ByteAddress nAddress) const
{
- for (const auto& pCodeNote : m_mCodeNotes)
+ for (const auto& pCodeNote : m_vCodeNotes)
{
- auto pair = pCodeNote.second.GetPointerNoteAtAddress(nAddress);
+ auto pair= pCodeNote.GetPointerNoteAtAddress(nAddress);
if (pair.second != nullptr && pair.first == nAddress) // only match start of note
- return {pCodeNote.first, pair.second};
+ return {pCodeNote.GetAddress(), pair.second};
}
return {0, nullptr};
@@ -382,16 +406,18 @@ ra::ByteAddress CodeNotesModel::GetNextNoteAddress(ra::ByteAddress nAfterAddress
ra::ByteAddress nBestAddress = 0xFFFFFFFF;
// lower_bound will return the item if it's an exact match, or the *next* item otherwise
- const auto pIter = m_mCodeNotes.lower_bound(nAfterAddress + 1);
- if (pIter != m_mCodeNotes.end())
- nBestAddress = pIter->first;
+ CodeNoteModel searchNote;
+ searchNote.SetAddress(nAfterAddress + 1);
+ const auto pCodeNote = std::lower_bound(m_vCodeNotes.begin(), m_vCodeNotes.end(), searchNote, CompareNoteAddresses);
+ if (pCodeNote != m_vCodeNotes.end())
+ nBestAddress = pCodeNote->GetAddress();
if (m_bHasPointers && bIncludeDerived)
{
ra::ByteAddress nNextAddress = 0U;
- for (const auto& pNote : m_mCodeNotes)
+ for (const auto& pNote : m_vCodeNotes)
{
- if (pNote.second.GetNextAddress(nAfterAddress, nNextAddress))
+ if (pNote.GetNextAddress(nAfterAddress, nNextAddress))
nBestAddress = std::min(nBestAddress, nNextAddress);
}
}
@@ -404,18 +430,20 @@ ra::ByteAddress CodeNotesModel::GetPreviousNoteAddress(ra::ByteAddress nBeforeAd
unsigned nBestAddress = 0xFFFFFFFF;
// lower_bound will return the item if it's an exact match, or the *next* item otherwise
- auto pIter = m_mCodeNotes.lower_bound(nBeforeAddress - 1);
- if (pIter != m_mCodeNotes.end() && pIter->first == nBeforeAddress - 1)
+ CodeNoteModel searchNote;
+ searchNote.SetAddress(nBeforeAddress - 1);
+ auto pCodeNote = std::lower_bound(m_vCodeNotes.begin(), m_vCodeNotes.end(), searchNote, CompareNoteAddresses);
+ if (pCodeNote != m_vCodeNotes.end() && pCodeNote->GetAddress() == nBeforeAddress - 1)
{
// exact match for 1 byte lower, return it.
- return pIter->first;
+ return pCodeNote->GetAddress();
}
- if (pIter != m_mCodeNotes.begin())
+ if (pCodeNote != m_vCodeNotes.begin())
{
// found next lower item, claim it
- --pIter;
- nBestAddress = pIter->first;
+ --pCodeNote;
+ nBestAddress = pCodeNote->GetAddress();
}
if (m_bHasPointers && bIncludeDerived)
@@ -423,9 +451,9 @@ ra::ByteAddress CodeNotesModel::GetPreviousNoteAddress(ra::ByteAddress nBeforeAd
ra::ByteAddress nPreviousAddress = 0U;
// scan pointed-at addresses to see if there's anything between the next lower item and nBeforeAddress
- for (const auto& pNote : m_mCodeNotes)
+ for (const auto& pNote : m_vCodeNotes)
{
- if (pNote.second.GetPreviousAddress(nBeforeAddress, nPreviousAddress))
+ if (pNote.GetPreviousAddress(nBeforeAddress, nPreviousAddress))
nBestAddress = std::max(nBestAddress, nPreviousAddress);
}
}
@@ -438,9 +466,9 @@ void CodeNotesModel::EnumerateCodeNotes(std::function mNotes;
- for (const auto& pIter : m_mCodeNotes)
+ for (const auto& pCodeNote : m_vCodeNotes)
{
- if (!pIter.second.IsPointer())
+ if (!pCodeNote.IsPointer())
continue;
- pIter.second.EnumeratePointerNotes(
+ pCodeNote.EnumeratePointerNotes(
[&mNotes](ra::ByteAddress nAddress, const CodeNoteModel& pNote) {
mNotes[nAddress] = &pNote;
return true;
@@ -462,8 +490,8 @@ void CodeNotesModel::EnumerateCodeNotes(std::function();
- for (auto& pNote : m_mCodeNotes)
+ for (auto& pCodeNote : m_vCodeNotes)
{
- if (pNote.second.IsPointer())
+ if (pCodeNote.IsPointer())
{
- pNote.second.UpdateRawPointerValue(pNote.first, pEmulatorContext,
+ pCodeNote.UpdateRawPointerValue(pCodeNote.GetAddress(), pEmulatorContext,
[this](ra::ByteAddress nOldAddress, ra::ByteAddress nNewAddress, const CodeNoteModel& pOffsetNote) {
m_fCodeNoteChanged(nOldAddress, L"");
m_fCodeNoteChanged(nNewAddress, pOffsetNote.GetNote());
@@ -503,11 +531,13 @@ void CodeNotesModel::SetServerCodeNote(ra::ByteAddress nAddress, const std::wstr
}
// if we're just committing the current value, we're done
- const auto pIter2 = m_mCodeNotes.find(nAddress);
- if (pIter2 != m_mCodeNotes.end() && pIter2->second.GetNote() == sNote)
+ CodeNoteModel searchNote;
+ searchNote.SetAddress(nAddress);
+ const auto pCodeNote = std::lower_bound(m_vCodeNotes.begin(), m_vCodeNotes.end(), searchNote, CompareNoteAddresses);
+ if (pCodeNote != m_vCodeNotes.end() && pCodeNote->GetAddress() == nAddress && pCodeNote->GetNote() == sNote)
{
if (sNote.empty())
- m_mCodeNotes.erase(pIter2);
+ m_vCodeNotes.erase(pCodeNote);
if (m_mOriginalCodeNotes.empty())
SetValue(ra::data::models::AssetModelBase::ChangesProperty, ra::etoi(ra::data::models::AssetChanges::None));
@@ -558,9 +588,11 @@ void CodeNotesModel::Serialize(ra::services::TextWriter& pWriter) const
pWriter.Write(ra::ByteAddressToString(pIter.first));
- const auto pNote = m_mCodeNotes.find(pIter.first);
- if (pNote != m_mCodeNotes.end())
- WriteQuoted(pWriter, pNote->second.GetNote());
+ CodeNoteModel searchNote;
+ searchNote.SetAddress(pIter.first);
+ const auto pCodeNote = std::lower_bound(m_vCodeNotes.begin(), m_vCodeNotes.end(), searchNote, CompareNoteAddresses);
+ if (pCodeNote != m_vCodeNotes.end() && pCodeNote->GetAddress() == pIter.first)
+ WriteQuoted(pWriter, pCodeNote->GetNote());
else
WriteQuoted(pWriter, "");
}
diff --git a/src/data/models/CodeNotesModel.hh b/src/data/models/CodeNotesModel.hh
index c4652bea..f8f85553 100644
--- a/src/data/models/CodeNotesModel.hh
+++ b/src/data/models/CodeNotesModel.hh
@@ -149,14 +149,14 @@ public:
///
/// Returns the number of known code notes (not including indirect notes).
///
- size_t CodeNoteCount() const noexcept { return m_mCodeNotes.size(); }
+ size_t CodeNoteCount() const noexcept { return m_vCodeNotes.size(); }
///
/// Gets the address of the first code note.
///
ra::ByteAddress FirstCodeNoteAddress() const noexcept
{
- return (m_mCodeNotes.size() == 0) ? 0U : m_mCodeNotes.begin()->first;
+ return (m_vCodeNotes.empty()) ? 0U : m_vCodeNotes.front().GetAddress();
}
///
@@ -195,7 +195,7 @@ protected:
void AddCodeNote(ra::ByteAddress nAddress, const std::string& sAuthor, const std::wstring& sNote);
void OnCodeNoteChanged(ra::ByteAddress nAddress, const std::wstring& sNewNote);
- std::map m_mCodeNotes;
+ std::vector m_vCodeNotes;
std::map> m_mOriginalCodeNotes;
std::map m_mPendingCodeNotes;
diff --git a/src/services/AchievementLogicSerializer.cpp b/src/services/AchievementLogicSerializer.cpp
new file mode 100644
index 00000000..d77f0ddf
--- /dev/null
+++ b/src/services/AchievementLogicSerializer.cpp
@@ -0,0 +1,354 @@
+#include "AchievementLogicSerializer.hh"
+
+#include "RA_Defs.h"
+#include "RA_StringUtils.h"
+
+#include "data\context\ConsoleContext.hh"
+
+#include "services\ServiceLocator.hh"
+
+namespace ra {
+namespace services {
+
+void AchievementLogicSerializer::AppendConditionType(std::string& sBuffer, TriggerConditionType nType)
+{
+ switch (nType)
+ {
+ case TriggerConditionType::Standard:
+ return;
+ case TriggerConditionType::PauseIf:
+ sBuffer.push_back('P');
+ break;
+ case TriggerConditionType::ResetIf:
+ sBuffer.push_back('R');
+ break;
+ case TriggerConditionType::AddSource:
+ sBuffer.push_back('A');
+ break;
+ case TriggerConditionType::SubSource:
+ sBuffer.push_back('B');
+ break;
+ case TriggerConditionType::AddHits:
+ sBuffer.push_back('C');
+ break;
+ case TriggerConditionType::SubHits:
+ sBuffer.push_back('D');
+ break;
+ case TriggerConditionType::Remember:
+ sBuffer.push_back('K');
+ break;
+ case TriggerConditionType::AndNext:
+ sBuffer.push_back('N');
+ break;
+ case TriggerConditionType::OrNext:
+ sBuffer.push_back('O');
+ break;
+ case TriggerConditionType::Measured:
+ sBuffer.push_back('M');
+ break;
+ case TriggerConditionType::MeasuredAsPercent:
+ sBuffer.push_back('G');
+ break;
+ case TriggerConditionType::MeasuredIf:
+ sBuffer.push_back('Q');
+ break;
+ case TriggerConditionType::AddAddress:
+ sBuffer.push_back('I');
+ break;
+ case TriggerConditionType::Trigger:
+ sBuffer.push_back('T');
+ break;
+ case TriggerConditionType::ResetNextIf:
+ sBuffer.push_back('Z');
+ break;
+ default:
+ assert(!"Unknown condition type");
+ break;
+ }
+
+ sBuffer.push_back(':');
+}
+
+void AchievementLogicSerializer::AppendOperand(std::string& sBuffer, TriggerOperandType nType, MemSize nSize, uint32_t nValue)
+{
+ switch (nType)
+ {
+ case TriggerOperandType::Address:
+ break;
+
+ case TriggerOperandType::Value:
+ sBuffer.append(std::to_string(nValue));
+ return;
+
+ case TriggerOperandType::Float:
+ sBuffer.push_back('f');
+ sBuffer.append(std::to_string(nValue));
+ sBuffer.push_back('.');
+ sBuffer.push_back('0');
+ return;
+
+ case TriggerOperandType::Delta:
+ sBuffer.push_back('d');
+ break;
+
+ case TriggerOperandType::Prior:
+ sBuffer.push_back('p');
+ break;
+
+ case TriggerOperandType::BCD:
+ sBuffer.push_back('b');
+ break;
+
+ case TriggerOperandType::Inverted:
+ sBuffer.push_back('~');
+ break;
+
+ case TriggerOperandType::Recall:
+ sBuffer.append("{recall}");
+ return;
+
+ default:
+ assert(!"Unknown operand type");
+ break;
+ }
+
+ sBuffer.push_back('0');
+ sBuffer.push_back('x');
+
+ switch (nSize)
+ {
+ case MemSize::BitCount: sBuffer.push_back('K'); break;
+ case MemSize::Bit_0: sBuffer.push_back('M'); break;
+ case MemSize::Bit_1: sBuffer.push_back('N'); break;
+ case MemSize::Bit_2: sBuffer.push_back('O'); break;
+ case MemSize::Bit_3: sBuffer.push_back('P'); break;
+ case MemSize::Bit_4: sBuffer.push_back('Q'); break;
+ case MemSize::Bit_5: sBuffer.push_back('R'); break;
+ case MemSize::Bit_6: sBuffer.push_back('S'); break;
+ case MemSize::Bit_7: sBuffer.push_back('T'); break;
+ case MemSize::Nibble_Lower: sBuffer.push_back('L'); break;
+ case MemSize::Nibble_Upper: sBuffer.push_back('U'); break;
+ case MemSize::EightBit: sBuffer.push_back('H'); break;
+ case MemSize::TwentyFourBit: sBuffer.push_back('W'); break;
+ case MemSize::ThirtyTwoBit: sBuffer.push_back('X'); break;
+ case MemSize::SixteenBit: sBuffer.push_back(' '); break;
+ case MemSize::ThirtyTwoBitBigEndian: sBuffer.push_back('G'); break;
+ case MemSize::SixteenBitBigEndian: sBuffer.push_back('I'); break;
+ case MemSize::TwentyFourBitBigEndian:sBuffer.push_back('J'); break;
+
+ case MemSize::Float:
+ sBuffer.pop_back();
+ sBuffer.pop_back();
+ sBuffer.push_back('f');
+ sBuffer.push_back('F');
+ break;
+
+ case MemSize::FloatBigEndian:
+ sBuffer.pop_back();
+ sBuffer.pop_back();
+ sBuffer.push_back('f');
+ sBuffer.push_back('B');
+ break;
+
+ case MemSize::Double32:
+ sBuffer.pop_back();
+ sBuffer.pop_back();
+ sBuffer.push_back('f');
+ sBuffer.push_back('H');
+ break;
+
+ case MemSize::Double32BigEndian:
+ sBuffer.pop_back();
+ sBuffer.pop_back();
+ sBuffer.push_back('f');
+ sBuffer.push_back('I');
+ break;
+
+ case MemSize::MBF32:
+ sBuffer.pop_back();
+ sBuffer.pop_back();
+ sBuffer.push_back('f');
+ sBuffer.push_back('M');
+ break;
+
+ case MemSize::MBF32LE:
+ sBuffer.pop_back();
+ sBuffer.pop_back();
+ sBuffer.push_back('f');
+ sBuffer.push_back('L');
+ break;
+
+ default:
+ assert(!"Unknown memory size");
+ break;
+ }
+
+ sBuffer.append(ra::ByteAddressToString(nValue), 2);
+}
+
+void AchievementLogicSerializer::AppendOperand(std::string& sBuffer, TriggerOperandType nType, MemSize, float fValue)
+{
+ switch (nType)
+ {
+ case TriggerOperandType::Value:
+ sBuffer.append(std::to_string(ra::to_unsigned(gsl::narrow_cast(fValue))));
+ break;
+
+ case TriggerOperandType::Float: {
+ std::string sFloat = std::to_string(fValue);
+ if (sFloat.find('.') != std::string::npos)
+ {
+ while (sFloat.back() == '0') // remove insignificant zeros
+ sFloat.pop_back();
+ if (sFloat.back() == '.') // if everything after the decimal was removed, add back a zero
+ sFloat.push_back('0');
+ }
+
+ sBuffer.push_back('f');
+ sBuffer.append(sFloat);
+ break;
+ }
+
+ default:
+ assert(!"Operand does not support float value");
+ break;
+ }
+}
+
+void AchievementLogicSerializer::AppendOperator(std::string& sBuffer, TriggerOperatorType nType)
+{
+ switch (nType)
+ {
+ case TriggerOperatorType::Equals:
+ sBuffer.push_back('=');
+ break;
+
+ case TriggerOperatorType::NotEquals:
+ sBuffer.push_back('!');
+ sBuffer.push_back('=');
+ break;
+
+ case TriggerOperatorType::LessThan:
+ sBuffer.push_back('<');
+ break;
+
+ case TriggerOperatorType::LessThanOrEqual:
+ sBuffer.push_back('<');
+ sBuffer.push_back('=');
+ break;
+
+ case TriggerOperatorType::GreaterThan:
+ sBuffer.push_back('>');
+ break;
+
+ case TriggerOperatorType::GreaterThanOrEqual:
+ sBuffer.push_back('>');
+ sBuffer.push_back('=');
+ break;
+
+ case TriggerOperatorType::Multiply:
+ sBuffer.push_back('*');
+ break;
+
+ case TriggerOperatorType::Divide:
+ sBuffer.push_back('/');
+ break;
+
+ case TriggerOperatorType::BitwiseAnd:
+ sBuffer.push_back('&');
+ break;
+
+ case TriggerOperatorType::BitwiseXor:
+ sBuffer.push_back('^');
+ break;
+
+ case TriggerOperatorType::Modulus:
+ sBuffer.push_back('%');
+ break;
+
+ case TriggerOperatorType::Add:
+ sBuffer.push_back('+');
+ break;
+
+ case TriggerOperatorType::Subtract:
+ sBuffer.push_back('-');
+ break;
+
+ default:
+ assert(!"Unknown comparison");
+ break;
+ }
+}
+
+void AchievementLogicSerializer::AppendHitTarget(std::string& sBuffer, uint32_t nTarget)
+{
+ if (nTarget > 0)
+ {
+ sBuffer.push_back('.');
+ sBuffer.append(std::to_string(nTarget));
+ sBuffer.push_back('.');
+ }
+}
+
+std::string AchievementLogicSerializer::BuildMemRefChain(const ra::data::models::CodeNoteModel& pRootNote,
+ const ra::data::models::CodeNoteModel& pLeafNote)
+{
+ std::vector vChain;
+ if (!pLeafNote.GetPointerChain(vChain, pRootNote))
+ return std::string();
+
+ MemSize nSize = MemSize::ThirtyTwoBit;
+ uint32_t nMask = 0xFFFFFFFF;
+ uint32_t nOffset = 0;
+
+ const auto& pConsoleContext = ra::services::ServiceLocator::Get();
+ if (!pConsoleContext.GetRealAddressConversion(&nSize, &nMask, &nOffset))
+ {
+ nSize = pRootNote.GetMemSize();
+ nMask = 0xFFFFFFFF;
+ nOffset = pConsoleContext.RealAddressFromByteAddress(0);
+ if (nOffset == 0xFFFFFFFF)
+ nOffset = 0;
+ }
+
+ std::string sBuffer;
+ ra::ByteAddress nPointerBase = 0;
+ for (size_t i = 0; i < vChain.size() - 1; ++i)
+ {
+ const auto* pNote = vChain.at(i);
+ Expects(pNote != nullptr);
+
+ AppendConditionType(sBuffer, ra::services::TriggerConditionType::AddAddress);
+ AppendOperand(sBuffer, ra::services::TriggerOperandType::Address, nSize, pNote->GetAddress());
+ nPointerBase = pNote->GetPointerAddress();
+
+ if (nOffset != 0)
+ {
+ AppendOperator(sBuffer, ra::services::TriggerOperatorType::Subtract);
+ AppendOperand(sBuffer, ra::services::TriggerOperandType::Value, MemSize::ThirtyTwoBit, nOffset);
+ }
+ else if (nMask != 0xFFFFFFFF)
+ {
+ const auto nBitsMask = ra::to_unsigned((1 << ra::data::MemSizeBits(nSize)) - 1);
+ if (nMask != nBitsMask)
+ {
+ AppendOperator(sBuffer, ra::services::TriggerOperatorType::BitwiseAnd);
+ AppendOperand(sBuffer, ra::services::TriggerOperandType::Value, MemSize::ThirtyTwoBit, nMask);
+ }
+ }
+
+ AppendConditionSeparator(sBuffer);
+ }
+
+ AppendConditionType(sBuffer, ra::services::TriggerConditionType::Measured);
+
+ nSize = pLeafNote.GetMemSize();
+ if (nSize == MemSize::Unknown)
+ nSize = MemSize::EightBit;
+ AppendOperand(sBuffer, ra::services::TriggerOperandType::Address, nSize, pLeafNote.GetAddress());
+
+ return sBuffer;
+}
+
+} // namespace services
+} // namespace ra
diff --git a/src/services/AchievementLogicSerializer.hh b/src/services/AchievementLogicSerializer.hh
new file mode 100644
index 00000000..da477f40
--- /dev/null
+++ b/src/services/AchievementLogicSerializer.hh
@@ -0,0 +1,93 @@
+#ifndef RA_ACHIEVEMENT_LOGIC_SERIALIZER_HH
+#define RA_ACHIEVEMENT_LOGIC_SERIALIZER_HH
+#pragma once
+
+#include "ra_fwd.h"
+
+#include "data\Types.hh"
+
+#include "data\models\CodeNoteModel.hh"
+
+namespace ra {
+namespace services {
+
+enum class TriggerConditionType : uint8_t
+{
+ Standard = RC_CONDITION_STANDARD,
+ PauseIf = RC_CONDITION_PAUSE_IF,
+ ResetIf = RC_CONDITION_RESET_IF,
+ AddSource = RC_CONDITION_ADD_SOURCE,
+ SubSource = RC_CONDITION_SUB_SOURCE,
+ AddHits = RC_CONDITION_ADD_HITS,
+ SubHits = RC_CONDITION_SUB_HITS,
+ Remember = RC_CONDITION_REMEMBER,
+ AndNext = RC_CONDITION_AND_NEXT,
+ Measured = RC_CONDITION_MEASURED,
+ AddAddress = RC_CONDITION_ADD_ADDRESS,
+ OrNext = RC_CONDITION_OR_NEXT,
+ Trigger = RC_CONDITION_TRIGGER,
+ MeasuredIf = RC_CONDITION_MEASURED_IF,
+ ResetNextIf = RC_CONDITION_RESET_NEXT_IF,
+ MeasuredAsPercent = 99
+};
+
+enum class TriggerOperandType : uint8_t
+{
+ Address = RC_OPERAND_ADDRESS, // compare to the value of a live address in RAM
+ Delta = RC_OPERAND_DELTA, // the value last known at this address.
+ Value = RC_OPERAND_CONST, // a 32 bit unsigned integer
+ Prior = RC_OPERAND_PRIOR, // the last differing value at this address.
+ BCD = RC_OPERAND_BCD, // Address, but decoded from binary-coded-decimal
+ Float = RC_OPERAND_FP, // a 32-bit floating point value
+ Inverted = RC_OPERAND_INVERTED, // the bitwise compliment of the current value at the address
+ Recall = RC_OPERAND_RECALL // the last value stored by RC_CONDITION_REMEMBER
+};
+
+enum class TriggerOperatorType : uint8_t
+{
+ Equals = RC_OPERATOR_EQ,
+ LessThan = RC_OPERATOR_LT,
+ LessThanOrEqual = RC_OPERATOR_LE,
+ GreaterThan = RC_OPERATOR_GT,
+ GreaterThanOrEqual = RC_OPERATOR_GE,
+ NotEquals = RC_OPERATOR_NE,
+ None = RC_OPERATOR_NONE,
+ Multiply = RC_OPERATOR_MULT,
+ Divide = RC_OPERATOR_DIV,
+ BitwiseAnd = RC_OPERATOR_AND,
+ BitwiseXor = RC_OPERATOR_XOR,
+ Modulus = RC_OPERATOR_MOD,
+ Add = RC_OPERATOR_ADD,
+ Subtract = RC_OPERATOR_SUB
+};
+
+class AchievementLogicSerializer
+{
+public:
+ GSL_SUPPRESS_F6 AchievementLogicSerializer() = default;
+ virtual ~AchievementLogicSerializer() = default;
+
+ AchievementLogicSerializer(const AchievementLogicSerializer&) noexcept = delete;
+ AchievementLogicSerializer& operator=(const AchievementLogicSerializer&) noexcept = delete;
+ AchievementLogicSerializer(AchievementLogicSerializer&&) noexcept = delete;
+ AchievementLogicSerializer& operator=(AchievementLogicSerializer&&) noexcept = delete;
+
+ static void AppendConditionSeparator(std::string& sBuffer) { sBuffer.push_back('_'); }
+
+ static void AppendConditionType(std::string& sBuffer, TriggerConditionType nType);
+
+ static void AppendOperand(std::string& sBuffer, TriggerOperandType nType, MemSize nSize, uint32_t nValue);
+ static void AppendOperand(std::string& sBuffer, TriggerOperandType nType, MemSize nSize, float fValue);
+
+ static void AppendOperator(std::string& sBuffer, TriggerOperatorType nType);
+
+ static void AppendHitTarget(std::string& sBuffer, uint32_t nTarget);
+
+ static std::string BuildMemRefChain(const ra::data::models::CodeNoteModel& pRootNote,
+ const ra::data::models::CodeNoteModel& pLeafNote);
+};
+
+} // namespace services
+} // namespace ra
+
+#endif // !RA_ACHIEVEMENT_LOGIC_SERIALIZER_HH
diff --git a/src/ui/viewmodels/TriggerConditionViewModel.cpp b/src/ui/viewmodels/TriggerConditionViewModel.cpp
index 1cab3981..5d454885 100644
--- a/src/ui/viewmodels/TriggerConditionViewModel.cpp
+++ b/src/ui/viewmodels/TriggerConditionViewModel.cpp
@@ -6,6 +6,7 @@
#include "data\context\GameContext.hh"
#include "data\models\TriggerValidation.hh"
+#include "services\AchievementLogicSerializer.hh"
#include "services\AchievementRuntime.hh"
#include "services\IConfiguration.hh"
#include "services\ServiceLocator.hh"
@@ -56,36 +57,18 @@ void TriggerConditionViewModel::SerializeAppend(std::string& sBuffer) const
const auto nType = GetType();
if (nType != TriggerConditionType::Standard)
{
- switch (nType)
+ if (nType == TriggerConditionType::Measured)
{
- case TriggerConditionType::ResetIf: sBuffer.push_back('R'); break;
- case TriggerConditionType::PauseIf: sBuffer.push_back('P'); break;
- case TriggerConditionType::AddSource: sBuffer.push_back('A'); break;
- case TriggerConditionType::SubSource: sBuffer.push_back('B'); break;
- case TriggerConditionType::AddHits: sBuffer.push_back('C'); break;
- case TriggerConditionType::SubHits: sBuffer.push_back('D'); break;
- case TriggerConditionType::Remember: sBuffer.push_back('K'); break;
- case TriggerConditionType::AndNext: sBuffer.push_back('N'); break;
- case TriggerConditionType::OrNext: sBuffer.push_back('O'); break;
- case TriggerConditionType::MeasuredIf: sBuffer.push_back('Q'); break;
- case TriggerConditionType::AddAddress: sBuffer.push_back('I'); break;
- case TriggerConditionType::Trigger: sBuffer.push_back('T'); break;
- case TriggerConditionType::ResetNextIf: sBuffer.push_back('Z'); break;
- case TriggerConditionType::Measured:
- {
- const auto* pTriggerViewModel = dynamic_cast(m_pTriggerViewModel);
- if (pTriggerViewModel != nullptr && pTriggerViewModel->IsMeasuredTrackedAsPercent())
- sBuffer.push_back('G');
- else
- sBuffer.push_back('M');
- break;
- }
- default:
- assert(!"Unknown condition type");
- break;
+ const auto* pTriggerViewModel = dynamic_cast(m_pTriggerViewModel);
+ if (pTriggerViewModel != nullptr && pTriggerViewModel->IsMeasuredTrackedAsPercent())
+ ra::services::AchievementLogicSerializer::AppendConditionType(sBuffer, TriggerConditionType::MeasuredAsPercent);
+ else
+ ra::services::AchievementLogicSerializer::AppendConditionType(sBuffer, TriggerConditionType::Measured);
+ }
+ else
+ {
+ ra::services::AchievementLogicSerializer::AppendConditionType(sBuffer, nType);
}
-
- sBuffer.push_back(':');
}
SerializeAppendOperand(sBuffer, GetSourceType(), GetSourceSize(), GetSourceValue());
@@ -93,232 +76,42 @@ void TriggerConditionViewModel::SerializeAppend(std::string& sBuffer) const
const auto nOperator = GetOperator();
if (nOperator != TriggerOperatorType::None)
{
- switch (nOperator)
- {
- case TriggerOperatorType::Equals:
- sBuffer.push_back('=');
- break;
-
- case TriggerOperatorType::NotEquals:
- sBuffer.push_back('!');
- sBuffer.push_back('=');
- break;
-
- case TriggerOperatorType::LessThan:
- sBuffer.push_back('<');
- break;
-
- case TriggerOperatorType::LessThanOrEqual:
- sBuffer.push_back('<');
- sBuffer.push_back('=');
- break;
-
- case TriggerOperatorType::GreaterThan:
- sBuffer.push_back('>');
- break;
-
- case TriggerOperatorType::GreaterThanOrEqual:
- sBuffer.push_back('>');
- sBuffer.push_back('=');
- break;
-
- case TriggerOperatorType::Multiply:
- sBuffer.push_back('*');
- break;
-
- case TriggerOperatorType::Divide:
- sBuffer.push_back('/');
- break;
-
- case TriggerOperatorType::BitwiseAnd:
- sBuffer.push_back('&');
- break;
-
- case TriggerOperatorType::BitwiseXor:
- sBuffer.push_back('^');
- break;
-
- case TriggerOperatorType::Modulus:
- sBuffer.push_back('%');
- break;
-
- case TriggerOperatorType::Add:
- sBuffer.push_back('+');
- break;
-
- case TriggerOperatorType::Subtract:
- sBuffer.push_back('-');
- break;
-
- default:
- assert(!"Unknown comparison");
- break;
- }
-
+ ra::services::AchievementLogicSerializer::AppendOperator(sBuffer, nOperator);
SerializeAppendOperand(sBuffer, GetTargetType(), GetTargetSize(), GetTargetValue());
}
if (GetValue(HasHitsProperty) && GetValue(CanEditHitsProperty))
- {
- const auto nRequiredHits = GetRequiredHits();
- if (nRequiredHits > 0)
- sBuffer.append(ra::StringPrintf(".%zu.", nRequiredHits));
- }
+ ra::services::AchievementLogicSerializer::AppendHitTarget(sBuffer, GetRequiredHits());
}
void TriggerConditionViewModel::SerializeAppendOperand(std::string& sBuffer, TriggerOperandType nType, MemSize nSize, const std::wstring& sValue) const
{
+ unsigned int nValue = 0;
+ float fValue = 0.0;
+ std::wstring sError;
+
switch (nType)
{
- case TriggerOperandType::Address:
- break;
-
case TriggerOperandType::Value:
- {
- unsigned int nValue = 0;
- std::wstring sError;
-
- if (sValue.length() > 2 && sValue.at(1) == 'x' && ra::ParseHex(sValue, 0xFFFFFFFF, nValue, sError))
- sBuffer.append(std::to_string(nValue));
- else if (ra::ParseUnsignedInt(sValue, 0xFFFFFFFF, nValue, sError))
- sBuffer.append(std::to_string(nValue));
- else
- sBuffer.push_back('0');
- return;
- }
-
- case TriggerOperandType::Float:
- {
- float fValue = 0.0;
- std::wstring sError;
+ if (!ra::ParseNumeric(sValue, nValue, sError))
+ nValue = 0;
- if (ra::ParseFloat(sValue, fValue, sError))
- {
- std::string sFloat = std::to_string(fValue);
- if (sFloat.find('.') != std::string::npos)
- {
- while (sFloat.back() == '0')
- sFloat.pop_back();
- if (sFloat.back() == '.')
- sFloat.push_back('0');
- }
-
- sBuffer.push_back('f');
- sBuffer.append(sFloat);
- }
- else
- {
- sBuffer.push_back('0');
- }
- return;
- }
-
- case TriggerOperandType::Delta:
- sBuffer.push_back('d');
+ ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, nType, nSize, nValue);
break;
- case TriggerOperandType::Prior:
- sBuffer.push_back('p');
- break;
-
- case TriggerOperandType::BCD:
- sBuffer.push_back('b');
- break;
+ case TriggerOperandType::Float:
+ if (!ra::ParseFloat(sValue, fValue, sError))
+ fValue = 0.0;
- case TriggerOperandType::Inverted:
- sBuffer.push_back('~');
+ ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, nType, nSize, fValue);
break;
- case TriggerOperandType::Recall:
- {
- sBuffer.append("{recall}");
- return;
- }
-
default:
- assert(!"Unknown operand type");
- break;
- }
-
- sBuffer.push_back('0');
- sBuffer.push_back('x');
-
- switch (nSize)
- {
- case MemSize::BitCount: sBuffer.push_back('K'); break;
- case MemSize::Bit_0: sBuffer.push_back('M'); break;
- case MemSize::Bit_1: sBuffer.push_back('N'); break;
- case MemSize::Bit_2: sBuffer.push_back('O'); break;
- case MemSize::Bit_3: sBuffer.push_back('P'); break;
- case MemSize::Bit_4: sBuffer.push_back('Q'); break;
- case MemSize::Bit_5: sBuffer.push_back('R'); break;
- case MemSize::Bit_6: sBuffer.push_back('S'); break;
- case MemSize::Bit_7: sBuffer.push_back('T'); break;
- case MemSize::Nibble_Lower: sBuffer.push_back('L'); break;
- case MemSize::Nibble_Upper: sBuffer.push_back('U'); break;
- case MemSize::EightBit: sBuffer.push_back('H'); break;
- case MemSize::TwentyFourBit: sBuffer.push_back('W'); break;
- case MemSize::ThirtyTwoBit: sBuffer.push_back('X'); break;
- case MemSize::SixteenBit: sBuffer.push_back(' '); break;
- case MemSize::ThirtyTwoBitBigEndian: sBuffer.push_back('G'); break;
- case MemSize::SixteenBitBigEndian: sBuffer.push_back('I'); break;
- case MemSize::TwentyFourBitBigEndian:sBuffer.push_back('J'); break;
-
- case MemSize::Float:
- sBuffer.pop_back();
- sBuffer.pop_back();
- sBuffer.push_back('f');
- sBuffer.push_back('F');
- break;
-
- case MemSize::FloatBigEndian:
- sBuffer.pop_back();
- sBuffer.pop_back();
- sBuffer.push_back('f');
- sBuffer.push_back('B');
- break;
-
- case MemSize::Double32:
- sBuffer.pop_back();
- sBuffer.pop_back();
- sBuffer.push_back('f');
- sBuffer.push_back('H');
- break;
-
- case MemSize::Double32BigEndian:
- sBuffer.pop_back();
- sBuffer.pop_back();
- sBuffer.push_back('f');
- sBuffer.push_back('I');
- break;
+ if (!ra::ParseHex(sValue, 0xFFFFFFFF, nValue, sError))
+ nValue = 0;
- case MemSize::MBF32:
- sBuffer.pop_back();
- sBuffer.pop_back();
- sBuffer.push_back('f');
- sBuffer.push_back('M');
+ ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, nType, nSize, nValue);
break;
-
- case MemSize::MBF32LE:
- sBuffer.pop_back();
- sBuffer.pop_back();
- sBuffer.push_back('f');
- sBuffer.push_back('L');
- break;
-
- default:
- assert(!"Unknown memory size");
- break;
- }
-
- {
- unsigned int nValue = 0;
- std::wstring sError;
-
- if (ra::ParseHex(sValue, 0xFFFFFFFF, nValue, sError))
- sBuffer.append(ra::ByteAddressToString(nValue), 2);
- else
- sBuffer.push_back('0');
}
}
diff --git a/src/ui/viewmodels/TriggerConditionViewModel.hh b/src/ui/viewmodels/TriggerConditionViewModel.hh
index 45202f61..9287144c 100644
--- a/src/ui/viewmodels/TriggerConditionViewModel.hh
+++ b/src/ui/viewmodels/TriggerConditionViewModel.hh
@@ -7,6 +7,8 @@
#include "data\Types.hh"
+#include "services\AchievementLogicSerializer.hh"
+
#include "ra_utility.h"
struct rc_condition_t;
@@ -15,54 +17,9 @@ namespace ra {
namespace ui {
namespace viewmodels {
-enum class TriggerConditionType : uint8_t
-{
- Standard = RC_CONDITION_STANDARD,
- PauseIf = RC_CONDITION_PAUSE_IF,
- ResetIf = RC_CONDITION_RESET_IF,
- AddSource = RC_CONDITION_ADD_SOURCE,
- SubSource = RC_CONDITION_SUB_SOURCE,
- AddHits = RC_CONDITION_ADD_HITS,
- SubHits = RC_CONDITION_SUB_HITS,
- Remember = RC_CONDITION_REMEMBER,
- AndNext = RC_CONDITION_AND_NEXT,
- Measured = RC_CONDITION_MEASURED,
- AddAddress = RC_CONDITION_ADD_ADDRESS,
- OrNext = RC_CONDITION_OR_NEXT,
- Trigger = RC_CONDITION_TRIGGER,
- MeasuredIf = RC_CONDITION_MEASURED_IF,
- ResetNextIf = RC_CONDITION_RESET_NEXT_IF
-};
-
-enum class TriggerOperandType : uint8_t
-{
- Address = RC_OPERAND_ADDRESS, // compare to the value of a live address in RAM
- Delta = RC_OPERAND_DELTA, // the value last known at this address.
- Value = RC_OPERAND_CONST, // a 32 bit unsigned integer
- Prior = RC_OPERAND_PRIOR, // the last differing value at this address.
- BCD = RC_OPERAND_BCD, // Address, but decoded from binary-coded-decimal
- Float = RC_OPERAND_FP, // a 32-bit floating point value
- Inverted = RC_OPERAND_INVERTED, // the bitwise compliment of the current value at the address
- Recall = RC_OPERAND_RECALL // the last value stored by RC_CONDITION_REMEMBER
-};
-
-enum class TriggerOperatorType : uint8_t
-{
- Equals = RC_OPERATOR_EQ,
- LessThan = RC_OPERATOR_LT,
- LessThanOrEqual = RC_OPERATOR_LE,
- GreaterThan = RC_OPERATOR_GT,
- GreaterThanOrEqual = RC_OPERATOR_GE,
- NotEquals = RC_OPERATOR_NE,
- None = RC_OPERATOR_NONE,
- Multiply = RC_OPERATOR_MULT,
- Divide = RC_OPERATOR_DIV,
- BitwiseAnd = RC_OPERATOR_AND,
- BitwiseXor = RC_OPERATOR_XOR,
- Modulus = RC_OPERATOR_MOD,
- Add = RC_OPERATOR_ADD,
- Subtract = RC_OPERATOR_SUB
-};
+using ra::services::TriggerConditionType;
+using ra::services::TriggerOperandType;
+using ra::services::TriggerOperatorType;
class TriggerConditionViewModel : public ViewModelBase
{
diff --git a/src/ui/viewmodels/TriggerViewModel.cpp b/src/ui/viewmodels/TriggerViewModel.cpp
index 4da274c0..7b0254fb 100644
--- a/src/ui/viewmodels/TriggerViewModel.cpp
+++ b/src/ui/viewmodels/TriggerViewModel.cpp
@@ -894,11 +894,7 @@ static unsigned int ParseNumeric(const std::wstring& sValue)
{
unsigned int nValue = 0;
std::wstring sError;
-
- if (sValue.length() > 2 && sValue.at(1) == 'x' && ra::ParseHex(sValue, 0xFFFFFFFF, nValue, sError))
- return nValue;
-
- if (ra::ParseUnsignedInt(sValue, 0xFFFFFFFF, nValue, sError))
+ if (ra::ParseNumeric(sValue, nValue, sError))
return nValue;
return 0;
diff --git a/tests/RA_Integration.Tests.vcxproj b/tests/RA_Integration.Tests.vcxproj
index aed899ea..5eb2d465 100644
--- a/tests/RA_Integration.Tests.vcxproj
+++ b/tests/RA_Integration.Tests.vcxproj
@@ -313,6 +313,7 @@
+
@@ -386,6 +387,7 @@
+
diff --git a/tests/RA_Integration.Tests.vcxproj.filters b/tests/RA_Integration.Tests.vcxproj.filters
index 2a89fb56..7b2e0833 100644
--- a/tests/RA_Integration.Tests.vcxproj.filters
+++ b/tests/RA_Integration.Tests.vcxproj.filters
@@ -474,6 +474,12 @@
Tests\Data\Models
+
+ Code
+
+
+ Tests\Services
+
diff --git a/tests/services/AchievementLogicSerializer_Tests.cpp b/tests/services/AchievementLogicSerializer_Tests.cpp
new file mode 100644
index 00000000..8e459949
--- /dev/null
+++ b/tests/services/AchievementLogicSerializer_Tests.cpp
@@ -0,0 +1,124 @@
+#include "CppUnitTest.h"
+
+#include "services\AchievementLogicSerializer.hh"
+
+#include "tests\RA_UnitTestHelpers.h"
+#include "tests\data\DataAsserts.hh"
+
+#include "tests\mocks\MockConsoleContext.hh"
+#include "tests\mocks\MockEmulatorContext.hh"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+namespace ra {
+namespace services {
+namespace tests {
+
+TEST_CLASS(AchievementLogicSerializer_Tests)
+{
+public:
+ TEST_METHOD(TestBuildMemRefChain)
+ {
+ ra::data::context::mocks::MockConsoleContext mockConsoleContext;
+ ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+
+ ra::data::models::CodeNoteModel note;
+ const std::wstring sNote =
+ L"Pointer [32bit]\n"
+ L"+0x428 | Obj1 pointer\n"
+ L"++0x24C | [16-bit] State\n"
+ L"-- Increments\n"
+ L"+0x438 | Obj2 pointer\n"
+ L"++0x08 | Flag\n"
+ L"-- b0=quest1 complete\n"
+ L"-- b1=quest2 complete\n"
+ L"+0x448 | [32-bit BE] Not-nested number";
+ note.SetNote(sNote);
+ note.SetAddress(0x1234);
+ note.UpdateRawPointerValue(0x1234, mockEmulatorContext, nullptr);
+
+ const auto* note2 = note.GetPointerNoteAtOffset(0x438);
+ Assert::IsNotNull(note2);
+ const auto* note3 = note2->GetPointerNoteAtOffset(0x08);
+ Assert::IsNotNull(note3);
+
+ std::string sSerialized = AchievementLogicSerializer::BuildMemRefChain(note, *note3);
+ Assert::AreEqual(std::string("I:0xX1234_I:0xX0438_M:0xH0008"), sSerialized);
+ }
+
+ TEST_METHOD(TestBuildMemRefChain24Bit)
+ {
+ ra::data::context::mocks::MockConsoleContext mockConsoleContext;
+ ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+ mockConsoleContext.SetId(ConsoleID::PlayStation); // 24-bit read
+
+ ra::data::models::CodeNoteModel note;
+ const std::wstring sNote =
+ L"Pointer [32bit]\n"
+ L"+0x428 | Obj1 pointer\n"
+ L"++0x24C | [16-bit] State";
+ note.SetNote(sNote);
+ note.SetAddress(0x1234);
+ note.UpdateRawPointerValue(0x1234, mockEmulatorContext, nullptr);
+
+ const auto* note2 = note.GetPointerNoteAtOffset(0x428);
+ Assert::IsNotNull(note2);
+ const auto* note3 = note2->GetPointerNoteAtOffset(0x24C);
+ Assert::IsNotNull(note3);
+
+ std::string sSerialized = AchievementLogicSerializer::BuildMemRefChain(note, *note3);
+ Assert::AreEqual(std::string("I:0xW1234_I:0xW0428_M:0x 024c"), sSerialized);
+ }
+
+ TEST_METHOD(TestBuildMemRefChain25Bit)
+ {
+ ra::data::context::mocks::MockConsoleContext mockConsoleContext;
+ ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+ mockConsoleContext.SetId(ConsoleID::PSP); // 25-bit read
+
+ ra::data::models::CodeNoteModel note;
+ const std::wstring sNote =
+ L"Pointer [32bit]\n"
+ L"+0x428 | Obj1 pointer\n"
+ L"++0x24C | [16-bit] State";
+ note.SetNote(sNote);
+ note.SetAddress(0x1234);
+ note.UpdateRawPointerValue(0x1234, mockEmulatorContext, nullptr);
+
+ const auto* note2 = note.GetPointerNoteAtOffset(0x428);
+ Assert::IsNotNull(note2);
+ const auto* note3 = note2->GetPointerNoteAtOffset(0x24C);
+ Assert::IsNotNull(note3);
+
+ std::string sSerialized = AchievementLogicSerializer::BuildMemRefChain(note, *note3);
+ Assert::AreEqual(std::string("I:0xX1234&33554431_I:0xX0428&33554431_M:0x 024c"), sSerialized);
+ }
+
+ TEST_METHOD(TestBuildMemRefChain25BitBE)
+ {
+ ra::data::context::mocks::MockConsoleContext mockConsoleContext;
+ ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+ mockConsoleContext.SetId(ConsoleID::GameCube); // 25-bit BE read
+
+ ra::data::models::CodeNoteModel note;
+ const std::wstring sNote =
+ L"Pointer [32bit]\n"
+ L"+0x428 | Obj1 pointer\n"
+ L"++0x24C | [16-bit] State";
+ note.SetNote(sNote);
+ note.SetAddress(0x1234);
+ note.UpdateRawPointerValue(0x1234, mockEmulatorContext, nullptr);
+
+ const auto* note2 = note.GetPointerNoteAtOffset(0x428);
+ Assert::IsNotNull(note2);
+ const auto* note3 = note2->GetPointerNoteAtOffset(0x24C);
+ Assert::IsNotNull(note3);
+
+ std::string sSerialized = AchievementLogicSerializer::BuildMemRefChain(note, *note3);
+ Assert::AreEqual(std::string("I:0xG1234&33554431_I:0xG0428&33554431_M:0x 024c"), sSerialized);
+ }
+};
+
+} // namespace tests
+} // namespace services
+} // namespace ra