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