From 0c1ca72858075b683876912ced4343dc42e6cc29 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Fri, 8 Nov 2024 17:31:59 -0700 Subject: [PATCH 1/9] add PointerInspectorViewModel --- src/RA_Integration.vcxproj | 2 + src/RA_Integration.vcxproj.filters | 6 + src/data/models/CodeNoteModel.cpp | 14 +- src/data/models/CodeNoteModel.hh | 1 + src/data/models/CodeNotesModel.hh | 7 + .../viewmodels/PointerInspectorViewModel.cpp | 107 +++++++++++++++ .../viewmodels/PointerInspectorViewModel.hh | 94 +++++++++++++ tests/RA_Integration.Tests.vcxproj | 2 + tests/RA_Integration.Tests.vcxproj.filters | 6 + .../PointerInspectorViewModel_Tests.cpp | 129 ++++++++++++++++++ 10 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 src/ui/viewmodels/PointerInspectorViewModel.cpp create mode 100644 src/ui/viewmodels/PointerInspectorViewModel.hh create mode 100644 tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp diff --git a/src/RA_Integration.vcxproj b/src/RA_Integration.vcxproj index 5744e6ac9..88a3d5a53 100644 --- a/src/RA_Integration.vcxproj +++ b/src/RA_Integration.vcxproj @@ -133,6 +133,7 @@ + @@ -307,6 +308,7 @@ + diff --git a/src/RA_Integration.vcxproj.filters b/src/RA_Integration.vcxproj.filters index 3bea2687f..31e3e9dc8 100644 --- a/src/RA_Integration.vcxproj.filters +++ b/src/RA_Integration.vcxproj.filters @@ -411,6 +411,9 @@ Data\Models + + UI\ViewModels + @@ -977,6 +980,9 @@ Data\Models + + UI\ViewModels + diff --git a/src/data/models/CodeNoteModel.cpp b/src/data/models/CodeNoteModel.cpp index 43eba9c74..e3e47efcf 100644 --- a/src/data/models/CodeNoteModel.cpp +++ b/src/data/models/CodeNoteModel.cpp @@ -262,6 +262,18 @@ bool CodeNoteModel::GetNextAddress(ra::ByteAddress nAfterAddress, ra::ByteAddres return bResult; } +std::wstring CodeNoteModel::GetPrimaryNote() const noexcept +{ + if (m_pPointerData != nullptr) + { + const auto nIndex = m_sNote.find(L"\n+"); + if (nIndex != std::wstring::npos) + return m_sNote.substr(0, nIndex); + } + + return m_sNote; +} + void CodeNoteModel::SetNote(const std::wstring& sNote) { if (m_sNote == sNote) @@ -284,7 +296,7 @@ void CodeNoteModel::SetNote(const std::wstring& sNote) // if there are any lines starting with a plus sign, extract the indirect code notes nIndex = sNote.find(L"\n+"); - if (nIndex != std::string::npos) + if (nIndex != std::wstring::npos) ProcessIndirectNotes(sNote, nIndex); } } diff --git a/src/data/models/CodeNoteModel.hh b/src/data/models/CodeNoteModel.hh index 80e522ca4..1aa135b7b 100644 --- a/src/data/models/CodeNoteModel.hh +++ b/src/data/models/CodeNoteModel.hh @@ -41,6 +41,7 @@ public: bool GetPreviousAddress(ra::ByteAddress nBeforeAddress, ra::ByteAddress& nPreviousAddress) const; bool GetNextAddress(ra::ByteAddress nAfterAddress, ra::ByteAddress& nNextAddress) const; + std::wstring GetPrimaryNote() const noexcept; void EnumeratePointerNotes(ra::ByteAddress nPointerAddress, std::function fCallback) const; void EnumeratePointerNotes(std::function fCallback) const; diff --git a/src/data/models/CodeNotesModel.hh b/src/data/models/CodeNotesModel.hh index c4652bea7..2c1330981 100644 --- a/src/data/models/CodeNotesModel.hh +++ b/src/data/models/CodeNotesModel.hh @@ -100,6 +100,13 @@ public: return (pNote == nullptr) ? MemSize::Unknown : pNote->GetMemSize(); } + /// + /// Returns the model for the note associated with the specified address. + /// + /// The address to look up. + /// The note associated to the address, nullptr if no note is associated to the address. + const CodeNoteModel* FindCodeNoteModel(ra::ByteAddress nAddress) const; + /// /// Returns the address of the real code note from which an indirect code note was derived. /// diff --git a/src/ui/viewmodels/PointerInspectorViewModel.cpp b/src/ui/viewmodels/PointerInspectorViewModel.cpp new file mode 100644 index 000000000..ddba5b5e8 --- /dev/null +++ b/src/ui/viewmodels/PointerInspectorViewModel.cpp @@ -0,0 +1,107 @@ +#include "PointerInspectorViewModel.hh" + +#include "RA_Defs.h" + +#include "services\ServiceLocator.hh" + +namespace ra { +namespace ui { +namespace viewmodels { + +const IntModelProperty PointerInspectorViewModel::CurrentAddressProperty("PointerInspectorViewModel", "CurrentAddress", 0); +const StringModelProperty PointerInspectorViewModel::CurrentAddressTextProperty("PointerInspectorViewModel", "CurrentAddressText", L"0x0000"); +const StringModelProperty PointerInspectorViewModel::CurrentAddressNoteProperty("PointerInspectorViewModel", "CurrentAddressNote", L""); + +PointerInspectorViewModel::PointerInspectorViewModel() +{ + SetWindowTitle(L"Pointer Inspector"); +} + +void PointerInspectorViewModel::InitializeNotifyTargets() +{ + auto& pGameContext = ra::services::ServiceLocator::GetMutable(); + pGameContext.AddNotifyTarget(*this); +} + +void PointerInspectorViewModel::DoFrame() +{ +} + +void PointerInspectorViewModel::OnValueChanged(const IntModelProperty::ChangeArgs& args) +{ + if (args.Property == CurrentAddressProperty && !m_bSyncingAddress) + { + const auto nAddress = static_cast(args.tNewValue); + + m_bSyncingAddress = true; + SetCurrentAddressText(ra::Widen(ra::ByteAddressToString(nAddress))); + m_bSyncingAddress = false; + + OnCurrentAddressChanged(nAddress); + } + + WindowViewModelBase::OnValueChanged(args); +} + +void PointerInspectorViewModel::OnValueChanged(const StringModelProperty::ChangeArgs& args) +{ + if (args.Property == CurrentAddressTextProperty && !m_bSyncingAddress) + { + const auto nAddress = ra::ByteAddressFromString(ra::Narrow(args.tNewValue)); + + // ignore change event for current address so text field is not modified + m_bSyncingAddress = true; + SetCurrentAddress(nAddress); + m_bSyncingAddress = false; + + OnCurrentAddressChanged(nAddress); + } + + WindowViewModelBase::OnValueChanged(args); +} + +void PointerInspectorViewModel::OnActiveGameChanged() +{ + const auto& pGameContext = ra::services::ServiceLocator::Get(); + if (pGameContext.GameId() == 0) + SetCurrentAddress(0); + else + OnCurrentAddressChanged(GetCurrentAddress()); +} + +void PointerInspectorViewModel::OnCodeNoteChanged(ra::ByteAddress nAddress, const std::wstring& sNewNote) +{ + if (nAddress == GetCurrentAddress()) + OnCurrentAddressChanged(nAddress); // not really, but causes the note to be reloaded +} + +void PointerInspectorViewModel::OnCurrentAddressChanged(ra::ByteAddress nNewAddress) +{ + const auto& pGameContext = ra::services::ServiceLocator::Get(); + const auto* pCodeNotes = pGameContext.Assets().FindCodeNotes(); + if (pCodeNotes != nullptr) + { + const auto* pNote = pCodeNotes->FindCodeNoteModel(nNewAddress); + if (pNote) + LoadNote(nNewAddress, pNote); + else + LoadNote(nNewAddress, nullptr); + } +} + +void PointerInspectorViewModel::LoadNote(ra::ByteAddress nAddress, const ra::data::models::CodeNoteModel* pNote) +{ + if (pNote == nullptr) + { + SetCurrentAddressNote(L""); + // TODO: clear list + return; + } + + SetCurrentAddressNote(pNote->GetPrimaryNote()); + // TODO: update list +} + +} // namespace viewmodels +} // namespace ui +} // namespace ra diff --git a/src/ui/viewmodels/PointerInspectorViewModel.hh b/src/ui/viewmodels/PointerInspectorViewModel.hh new file mode 100644 index 000000000..d3cdea977 --- /dev/null +++ b/src/ui/viewmodels/PointerInspectorViewModel.hh @@ -0,0 +1,94 @@ +#ifndef RA_UI_POINTERINSPECTORVIEWMODEL_H +#define RA_UI_POINTERINSPECTORVIEWMODEL_H +#pragma once + +#include "data\Types.hh" +#include "data\context\GameContext.hh" + +#include "ui\WindowViewModelBase.hh" + +namespace ra { +namespace ui { +namespace viewmodels { + +class PointerInspectorViewModel : public WindowViewModelBase, + protected ra::data::context::GameContext::NotifyTarget +{ +public: + GSL_SUPPRESS_F6 PointerInspectorViewModel(); + ~PointerInspectorViewModel() noexcept = default; + + PointerInspectorViewModel(const PointerInspectorViewModel&) noexcept = delete; + PointerInspectorViewModel& operator=(const PointerInspectorViewModel&) noexcept = delete; + PointerInspectorViewModel(PointerInspectorViewModel&&) noexcept = delete; + PointerInspectorViewModel& operator=(PointerInspectorViewModel&&) noexcept = delete; + + void InitializeNotifyTargets(); + + void DoFrame(); + + /// + /// The for the current address. + /// + static const IntModelProperty CurrentAddressProperty; + + /// + /// Gets the current address. + /// + ra::ByteAddress GetCurrentAddress() const { return GetValue(CurrentAddressProperty); } + + /// + /// Sets the current address. + /// + void SetCurrentAddress(const ra::ByteAddress nValue) { SetValue(CurrentAddressProperty, nValue); } + + /// + /// The for the current address as a string. + /// + static const StringModelProperty CurrentAddressTextProperty; + + /// + /// Gets the current address as a string. + /// + const std::wstring& GetCurrentAddressText() const { return GetValue(CurrentAddressTextProperty); } + + /// + /// Sets the current address as a string. + /// + void SetCurrentAddressText(const std::wstring& sValue) { SetValue(CurrentAddressTextProperty, sValue); } + + /// + /// The for the current address's note. + /// + static const StringModelProperty CurrentAddressNoteProperty; + + /// + /// Gets the current address's note. + /// + const std::wstring& GetCurrentAddressNote() const { return GetValue(CurrentAddressNoteProperty); } + + /// + /// Sets the current address's note. + /// + void SetCurrentAddressNote(const std::wstring& sValue) { SetValue(CurrentAddressNoteProperty, sValue); } + +protected: + void OnValueChanged(const IntModelProperty::ChangeArgs& args) override; + void OnValueChanged(const StringModelProperty::ChangeArgs& args) override; + + // GameContext::NotifyTarget + void OnActiveGameChanged() override; + void OnCodeNoteChanged(ra::ByteAddress nAddress, const std::wstring& sNewNote) override; + +private: + void OnCurrentAddressChanged(ra::ByteAddress nNewAddress); + void LoadNote(ra::ByteAddress nAddress, const ra::data::models::CodeNoteModel* pNote); + + bool m_bSyncingAddress = false; +}; + +} // namespace viewmodels +} // namespace ui +} // namespace ra + +#endif !RA_UI_POINTERINSPECTORVIEWMODEL_H diff --git a/tests/RA_Integration.Tests.vcxproj b/tests/RA_Integration.Tests.vcxproj index aed899ea5..8998ae6dd 100644 --- a/tests/RA_Integration.Tests.vcxproj +++ b/tests/RA_Integration.Tests.vcxproj @@ -348,6 +348,7 @@ + @@ -423,6 +424,7 @@ + diff --git a/tests/RA_Integration.Tests.vcxproj.filters b/tests/RA_Integration.Tests.vcxproj.filters index 2a89fb56f..0c0e3b992 100644 --- a/tests/RA_Integration.Tests.vcxproj.filters +++ b/tests/RA_Integration.Tests.vcxproj.filters @@ -474,6 +474,12 @@ Tests\Data\Models + + Tests\UI\ViewModels + + + Code + diff --git a/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp new file mode 100644 index 000000000..b9d47f418 --- /dev/null +++ b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp @@ -0,0 +1,129 @@ +#include "CppUnitTest.h" + +#include "api\DeleteCodeNote.hh" +#include "api\UpdateCodeNote.hh" + +#include "ui\viewmodels\PointerInspectorViewModel.hh" + +#include "tests\ui\UIAsserts.hh" +#include "tests\RA_UnitTestHelpers.h" + +#include "tests\mocks\MockConfiguration.hh" +#include "tests\mocks\MockConsoleContext.hh" +#include "tests\mocks\MockDesktop.hh" +#include "tests\mocks\MockEmulatorContext.hh" +#include "tests\mocks\MockGameContext.hh" +#include "tests\mocks\MockLocalStorage.hh" +#include "tests\mocks\MockUserContext.hh" +#include "tests\mocks\MockServer.hh" +#include "tests\mocks\MockThreadPool.hh" +#include "tests\mocks\MockWindowManager.hh" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace ra { +namespace ui { +namespace viewmodels { +namespace tests { + +TEST_CLASS(PointerInspectorViewModel_Tests) +{ +private: + class PointerInspectorViewModelHarness : public PointerInspectorViewModel + { + public: + ra::data::context::mocks::MockGameContext mockGameContext; + ra::data::context::mocks::MockUserContext mockUserContext; + ra::data::context::mocks::MockEmulatorContext mockEmulatorContext; + + std::array memory{}; + + GSL_SUPPRESS_F6 PointerInspectorViewModelHarness() + { + InitializeNotifyTargets(); + + for (size_t i = 0; i < memory.size(); ++i) + memory.at(i) = gsl::narrow_cast(i); + + mockEmulatorContext.MockMemory(memory); + + mockUserContext.SetUsername("Author"); + + mockGameContext.InitializeCodeNotes(); + } + + ~PointerInspectorViewModelHarness() = default; + + PointerInspectorViewModelHarness(const PointerInspectorViewModelHarness&) noexcept = delete; + PointerInspectorViewModelHarness& operator=(const PointerInspectorViewModelHarness&) noexcept = delete; + PointerInspectorViewModelHarness(PointerInspectorViewModelHarness&&) noexcept = delete; + PointerInspectorViewModelHarness& operator=(PointerInspectorViewModelHarness&&) noexcept = delete; + + const std::wstring* FindCodeNote(ra::ByteAddress nAddress) const + { + const auto* pCodeNotes = mockGameContext.Assets().FindCodeNotes(); + return (pCodeNotes != nullptr) ? pCodeNotes->FindCodeNote(nAddress) : nullptr; + } + }; + + +public: + TEST_METHOD(TestInitialValues) + { + PointerInspectorViewModelHarness inspector; + + Assert::AreEqual({ 0U }, inspector.GetCurrentAddress()); + Assert::AreEqual(std::wstring(L"0x0000"), inspector.GetCurrentAddressText()); + Assert::AreEqual(std::wstring(), inspector.GetCurrentAddressNote()); + } + + TEST_METHOD(TestSetCurrentAddress) + { + PointerInspectorViewModelHarness inspector; + inspector.mockGameContext.SetGameId(1); + inspector.mockGameContext.NotifyActiveGameChanged(); // enable note support + + inspector.SetCurrentAddress({ 3U }); + + Assert::AreEqual({ 3U }, inspector.GetCurrentAddress()); + Assert::AreEqual(std::wstring(L"0x0003"), inspector.GetCurrentAddressText()); + Assert::AreEqual(std::wstring(), inspector.GetCurrentAddressNote()); + } + + TEST_METHOD(TestSetCurrentAddressText) + { + PointerInspectorViewModelHarness inspector; + inspector.mockGameContext.SetGameId(1); + inspector.mockGameContext.NotifyActiveGameChanged(); // enable note support + + inspector.SetCurrentAddressText(L"3"); + + Assert::AreEqual({ 3U }, inspector.GetCurrentAddress()); + Assert::AreEqual(std::wstring(L"3"), inspector.GetCurrentAddressText()); /* don't update text when user types in partial address */ + Assert::AreEqual(std::wstring(), inspector.GetCurrentAddressNote()); + } + + TEST_METHOD(TestSetCurrentAddressWithNote) + { + PointerInspectorViewModelHarness inspector; + inspector.mockGameContext.SetGameId(1); + inspector.mockGameContext.NotifyActiveGameChanged(); // enable note support + + inspector.SetCurrentAddress({ 3U }); + inspector.mockGameContext.Assets().FindCodeNotes()->SetServerCodeNote({3U}, L"Note on 3"); + inspector.mockGameContext.Assets().FindCodeNotes()->SetCodeNote({3U}, L"Note on 3"); + + Assert::AreEqual({ 3U }, inspector.GetCurrentAddress()); + Assert::AreEqual(std::wstring(L"0x0003"), inspector.GetCurrentAddressText()); + Assert::AreEqual(std::wstring(L"Note on 3"), inspector.GetCurrentAddressNote()); + + inspector.mockGameContext.Assets().FindCodeNotes()->SetCodeNote({3U}, L"Modified Note on 3"); + + Assert::AreEqual(std::wstring(L"Modified Note on 3"), inspector.GetCurrentAddressNote()); + } +}; + +} // namespace tests +} // namespace viewmodels +} // namespace ui +} // namespace ra From 076de616583090eb528b155f5c5652160212b6c6 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Fri, 8 Nov 2024 19:36:55 -0700 Subject: [PATCH 2/9] add fields for indirect data --- src/RA_StringUtils.h | 22 ++-- src/data/ModelCollectionBase.hh | 17 +++ .../viewmodels/MemoryBookmarksViewModel.cpp | 77 ++++++------- src/ui/viewmodels/MemoryBookmarksViewModel.hh | 2 + .../viewmodels/PointerInspectorViewModel.cpp | 103 +++++++++++++++--- .../viewmodels/PointerInspectorViewModel.hh | 47 ++++++-- tests/RA_StringUtils_Tests.cpp | 32 +++--- tests/RA_UnitTestHelpers.h | 14 +++ .../PointerInspectorViewModel_Tests.cpp | 48 +++++++- 9 files changed, 271 insertions(+), 91 deletions(-) diff --git a/src/RA_StringUtils.h b/src/RA_StringUtils.h index e8301a773..3d1de5f95 100644 --- a/src/RA_StringUtils.h +++ b/src/RA_StringUtils.h @@ -45,7 +45,7 @@ std::wstring& NormalizeLineEndings(_Inout_ std::wstring& str); // ----- ToString ----- template -_NODISCARD inline const std::string ToString(_In_ const T& value) +_NODISCARD inline const std::string ToAString(_In_ const T& value) { if constexpr (std::is_arithmetic_v) { @@ -74,44 +74,44 @@ _NODISCARD inline const std::string ToString(_In_ const T& value) } template<> -_NODISCARD inline const std::string ToString(_In_ const std::string& value) +_NODISCARD inline const std::string ToAString(_In_ const std::string& value) { return value; } template<> -_NODISCARD inline const std::string ToString(_In_ const std::wstring& value) +_NODISCARD inline const std::string ToAString(_In_ const std::wstring& value) { return ra::Narrow(value); } template<> -_NODISCARD inline const std::string ToString(_In_ const wchar_t* const& value) +_NODISCARD inline const std::string ToAString(_In_ const wchar_t* const& value) { return ra::Narrow(value); } template<> -_NODISCARD inline const std::string ToString(_In_ const char* const& value) +_NODISCARD inline const std::string ToAString(_In_ const char* const& value) { return std::string(value); } template<> -_NODISCARD inline const std::string ToString(_In_ char* const& value) +_NODISCARD inline const std::string ToAString(_In_ char* const& value) { return std::string(value); } template<> -_NODISCARD inline const std::string ToString(_In_ const char& value) +_NODISCARD inline const std::string ToAString(_In_ const char& value) { return std::string(1, value); } // literal strings can't be passed by reference, so won't call the templated methods -_NODISCARD inline const std::string ToString(_In_ const char* value) { return std::string(value); } -_NODISCARD inline const std::string ToString(_In_ const wchar_t* value) { return ra::Narrow(value); } +_NODISCARD inline const std::string ToAString(_In_ const char* value) { return std::string(value); } +_NODISCARD inline const std::string ToAString(_In_ const wchar_t* value) { return ra::Narrow(value); } // ----- ToWString ----- @@ -200,7 +200,7 @@ class StringBuilder if (m_bPrepareWide) m_vPending.emplace_back(std::wstring{ra::ToWString(arg)}); else - m_vPending.emplace_back(std::string{ra::ToString(arg)}); + m_vPending.emplace_back(std::string{ra::ToAString(arg)}); } template<> @@ -441,7 +441,7 @@ class StringBuilder const char c = sFormat.back(); sFormat.pop_back(); // remove 's'/'x' sFormat.pop_back(); // remove '*' - sFormat.append(ra::ToString(value)); + sFormat.append(ra::ToAString(value)); sFormat.push_back(c); // replace 's'/'x' if constexpr (sizeof...(args) > 0) diff --git a/src/data/ModelCollectionBase.hh b/src/data/ModelCollectionBase.hh index 7ff6cd794..fe9a575d8 100644 --- a/src/data/ModelCollectionBase.hh +++ b/src/data/ModelCollectionBase.hh @@ -180,6 +180,23 @@ public: return -1; } + /// + /// Finds the index of the first item where the specified property has the specified value. + /// + /// The property to query. + /// The value to find. + /// Index of the first matching item, -1 if not found. + gsl::index FindItemIndex(const StringModelProperty& pProperty, const std::wstring& sValue) const + { + for (gsl::index nIndex = 0; nIndex < gsl::narrow(m_nSize); ++nIndex) + { + if (m_vItems.at(nIndex)->GetValue(pProperty) == sValue) + return nIndex; + } + + return -1; + } + /// /// Calls the OnBeginModelCollectionUpdate method of any attached NotifyTargets. /// diff --git a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp index 413fa559a..1b4b77662 100644 --- a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp +++ b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp @@ -586,55 +586,58 @@ void MemoryBookmarksViewModel::DoFrame() for (gsl::index nIndex = 0; ra::to_unsigned(nIndex) < m_vBookmarks.Count(); ++nIndex) { auto& pBookmark = *m_vBookmarks.GetItemAt(nIndex); - if (pBookmark.MemoryChanged()) + UpdateBookmark(pBookmark, pEmulatorContext); + } + + pEmulatorContext.AddNotifyTarget(*this); +} + +void MemoryBookmarksViewModel::UpdateBookmark(MemoryBookmarksViewModel::MemoryBookmarkViewModel& pBookmark, ra::data::context::EmulatorContext& pEmulatorContext) +{ + if (pBookmark.MemoryChanged()) + { + if (pBookmark.GetBehavior() == BookmarkBehavior::PauseOnChange) { - if (pBookmark.GetBehavior() == BookmarkBehavior::PauseOnChange) - { - pBookmark.SetRowColor(ra::ui::Color(0xFFFFC0C0)); + pBookmark.SetRowColor(ra::ui::Color(0xFFFFC0C0)); - const auto nSizeIndex = m_vSizes.FindItemIndex(LookupItemViewModel::IdProperty, ra::etoi(pBookmark.GetSize())); - Expects(nSizeIndex >= 0); + const auto nSizeIndex = m_vSizes.FindItemIndex(LookupItemViewModel::IdProperty, ra::etoi(pBookmark.GetSize())); + Expects(nSizeIndex >= 0); - auto sMessage = ra::StringPrintf(L"%s %s", - m_vSizes.GetItemAt(nSizeIndex)->GetLabel(), - ra::ByteAddressToString(pBookmark.GetAddress())); + auto sMessage = ra::StringPrintf(L"%s %s", m_vSizes.GetItemAt(nSizeIndex)->GetLabel(), ra::ByteAddressToString(pBookmark.GetAddress())); - // remove leading space of " 8-bit" - if (isspace(sMessage.at(0))) - sMessage.erase(0, 1); + // remove leading space of " 8-bit" + if (isspace(sMessage.at(0))) + sMessage.erase(0, 1); - const auto& pDescription = pBookmark.GetDescription(); - if (!pDescription.empty()) + const auto& pDescription = pBookmark.GetDescription(); + if (!pDescription.empty()) + { + auto nDescriptionLength = pDescription.find(L'\n'); + if (nDescriptionLength == std::string::npos) { - auto nDescriptionLength = pDescription.find(L'\n'); - if (nDescriptionLength == std::string::npos) + if (pDescription.length() < 40) { - if (pDescription.length() < 40) - { - nDescriptionLength = pDescription.length(); - } - else - { - nDescriptionLength = pDescription.find_last_of(L' ', 40); - if (nDescriptionLength == std::string::npos) - nDescriptionLength = 40; - } + nDescriptionLength = pDescription.length(); + } + else + { + nDescriptionLength = pDescription.find_last_of(L' ', 40); + if (nDescriptionLength == std::string::npos) + nDescriptionLength = 40; } - sMessage.append(L": "); - sMessage.append(pDescription, 0, nDescriptionLength); } - - auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable(); - pFrameEventQueue.QueuePauseOnChange(sMessage); + sMessage.append(L": "); + sMessage.append(pDescription, 0, nDescriptionLength); } - } - else if (pBookmark.GetBehavior() == BookmarkBehavior::PauseOnChange) - { - pBookmark.SetRowColor(ra::ui::Color(ra::to_unsigned(MemoryBookmarkViewModel::RowColorProperty.GetDefaultValue()))); + + auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable(); + pFrameEventQueue.QueuePauseOnChange(sMessage); } } - - pEmulatorContext.AddNotifyTarget(*this); + else if (pBookmark.GetBehavior() == BookmarkBehavior::PauseOnChange) + { + pBookmark.SetRowColor(ra::ui::Color(ra::to_unsigned(MemoryBookmarkViewModel::RowColorProperty.GetDefaultValue()))); + } } bool MemoryBookmarksViewModel::HasBookmark(ra::ByteAddress nAddress) const diff --git a/src/ui/viewmodels/MemoryBookmarksViewModel.hh b/src/ui/viewmodels/MemoryBookmarksViewModel.hh index 26c0aefef..52b9ad829 100644 --- a/src/ui/viewmodels/MemoryBookmarksViewModel.hh +++ b/src/ui/viewmodels/MemoryBookmarksViewModel.hh @@ -399,6 +399,8 @@ protected: void OnViewModelIntValueChanged(gsl::index nIndex, const IntModelProperty::ChangeArgs& args) override; void OnEndViewModelCollectionUpdate() override; + void UpdateBookmark(MemoryBookmarkViewModel& pBookmark, ra::data::context::EmulatorContext& pEmulatorContext); + bool IsModified() const; size_t m_nUnmodifiedBookmarkCount = 0; diff --git a/src/ui/viewmodels/PointerInspectorViewModel.cpp b/src/ui/viewmodels/PointerInspectorViewModel.cpp index ddba5b5e8..cae7a8d15 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.cpp +++ b/src/ui/viewmodels/PointerInspectorViewModel.cpp @@ -11,22 +11,14 @@ namespace viewmodels { const IntModelProperty PointerInspectorViewModel::CurrentAddressProperty("PointerInspectorViewModel", "CurrentAddress", 0); const StringModelProperty PointerInspectorViewModel::CurrentAddressTextProperty("PointerInspectorViewModel", "CurrentAddressText", L"0x0000"); const StringModelProperty PointerInspectorViewModel::CurrentAddressNoteProperty("PointerInspectorViewModel", "CurrentAddressNote", L""); +const StringModelProperty PointerInspectorViewModel::StructFieldViewModel::OffsetProperty("StructFieldViewModel", "Offset", L"+0000"); PointerInspectorViewModel::PointerInspectorViewModel() + : MemoryBookmarksViewModel() { SetWindowTitle(L"Pointer Inspector"); } -void PointerInspectorViewModel::InitializeNotifyTargets() -{ - auto& pGameContext = ra::services::ServiceLocator::GetMutable(); - pGameContext.AddNotifyTarget(*this); -} - -void PointerInspectorViewModel::DoFrame() -{ -} - void PointerInspectorViewModel::OnValueChanged(const IntModelProperty::ChangeArgs& args) { if (args.Property == CurrentAddressProperty && !m_bSyncingAddress) @@ -40,7 +32,7 @@ void PointerInspectorViewModel::OnValueChanged(const IntModelProperty::ChangeArg OnCurrentAddressChanged(nAddress); } - WindowViewModelBase::OnValueChanged(args); + MemoryBookmarksViewModel::OnValueChanged(args); } void PointerInspectorViewModel::OnValueChanged(const StringModelProperty::ChangeArgs& args) @@ -57,7 +49,7 @@ void PointerInspectorViewModel::OnValueChanged(const StringModelProperty::Change OnCurrentAddressChanged(nAddress); } - WindowViewModelBase::OnValueChanged(args); + MemoryBookmarksViewModel::OnValueChanged(args); } void PointerInspectorViewModel::OnActiveGameChanged() @@ -69,7 +61,7 @@ void PointerInspectorViewModel::OnActiveGameChanged() OnCurrentAddressChanged(GetCurrentAddress()); } -void PointerInspectorViewModel::OnCodeNoteChanged(ra::ByteAddress nAddress, const std::wstring& sNewNote) +void PointerInspectorViewModel::OnCodeNoteChanged(ra::ByteAddress nAddress, const std::wstring&) { if (nAddress == GetCurrentAddress()) OnCurrentAddressChanged(nAddress); // not really, but causes the note to be reloaded @@ -83,23 +75,98 @@ void PointerInspectorViewModel::OnCurrentAddressChanged(ra::ByteAddress nNewAddr { const auto* pNote = pCodeNotes->FindCodeNoteModel(nNewAddress); if (pNote) - LoadNote(nNewAddress, pNote); + LoadNote(pNote); else - LoadNote(nNewAddress, nullptr); + LoadNote(nullptr); } } -void PointerInspectorViewModel::LoadNote(ra::ByteAddress nAddress, const ra::data::models::CodeNoteModel* pNote) +void PointerInspectorViewModel::SyncField(PointerInspectorViewModel::StructFieldViewModel& pFieldViewModel, const ra::data::models::CodeNoteModel& pOffsetNote) +{ + pFieldViewModel.SetSize(pOffsetNote.GetMemSize()); + pFieldViewModel.SetDescription(pOffsetNote.GetNote()); +} + +void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* pNote) { if (pNote == nullptr) { SetCurrentAddressNote(L""); - // TODO: clear list + m_nPointerSize = MemSize::Unknown; + Bookmarks().Clear(); return; } SetCurrentAddressNote(pNote->GetPrimaryNote()); - // TODO: update list + m_nPointerSize = pNote->GetMemSize(); + + const auto nBaseAddress = pNote->GetPointerAddress(); + gsl::index nCount = gsl::narrow_cast(Fields().Count()); + + gsl::index nInsertIndex = 0; + Fields().BeginUpdate(); + pNote->EnumeratePointerNotes([this, &nCount, &nInsertIndex, nBaseAddress] + (ra::ByteAddress nAddress, const ra::data::models::CodeNoteModel& pOffsetNote) + { + const auto nOffset = nAddress - nBaseAddress; + const std::wstring sOffset = ra::StringPrintf(L"+%04x", nOffset); + + bool bFound = false; + for (gsl::index nExistingIndex = nInsertIndex; nExistingIndex < nCount; ++nExistingIndex) + { + if (Fields().GetItemValue(nExistingIndex, StructFieldViewModel::OffsetProperty) == sOffset) + { + // item exists. update it. + auto* pItem = Fields().GetItemAt(nExistingIndex); + Expects(pItem != nullptr); + SyncField(*pItem, pOffsetNote); + Fields().MoveItem(nExistingIndex, nInsertIndex); + bFound = true; + } + } + + if (!bFound) + { + // item doesn't exist. add it. + auto& pItem = Fields().Add(); + pItem.m_nOffset = nOffset; + pItem.SetOffset(sOffset); + SyncField(pItem, pOffsetNote); + + Fields().MoveItem(nCount, nInsertIndex); + ++nCount; + } + + ++nInsertIndex; + return true; + }); + + while (nCount > nInsertIndex) + Fields().RemoveAt(--nCount); + + UpdateValues(); + Fields().EndUpdate(); +} + +void PointerInspectorViewModel::UpdateValues() +{ + auto& pEmulatorContext = ra::services::ServiceLocator::GetMutable(); + const auto nBaseAddress = pEmulatorContext.ReadMemory(GetCurrentAddress(), m_nPointerSize); + + pEmulatorContext.RemoveNotifyTarget(*this); + + const auto nCount = gsl::narrow_cast(Fields().Count()); + for (gsl::index nIndex = 0; nIndex < nCount; ++nIndex) + { + auto* pField = Fields().GetItemAt(nIndex); + if (pField != nullptr) + { + pField->SetAddress(nBaseAddress + pField->m_nOffset); + UpdateBookmark(*pField, pEmulatorContext); + } + } + + pEmulatorContext.AddNotifyTarget(*this); } } // namespace viewmodels diff --git a/src/ui/viewmodels/PointerInspectorViewModel.hh b/src/ui/viewmodels/PointerInspectorViewModel.hh index d3cdea977..7a68fc316 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.hh +++ b/src/ui/viewmodels/PointerInspectorViewModel.hh @@ -5,14 +5,13 @@ #include "data\Types.hh" #include "data\context\GameContext.hh" -#include "ui\WindowViewModelBase.hh" +#include "ui\viewmodels\MemoryBookmarksViewModel.hh" namespace ra { namespace ui { namespace viewmodels { -class PointerInspectorViewModel : public WindowViewModelBase, - protected ra::data::context::GameContext::NotifyTarget +class PointerInspectorViewModel : public MemoryBookmarksViewModel { public: GSL_SUPPRESS_F6 PointerInspectorViewModel(); @@ -23,10 +22,6 @@ public: PointerInspectorViewModel(PointerInspectorViewModel&&) noexcept = delete; PointerInspectorViewModel& operator=(PointerInspectorViewModel&&) noexcept = delete; - void InitializeNotifyTargets(); - - void DoFrame(); - /// /// The for the current address. /// @@ -72,6 +67,37 @@ public: /// void SetCurrentAddressNote(const std::wstring& sValue) { SetValue(CurrentAddressNoteProperty, sValue); } + class StructFieldViewModel : public MemoryBookmarkViewModel + { + public: + /// + /// The for the field offset. + /// + static const StringModelProperty OffsetProperty; + + /// + /// Gets the field offset. + /// + const std::wstring& GetOffset() const { return GetValue(OffsetProperty); } + + /// + /// Sets the field offset. + /// + void SetOffset(const std::wstring& sValue) { SetValue(OffsetProperty, sValue); } + + int32_t m_nOffset; + }; + + /// + /// Gets the list of fields. + /// + ViewModelCollection& Fields() noexcept { return m_vFields; } + + /// + /// Gets the list of fields. + /// + const ViewModelCollection& Fields() const noexcept { return m_vFields; } + protected: void OnValueChanged(const IntModelProperty::ChangeArgs& args) override; void OnValueChanged(const StringModelProperty::ChangeArgs& args) override; @@ -82,9 +108,14 @@ protected: private: void OnCurrentAddressChanged(ra::ByteAddress nNewAddress); - void LoadNote(ra::ByteAddress nAddress, const ra::data::models::CodeNoteModel* pNote); + void LoadNote(const ra::data::models::CodeNoteModel* pNote); + void SyncField(StructFieldViewModel& pFieldViewModel, const ra::data::models::CodeNoteModel& pOffsetNote); + void UpdateValues(); + ViewModelCollection m_vFields; bool m_bSyncingAddress = false; + + MemSize m_nPointerSize = MemSize::Unknown; }; } // namespace viewmodels diff --git a/tests/RA_StringUtils_Tests.cpp b/tests/RA_StringUtils_Tests.cpp index 242bb0e0b..94bad3124 100644 --- a/tests/RA_StringUtils_Tests.cpp +++ b/tests/RA_StringUtils_Tests.cpp @@ -79,23 +79,23 @@ TEST_CLASS(RA_StringUtils_Tests) Assert::AreEqual(std::wstring(L"test test\r\ntest"), Trim(std::wstring(L"\ttest test\r\ntest\r\n\r\n"))); } - TEST_METHOD(TestToString) + TEST_METHOD(TestToAString) { - Assert::AreEqual(std::string("0"), ToString(0)); - Assert::AreEqual(std::string("1"), ToString(1)); - Assert::AreEqual(std::string{'a'}, ToString('a')); - Assert::AreEqual(std::string{'a'}, ToString(L'a')); - Assert::AreEqual(std::string{'3'}, ToString('3')); - Assert::AreEqual(std::string{'3'}, ToString(L'3')); - Assert::AreEqual(std::string("99"), ToString(99)); - Assert::AreEqual(std::string("-3"), ToString(-3)); - Assert::AreEqual(std::string("0"), ToString(0U)); - Assert::AreEqual(std::string("1"), ToString(1U)); - Assert::AreEqual(std::string("99"), ToString(99U)); - Assert::AreEqual(std::string("Apple"), ToString("Apple")); - Assert::AreEqual(std::string("Apple"), ToString(std::string("Apple"))); - Assert::AreEqual(std::string("Apple"), ToString(L"Apple")); - Assert::AreEqual(std::string("Apple"), ToString(std::wstring(L"Apple"))); + Assert::AreEqual(std::string("0"), ToAString(0)); + Assert::AreEqual(std::string("1"), ToAString(1)); + Assert::AreEqual(std::string{'a'}, ToAString('a')); + Assert::AreEqual(std::string{'a'}, ToAString(L'a')); + Assert::AreEqual(std::string{'3'}, ToAString('3')); + Assert::AreEqual(std::string{'3'}, ToAString(L'3')); + Assert::AreEqual(std::string("99"), ToAString(99)); + Assert::AreEqual(std::string("-3"), ToAString(-3)); + Assert::AreEqual(std::string("0"), ToAString(0U)); + Assert::AreEqual(std::string("1"), ToAString(1U)); + Assert::AreEqual(std::string("99"), ToAString(99U)); + Assert::AreEqual(std::string("Apple"), ToAString("Apple")); + Assert::AreEqual(std::string("Apple"), ToAString(std::string("Apple"))); + Assert::AreEqual(std::string("Apple"), ToAString(L"Apple")); + Assert::AreEqual(std::string("Apple"), ToAString(std::wstring(L"Apple"))); } TEST_METHOD(TestToWString) diff --git a/tests/RA_UnitTestHelpers.h b/tests/RA_UnitTestHelpers.h index 72b1924f1..681954af4 100644 --- a/tests/RA_UnitTestHelpers.h +++ b/tests/RA_UnitTestHelpers.h @@ -122,6 +122,20 @@ std::wstring ToString(const ComparisonType& t) } } +template<> +std::wstring ToString(const ra::MemFormat& t) +{ + switch (t) + { + case ra::MemFormat::Hex: + return L"Hex"; + case ra::MemFormat::Dec: + return L"Dec"; + default: + return std::to_wstring(ra::etoi(t)); + } +} + #pragma warning(pop) } // namespace CppUnitTestFramework diff --git a/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp index b9d47f418..06de02d2e 100644 --- a/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp +++ b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp @@ -32,6 +32,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) class PointerInspectorViewModelHarness : public PointerInspectorViewModel { public: + ra::data::context::mocks::MockConsoleContext mockConsoleContext; ra::data::context::mocks::MockGameContext mockGameContext; ra::data::context::mocks::MockUserContext mockUserContext; ra::data::context::mocks::MockEmulatorContext mockEmulatorContext; @@ -110,17 +111,62 @@ TEST_CLASS(PointerInspectorViewModel_Tests) inspector.mockGameContext.NotifyActiveGameChanged(); // enable note support inspector.SetCurrentAddress({ 3U }); - inspector.mockGameContext.Assets().FindCodeNotes()->SetServerCodeNote({3U}, L"Note on 3"); inspector.mockGameContext.Assets().FindCodeNotes()->SetCodeNote({3U}, L"Note on 3"); Assert::AreEqual({ 3U }, inspector.GetCurrentAddress()); Assert::AreEqual(std::wstring(L"0x0003"), inspector.GetCurrentAddressText()); Assert::AreEqual(std::wstring(L"Note on 3"), inspector.GetCurrentAddressNote()); + Assert::AreEqual({ 0U }, inspector.Bookmarks().Count()); inspector.mockGameContext.Assets().FindCodeNotes()->SetCodeNote({3U}, L"Modified Note on 3"); Assert::AreEqual(std::wstring(L"Modified Note on 3"), inspector.GetCurrentAddressNote()); } + + TEST_METHOD(TestSetCurrentAddressWithPointerNote) + { + PointerInspectorViewModelHarness inspector; + inspector.mockGameContext.SetGameId(1); + inspector.mockGameContext.NotifyActiveGameChanged(); // enable note support + + std::array memory = {}; + for (uint8_t i = 8; i < memory.size(); i += 4) + memory.at(i) = i; + inspector.mockEmulatorContext.MockMemory(memory); + memory.at(4) = 12; + + inspector.SetCurrentAddress({4U}); + inspector.mockGameContext.Assets().FindCodeNotes()->SetCodeNote({4U}, + L"[32-bit pointer] Player data\n" + L"+4: [32-bit] Current HP\n" + L"+8: [32-bit] Max HP"); + + Assert::AreEqual({ 4U }, inspector.GetCurrentAddress()); + Assert::AreEqual(std::wstring(L"0x0004"), inspector.GetCurrentAddressText()); + Assert::AreEqual(std::wstring(L"[32-bit pointer] Player data"), inspector.GetCurrentAddressNote()); + + Assert::AreEqual({ 2U }, inspector.Fields().Count()); + + const auto* pField = inspector.Fields().GetItemAt(0); + Expects(pField != nullptr); + Assert::AreEqual(4, pField->m_nOffset); + Assert::AreEqual({ 16U }, pField->GetAddress()); + Assert::AreEqual(std::wstring(L"+0004"), pField->GetOffset()); + Assert::AreEqual(std::wstring(L"[32-bit] Current HP"), pField->GetDescription()); + Assert::AreEqual(MemSize::ThirtyTwoBit, pField->GetSize()); + Assert::AreEqual(ra::MemFormat::Hex, pField->GetFormat()); + Assert::AreEqual(std::wstring(L"00000010"), pField->GetCurrentValue()); + + pField = inspector.Fields().GetItemAt(1); + Expects(pField != nullptr); + Assert::AreEqual(8, pField->m_nOffset); + Assert::AreEqual({ 20U }, pField->GetAddress()); + Assert::AreEqual(std::wstring(L"+0008"), pField->GetOffset()); + Assert::AreEqual(std::wstring(L"[32-bit] Max HP"), pField->GetDescription()); + Assert::AreEqual(MemSize::ThirtyTwoBit, pField->GetSize()); + Assert::AreEqual(ra::MemFormat::Hex, pField->GetFormat()); + Assert::AreEqual(std::wstring(L"00000014"), pField->GetCurrentValue()); + } }; } // namespace tests From b4591b748be253e92850874d11b61dc2176cbad8 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Mon, 11 Nov 2024 09:11:18 -0700 Subject: [PATCH 3/9] add pointer inspector dialog --- src/Exports.cpp | 4 + src/RA_Integration.vcxproj | 4 + src/RA_Integration.vcxproj.filters | 12 ++ src/RA_Resource.h | 2 + src/RA_Shared.rc | 17 ++ src/data/models/CodeNoteModel.cpp | 12 ++ src/data/models/CodeNoteModel.hh | 1 + src/data/models/CodeNotesModel.hh | 7 - src/services/AchievementRuntimeExports.cpp | 2 +- src/services/PerformanceCounter.hh | 2 + .../viewmodels/IntegrationMenuViewModel.cpp | 15 ++ src/ui/viewmodels/IntegrationMenuViewModel.hh | 1 + .../viewmodels/MemoryBookmarksViewModel.cpp | 4 +- src/ui/viewmodels/MemoryBookmarksViewModel.hh | 4 +- .../viewmodels/PointerInspectorViewModel.cpp | 171 +++++++++++++++--- .../viewmodels/PointerInspectorViewModel.hh | 33 +++- src/ui/viewmodels/WindowManager.hh | 2 + src/ui/win32/Desktop.cpp | 2 + src/ui/win32/MemoryBookmarksDialog.cpp | 106 +---------- src/ui/win32/PointerFinderDialog.hh | 2 +- src/ui/win32/PointerInspectorDialog.cpp | 169 +++++++++++++++++ src/ui/win32/PointerInspectorDialog.hh | 57 ++++++ src/ui/win32/bindings/GridBinding.cpp | 1 + .../GridBookmarkFormatColumnBinding.hh | 77 ++++++++ .../GridBookmarkValueColumnBinding.hh | 55 ++++++ tests/Exports_Tests.cpp | 21 ++- .../AchievementRuntimeExports_Tests.cpp | 21 ++- .../IntegrationMenuViewModel_Tests.cpp | 47 ++++- .../PointerInspectorViewModel_Tests.cpp | 148 +++++++++++++-- 29 files changed, 814 insertions(+), 185 deletions(-) create mode 100644 src/ui/win32/PointerInspectorDialog.cpp create mode 100644 src/ui/win32/PointerInspectorDialog.hh create mode 100644 src/ui/win32/bindings/GridBookmarkFormatColumnBinding.hh create mode 100644 src/ui/win32/bindings/GridBookmarkValueColumnBinding.hh diff --git a/src/Exports.cpp b/src/Exports.cpp index 3d93f7cb3..74595a3e9 100644 --- a/src/Exports.cpp +++ b/src/Exports.cpp @@ -514,8 +514,12 @@ static void UpdateUIForFrameChange() TALLY_PERFORMANCE(PerformanceCheckpoint::AssetEditorDoFrame); pWindowManager.AssetEditor.DoFrame(); + TALLY_PERFORMANCE(PerformanceCheckpoint::PointerFinderDoFrame); pWindowManager.PointerFinder.DoFrame(); + TALLY_PERFORMANCE(PerformanceCheckpoint::PointerInspectorDoFrame); + pWindowManager.PointerInspector.DoFrame(); + auto& pFrameEventQueue = ra::services::ServiceLocator::GetMutable(); pFrameEventQueue.DoFrame(); } diff --git a/src/RA_Integration.vcxproj b/src/RA_Integration.vcxproj index 88a3d5a53..c6a8d6140 100644 --- a/src/RA_Integration.vcxproj +++ b/src/RA_Integration.vcxproj @@ -169,6 +169,7 @@ + @@ -327,6 +328,8 @@ + + @@ -356,6 +359,7 @@ + diff --git a/src/RA_Integration.vcxproj.filters b/src/RA_Integration.vcxproj.filters index 31e3e9dc8..5e9f96fbb 100644 --- a/src/RA_Integration.vcxproj.filters +++ b/src/RA_Integration.vcxproj.filters @@ -414,6 +414,9 @@ UI\ViewModels + + UI\Win32 + @@ -983,6 +986,15 @@ UI\ViewModels + + UI\Win32 + + + UI\Win32\Bindings + + + UI\Win32\Bindings + diff --git a/src/RA_Resource.h b/src/RA_Resource.h index a10a9f1ee..161a698de 100644 --- a/src/RA_Resource.h +++ b/src/RA_Resource.h @@ -164,6 +164,7 @@ #define IDD_RA_PROGRESS 1512 #define IDD_RA_NEWASSET 1513 #define IDD_RA_POINTERFINDER 1514 +#define IDD_RA_POINTERINSPECTOR 1515 #define IDC_RA_PASSWORD 1535 #define IDC_RA_SAVEPASSWORD 1536 #define IDC_RA_USERNAME 1549 @@ -209,6 +210,7 @@ #define IDM_RA_NON_HARDCORE_WARNING 1718 #define IDM_RA_FILES_OPENALL 1719 #define IDM_RA_FILES_POINTERFINDER 1720 +#define IDM_RA_FILES_POINTERINSPECTOR 1721 #define IDM_RA_MENUEND 1739 // Next default values for new objects diff --git a/src/RA_Shared.rc b/src/RA_Shared.rc index c11b26a7f..377dc5990 100644 --- a/src/RA_Shared.rc +++ b/src/RA_Shared.rc @@ -263,6 +263,23 @@ BEGIN PUSHBUTTON "Move Down",IDC_RA_MOVE_BOOKMARK_DOWN,290,137,50,14 END +IDD_RA_POINTERINSPECTOR DIALOGEX 0, 0, 341, 166 +STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME +CAPTION "Pointer Inspector" +FONT 8, "MS Shell Dlg", 400, 0, 0x0 +BEGIN + LTEXT "Root Address:",IDC_STATIC,6,5,63,11 + EDITTEXT IDC_RA_ADDRESS,55,3,54,12,ES_AUTOHSCROLL,WS_EX_RIGHT + LTEXT "Viewing Node:",IDC_STATIC,120,5,83,11 + COMBOBOX IDC_RA_FILTER_VALUE,170,3,136,12,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Description:",IDC_STATIC,6,20,63,11 + EDITTEXT IDC_RA_DESCRIPTION,55,18,283,12,ES_AUTOHSCROLL + CONTROL "",IDC_RA_LBX_ADDRESSES,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_CLIPCHILDREN | WS_BORDER,4,32,334,116 + PUSHBUTTON "Bookmark Selected",IDC_RA_ADDBOOKMARK,4,149,70,14,BS_MULTILINE + PUSHBUTTON "Pause",IDC_RA_PAUSE,236,149,50,14,BS_MULTILINE + PUSHBUTTON "Freeze",IDC_RA_FREEZE,288,149,50,14,BS_MULTILINE +END + IDD_RA_CODENOTES DIALOGEX 0, 0, 287, 230 STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME CAPTION "Code Notes" diff --git a/src/data/models/CodeNoteModel.cpp b/src/data/models/CodeNoteModel.cpp index e3e47efcf..a6842abc9 100644 --- a/src/data/models/CodeNoteModel.cpp +++ b/src/data/models/CodeNoteModel.cpp @@ -72,6 +72,11 @@ uint32_t CodeNoteModel::GetRawPointerValue() const noexcept return m_pPointerData != nullptr ? m_pPointerData->RawPointerValue : 0xFFFFFFFF; } +bool CodeNoteModel::HasNestedPointers() const noexcept +{ + return m_pPointerData != nullptr && m_pPointerData->HasPointers; +} + static ra::ByteAddress ConvertPointer(ra::ByteAddress nAddress) { const auto& pConsoleContext = ra::services::ServiceLocator::Get(); @@ -298,6 +303,13 @@ void CodeNoteModel::SetNote(const std::wstring& sNote) nIndex = sNote.find(L"\n+"); if (nIndex != std::wstring::npos) ProcessIndirectNotes(sNote, nIndex); + + // failed to find nested code notes. create a PointerData object so the note still gets treated as a pointer + if (!m_pPointerData) + { + m_pPointerData.reset(new PointerData()); + m_pPointerData->HeaderLength = gsl::narrow_cast(sNote.length()); + } } } diff --git a/src/data/models/CodeNoteModel.hh b/src/data/models/CodeNoteModel.hh index 1aa135b7b..ae4f9bfd7 100644 --- a/src/data/models/CodeNoteModel.hh +++ b/src/data/models/CodeNoteModel.hh @@ -32,6 +32,7 @@ public: std::wstring GetPointerDescription() const; ra::ByteAddress GetPointerAddress() const noexcept; uint32_t GetRawPointerValue() const noexcept; + bool HasNestedPointers() const noexcept; const CodeNoteModel* GetPointerNoteAtOffset(int nOffset) const; std::pair GetPointerNoteAtAddress(ra::ByteAddress nAddress) const; diff --git a/src/data/models/CodeNotesModel.hh b/src/data/models/CodeNotesModel.hh index 2c1330981..c4652bea7 100644 --- a/src/data/models/CodeNotesModel.hh +++ b/src/data/models/CodeNotesModel.hh @@ -100,13 +100,6 @@ public: return (pNote == nullptr) ? MemSize::Unknown : pNote->GetMemSize(); } - /// - /// Returns the model for the note associated with the specified address. - /// - /// The address to look up. - /// The note associated to the address, nullptr if no note is associated to the address. - const CodeNoteModel* FindCodeNoteModel(ra::ByteAddress nAddress) const; - /// /// Returns the address of the real code note from which an indirect code note was derived. /// diff --git a/src/services/AchievementRuntimeExports.cpp b/src/services/AchievementRuntimeExports.cpp index d6cc36149..2bb6e2e7b 100644 --- a/src/services/AchievementRuntimeExports.cpp +++ b/src/services/AchievementRuntimeExports.cpp @@ -555,7 +555,7 @@ class AchievementRuntimeExports : private AchievementRuntime const auto* pItem = vmMenuItems.GetItemAt(nIndex); if (!pItem) continue; - + switch (pItem->GetId()) { case IDM_RA_FILES_LOGIN: diff --git a/src/services/PerformanceCounter.hh b/src/services/PerformanceCounter.hh index cb5668c15..90d335d05 100644 --- a/src/services/PerformanceCounter.hh +++ b/src/services/PerformanceCounter.hh @@ -15,6 +15,8 @@ enum class PerformanceCheckpoint MemoryInspectorDoFrame, AssetListDoFrame, AssetEditorDoFrame, + PointerFinderDoFrame, + PointerInspectorDoFrame, NUM_CHECKPOINTS }; diff --git a/src/ui/viewmodels/IntegrationMenuViewModel.cpp b/src/ui/viewmodels/IntegrationMenuViewModel.cpp index df20024ae..e5ebc0dea 100644 --- a/src/ui/viewmodels/IntegrationMenuViewModel.cpp +++ b/src/ui/viewmodels/IntegrationMenuViewModel.cpp @@ -84,6 +84,7 @@ void IntegrationMenuViewModel::AddCommonMenuItems(LookupItemViewModelCollection& vmMenu.Add(IDM_RA_PARSERICHPRESENCE, L"Rich &Presence Monitor"); vmMenu.Add(0, L"-----"); vmMenu.Add(IDM_RA_FILES_POINTERFINDER, L"Pointer &Finder"); + vmMenu.Add(IDM_RA_FILES_POINTERINSPECTOR, L"Pointer &Inspector"); } void IntegrationMenuViewModel::ActivateMenuItem(int nMenuItemId) @@ -142,6 +143,10 @@ void IntegrationMenuViewModel::ActivateMenuItem(int nMenuItemId) ShowPointerFinder(); break; + case IDM_RA_FILES_POINTERINSPECTOR: + ShowPointerInspector(); + break; + case IDM_RA_FILES_CODENOTES: ShowCodeNotes(); break; @@ -317,6 +322,16 @@ void IntegrationMenuViewModel::ShowPointerFinder() } } +void IntegrationMenuViewModel::ShowPointerInspector() +{ + auto& pEmulatorContext = ra::services::ServiceLocator::GetMutable(); + if (pEmulatorContext.WarnDisableHardcoreMode("inspect pointers")) + { + auto& pWindowManager = ra::services::ServiceLocator::GetMutable(); + pWindowManager.PointerInspector.Show(); + } +} + void IntegrationMenuViewModel::ShowCodeNotes() { auto& pEmulatorContext = ra::services::ServiceLocator::GetMutable(); diff --git a/src/ui/viewmodels/IntegrationMenuViewModel.hh b/src/ui/viewmodels/IntegrationMenuViewModel.hh index 5cf82c754..23ca822b3 100644 --- a/src/ui/viewmodels/IntegrationMenuViewModel.hh +++ b/src/ui/viewmodels/IntegrationMenuViewModel.hh @@ -34,6 +34,7 @@ private: static void ShowMemoryInspector(); static void ShowMemoryBookmarks(); static void ShowPointerFinder(); + static void ShowPointerInspector(); static void ShowCodeNotes(); static void ShowRichPresenceMonitor(); static void ShowAllEditors(); diff --git a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp index 1b4b77662..982e12ebf 100644 --- a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp +++ b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp @@ -586,13 +586,13 @@ void MemoryBookmarksViewModel::DoFrame() for (gsl::index nIndex = 0; ra::to_unsigned(nIndex) < m_vBookmarks.Count(); ++nIndex) { auto& pBookmark = *m_vBookmarks.GetItemAt(nIndex); - UpdateBookmark(pBookmark, pEmulatorContext); + UpdateBookmark(pBookmark); } pEmulatorContext.AddNotifyTarget(*this); } -void MemoryBookmarksViewModel::UpdateBookmark(MemoryBookmarksViewModel::MemoryBookmarkViewModel& pBookmark, ra::data::context::EmulatorContext& pEmulatorContext) +void MemoryBookmarksViewModel::UpdateBookmark(MemoryBookmarksViewModel::MemoryBookmarkViewModel& pBookmark) { if (pBookmark.MemoryChanged()) { diff --git a/src/ui/viewmodels/MemoryBookmarksViewModel.hh b/src/ui/viewmodels/MemoryBookmarksViewModel.hh index 52b9ad829..38afa4a28 100644 --- a/src/ui/viewmodels/MemoryBookmarksViewModel.hh +++ b/src/ui/viewmodels/MemoryBookmarksViewModel.hh @@ -33,7 +33,7 @@ public: void InitializeNotifyTargets(); - void DoFrame(); + virtual void DoFrame(); enum class BookmarkBehavior { @@ -399,7 +399,7 @@ protected: void OnViewModelIntValueChanged(gsl::index nIndex, const IntModelProperty::ChangeArgs& args) override; void OnEndViewModelCollectionUpdate() override; - void UpdateBookmark(MemoryBookmarkViewModel& pBookmark, ra::data::context::EmulatorContext& pEmulatorContext); + void UpdateBookmark(MemoryBookmarkViewModel& pBookmark); bool IsModified() const; size_t m_nUnmodifiedBookmarkCount = 0; diff --git a/src/ui/viewmodels/PointerInspectorViewModel.cpp b/src/ui/viewmodels/PointerInspectorViewModel.cpp index cae7a8d15..2e0ff94a4 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.cpp +++ b/src/ui/viewmodels/PointerInspectorViewModel.cpp @@ -12,6 +12,9 @@ const IntModelProperty PointerInspectorViewModel::CurrentAddressProperty("Pointe const StringModelProperty PointerInspectorViewModel::CurrentAddressTextProperty("PointerInspectorViewModel", "CurrentAddressText", L"0x0000"); const StringModelProperty PointerInspectorViewModel::CurrentAddressNoteProperty("PointerInspectorViewModel", "CurrentAddressNote", L""); const StringModelProperty PointerInspectorViewModel::StructFieldViewModel::OffsetProperty("StructFieldViewModel", "Offset", L"+0000"); +const IntModelProperty PointerInspectorViewModel::SelectedNodeProperty("PointerInspectorViewModel", "SelectedNode", -2); + +constexpr int RootNodeID = -1; PointerInspectorViewModel::PointerInspectorViewModel() : MemoryBookmarksViewModel() @@ -31,6 +34,10 @@ void PointerInspectorViewModel::OnValueChanged(const IntModelProperty::ChangeArg OnCurrentAddressChanged(nAddress); } + else if (args.Property == SelectedNodeProperty && !m_bSyncingAddress) + { + OnSelectedNodeChanged(args.tNewValue); + } MemoryBookmarksViewModel::OnValueChanged(args); } @@ -73,18 +80,84 @@ void PointerInspectorViewModel::OnCurrentAddressChanged(ra::ByteAddress nNewAddr const auto* pCodeNotes = pGameContext.Assets().FindCodeNotes(); if (pCodeNotes != nullptr) { + SetSelectedNode(RootNodeID); + const auto* pNote = pCodeNotes->FindCodeNoteModel(nNewAddress); if (pNote) + { + LoadNodes(pNote); LoadNote(pNote); + } else + { + LoadNodes(nullptr); LoadNote(nullptr); + } + } +} + +const ra::data::models::CodeNoteModel* PointerInspectorViewModel::FindNestedCodeNoteModel( + const ra::data::models::CodeNoteModel& pRootNote, int nNewNode) +{ + auto nIndex = m_vNodes.FindItemIndex(LookupItemViewModel::IdProperty, nNewNode); + if (nIndex == -1) + return nullptr; + + const auto* pNode = m_vNodes.GetItemAt(nIndex); + Expects(pNode != nullptr); + + std::stack sChain; + sChain.push(pNode); + + auto nDepth = nNewNode >> 24; + while (nDepth > 0 && nIndex > 1) + { + const auto* pNestedNode = m_vNodes.GetItemAt(--nIndex); + const auto nNestedDepth = pNode->GetId() >> 24; + if (nNestedDepth < nDepth) + { + sChain.push(pNestedNode); + nDepth = nNestedDepth; + break; + } + } + + const auto* pParentNote = &pRootNote; + do + { + const auto* pNestedNode = sChain.top(); + const auto nOffset = pNestedNode->GetId() & 0x00FFFFFF; + + const auto* pNestedNote = pParentNote->GetPointerNoteAtOffset(nOffset); + if (!pNestedNote) + return nullptr; + + pParentNote = pNestedNote; + sChain.pop(); + } while (!sChain.empty()); + + return pParentNote; +} + +void PointerInspectorViewModel::OnSelectedNodeChanged(int nNewNode) +{ + const auto& pGameContext = ra::services::ServiceLocator::Get(); + const auto* pCodeNotes = pGameContext.Assets().FindCodeNotes(); + if (pCodeNotes != nullptr) + { + const auto* pNote = pCodeNotes->FindCodeNoteModel(GetCurrentAddress()); + if (pNote != nullptr && nNewNode != RootNodeID) + pNote = FindNestedCodeNoteModel(*pNote, nNewNode); + + LoadNote(pNote); } } void PointerInspectorViewModel::SyncField(PointerInspectorViewModel::StructFieldViewModel& pFieldViewModel, const ra::data::models::CodeNoteModel& pOffsetNote) { - pFieldViewModel.SetSize(pOffsetNote.GetMemSize()); - pFieldViewModel.SetDescription(pOffsetNote.GetNote()); + const auto nSize = pOffsetNote.GetMemSize(); + pFieldViewModel.SetSize((nSize == MemSize::Unknown) ? MemSize::EightBit : nSize); + pFieldViewModel.SetDescription(pOffsetNote.GetPrimaryNote()); } void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* pNote) @@ -92,15 +165,15 @@ void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* if (pNote == nullptr) { SetCurrentAddressNote(L""); - m_nPointerSize = MemSize::Unknown; + m_pCurrentNote = nullptr; Bookmarks().Clear(); return; } SetCurrentAddressNote(pNote->GetPrimaryNote()); - m_nPointerSize = pNote->GetMemSize(); - const auto nBaseAddress = pNote->GetPointerAddress(); + m_pCurrentNote = pNote; + const auto nBaseAddress = m_pCurrentNote->GetPointerAddress(); gsl::index nCount = gsl::narrow_cast(Fields().Count()); gsl::index nInsertIndex = 0; @@ -111,32 +184,21 @@ void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* const auto nOffset = nAddress - nBaseAddress; const std::wstring sOffset = ra::StringPrintf(L"+%04x", nOffset); - bool bFound = false; - for (gsl::index nExistingIndex = nInsertIndex; nExistingIndex < nCount; ++nExistingIndex) + StructFieldViewModel* pItem; + if (nInsertIndex < nCount) { - if (Fields().GetItemValue(nExistingIndex, StructFieldViewModel::OffsetProperty) == sOffset) - { - // item exists. update it. - auto* pItem = Fields().GetItemAt(nExistingIndex); - Expects(pItem != nullptr); - SyncField(*pItem, pOffsetNote); - Fields().MoveItem(nExistingIndex, nInsertIndex); - bFound = true; - } + pItem = Fields().GetItemAt(nInsertIndex); } - - if (!bFound) + else { - // item doesn't exist. add it. - auto& pItem = Fields().Add(); - pItem.m_nOffset = nOffset; - pItem.SetOffset(sOffset); - SyncField(pItem, pOffsetNote); - - Fields().MoveItem(nCount, nInsertIndex); ++nCount; + pItem = &Fields().Add(); } + pItem->m_nOffset = nOffset; + pItem->SetOffset(sOffset); + SyncField(*pItem, pOffsetNote); + ++nInsertIndex; return true; }); @@ -148,11 +210,59 @@ void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* Fields().EndUpdate(); } +static void LoadSubNotes(LookupItemViewModelCollection& vNodes, + const ra::data::models::CodeNoteModel& pNote, ra::ByteAddress nBaseAddress, int nDepth, int nParentIndex) +{ + pNote.EnumeratePointerNotes([&vNodes, nBaseAddress, nDepth, nParentIndex] + (ra::ByteAddress nAddress, const ra::data::models::CodeNoteModel& pOffsetNote) { + const auto nOffset = nAddress - nBaseAddress; + if (!pOffsetNote.IsPointer()) + return true; + + std::wstring sLabel; + if (nDepth > 1) + sLabel = std::wstring(nDepth - 1, ' '); + sLabel += ra::StringPrintf(L"+%04x | %s", nOffset, pOffsetNote.GetPointerDescription()); + + vNodes.Add(nParentIndex << 24 | nOffset & 0x00FFFFFF, sLabel); + + if (pOffsetNote.HasNestedPointers()) + LoadSubNotes(vNodes, pOffsetNote, pOffsetNote.GetPointerAddress(), nDepth + 1, gsl::narrow_cast(vNodes.Count() - 1)); + + return true; + }); +} + +void PointerInspectorViewModel::LoadNodes(const ra::data::models::CodeNoteModel* pNote) +{ + const int nSelectedNode = GetSelectedNode(); + + m_vNodes.BeginUpdate(); + m_vNodes.Clear(); + + if (pNote == nullptr) + { + m_vNodes.Add(RootNodeID, L"Root"); + } + else + { + m_vNodes.Add(RootNodeID, pNote->GetPrimaryNote()); + if (pNote->HasNestedPointers()) + LoadSubNotes(m_vNodes, *pNote, pNote->GetPointerAddress(), 1, 0); + } + + m_vNodes.EndUpdate(); + + // if the selected item is no longer available, select the root node + if (m_vNodes.FindItemIndex(LookupItemViewModel::IdProperty, nSelectedNode) == -1) + SetSelectedNode(0); +} + void PointerInspectorViewModel::UpdateValues() { - auto& pEmulatorContext = ra::services::ServiceLocator::GetMutable(); - const auto nBaseAddress = pEmulatorContext.ReadMemory(GetCurrentAddress(), m_nPointerSize); + const auto nBaseAddress = (m_pCurrentNote != nullptr) ? m_pCurrentNote->GetPointerAddress() : 0U; + auto& pEmulatorContext = ra::services::ServiceLocator::GetMutable(); pEmulatorContext.RemoveNotifyTarget(*this); const auto nCount = gsl::narrow_cast(Fields().Count()); @@ -162,13 +272,18 @@ void PointerInspectorViewModel::UpdateValues() if (pField != nullptr) { pField->SetAddress(nBaseAddress + pField->m_nOffset); - UpdateBookmark(*pField, pEmulatorContext); + UpdateBookmark(*pField); } } pEmulatorContext.AddNotifyTarget(*this); } +void PointerInspectorViewModel::DoFrame() +{ + UpdateValues(); +} + } // namespace viewmodels } // namespace ui } // namespace ra diff --git a/src/ui/viewmodels/PointerInspectorViewModel.hh b/src/ui/viewmodels/PointerInspectorViewModel.hh index 7a68fc316..b22198f08 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.hh +++ b/src/ui/viewmodels/PointerInspectorViewModel.hh @@ -98,6 +98,33 @@ public: /// const ViewModelCollection& Fields() const noexcept { return m_vFields; } + /// + /// Gets the list of available nodes. + /// + LookupItemViewModelCollection& Nodes() noexcept { return m_vNodes; } + + /// + /// Gets the list of available nodes. + /// + const LookupItemViewModelCollection& Nodes() const noexcept { return m_vNodes; } + + /// + /// The for the selected node. + /// + static const IntModelProperty SelectedNodeProperty; + + /// + /// Gets the selected node index. + /// + int GetSelectedNode() const { return GetValue(SelectedNodeProperty); } + + /// + /// Sets the selected node index. + /// + void SetSelectedNode(int nValue) { SetValue(SelectedNodeProperty, nValue); } + + void DoFrame() override; + protected: void OnValueChanged(const IntModelProperty::ChangeArgs& args) override; void OnValueChanged(const StringModelProperty::ChangeArgs& args) override; @@ -108,14 +135,18 @@ protected: private: void OnCurrentAddressChanged(ra::ByteAddress nNewAddress); + void OnSelectedNodeChanged(int nNode); void LoadNote(const ra::data::models::CodeNoteModel* pNote); + void LoadNodes(const ra::data::models::CodeNoteModel* pNote); + const ra::data::models::CodeNoteModel* FindNestedCodeNoteModel(const ra::data::models::CodeNoteModel& pRootNote, int nNewNode); void SyncField(StructFieldViewModel& pFieldViewModel, const ra::data::models::CodeNoteModel& pOffsetNote); void UpdateValues(); + LookupItemViewModelCollection m_vNodes; ViewModelCollection m_vFields; bool m_bSyncingAddress = false; - MemSize m_nPointerSize = MemSize::Unknown; + const ra::data::models::CodeNoteModel* m_pCurrentNote = nullptr; }; } // namespace viewmodels diff --git a/src/ui/viewmodels/WindowManager.hh b/src/ui/viewmodels/WindowManager.hh index 93f4f4367..10a1ea61b 100644 --- a/src/ui/viewmodels/WindowManager.hh +++ b/src/ui/viewmodels/WindowManager.hh @@ -8,6 +8,7 @@ #include "MemoryBookmarksViewModel.hh" #include "MemoryInspectorViewModel.hh" #include "PointerFinderViewModel.hh" +#include "PointerInspectorViewModel.hh" #include "RichPresenceMonitorViewModel.hh" namespace ra { @@ -24,6 +25,7 @@ public: MemoryInspectorViewModel MemoryInspector; CodeNotesViewModel CodeNotes; PointerFinderViewModel PointerFinder; + PointerInspectorViewModel PointerInspector; }; } // namespace viewmodels diff --git a/src/ui/win32/Desktop.cpp b/src/ui/win32/Desktop.cpp index addfea011..bc09d2a9d 100644 --- a/src/ui/win32/Desktop.cpp +++ b/src/ui/win32/Desktop.cpp @@ -19,6 +19,7 @@ #include "ui/win32/NewAssetDialog.hh" #include "ui/win32/OverlaySettingsDialog.hh" #include "ui/win32/PointerFinderDialog.hh" +#include "ui/win32/PointerInspectorDialog.hh" #include "ui/win32/ProgressDialog.hh" #include "ui/win32/RichPresenceDialog.hh" #include "ui/win32/UnknownGameDialog.hh" @@ -47,6 +48,7 @@ Desktop::Desktop() noexcept m_vDialogPresenters.emplace_back(new (std::nothrow) AssetListDialog::Presenter); m_vDialogPresenters.emplace_back(new (std::nothrow) AssetEditorDialog::Presenter); m_vDialogPresenters.emplace_back(new (std::nothrow) PointerFinderDialog::Presenter); + m_vDialogPresenters.emplace_back(new (std::nothrow) PointerInspectorDialog::Presenter); m_vDialogPresenters.emplace_back(new (std::nothrow) FileDialog::Presenter); m_vDialogPresenters.emplace_back(new (std::nothrow) OverlaySettingsDialog::Presenter); m_vDialogPresenters.emplace_back(new (std::nothrow) NewAssetDialog::Presenter); diff --git a/src/ui/win32/MemoryBookmarksDialog.cpp b/src/ui/win32/MemoryBookmarksDialog.cpp index 6ea1de2fc..9188fd283 100644 --- a/src/ui/win32/MemoryBookmarksDialog.cpp +++ b/src/ui/win32/MemoryBookmarksDialog.cpp @@ -5,8 +5,11 @@ #include "data\context\EmulatorContext.hh" #include "ui\viewmodels\MessageBoxViewModel.hh" +#include "ui\viewmodels\PointerInspectorViewModel.hh" #include "ui\win32\bindings\GridAddressColumnBinding.hh" +#include "ui\win32\bindings\GridBookmarkFormatColumnBinding.hh" +#include "ui\win32\bindings\GridBookmarkValueColumnBinding.hh" #include "ui\win32\bindings\GridLookupColumnBinding.hh" #include "ui\win32\bindings\GridNumberColumnBinding.hh" #include "ui\win32\bindings\GridTextColumnBinding.hh" @@ -20,7 +23,8 @@ namespace win32 { bool MemoryBookmarksDialog::Presenter::IsSupported(const ra::ui::WindowViewModelBase& vmViewModel) noexcept { - return (dynamic_cast(&vmViewModel) != nullptr); + return (dynamic_cast(&vmViewModel) != nullptr && + dynamic_cast(&vmViewModel) == nullptr); } void MemoryBookmarksDialog::Presenter::ShowModal(ra::ui::WindowViewModelBase& vmViewModel, HWND) @@ -80,100 +84,6 @@ void MemoryBookmarksDialog::BookmarksGridBinding::OnTotalMemorySizeChanged() // ------------------------------------ -class GridBookmarkValueColumnBinding : public ra::ui::win32::bindings::GridTextColumnBinding -{ -public: - GridBookmarkValueColumnBinding(const StringModelProperty& pBoundProperty) noexcept - : ra::ui::win32::bindings::GridTextColumnBinding(pBoundProperty) - { - } - - HWND CreateInPlaceEditor(HWND hParent, InPlaceEditorInfo& pInfo) override - { - const auto& vmItems = static_cast(pInfo.pGridBinding)->GetItems(); - if (vmItems.GetItemValue(pInfo.nItemIndex, MemoryBookmarksViewModel::MemoryBookmarkViewModel::ReadOnlyProperty)) - return nullptr; - - return ra::ui::win32::bindings::GridTextColumnBinding::CreateInPlaceEditor(hParent, pInfo); - } - - bool SetText(ra::ui::ViewModelCollectionBase& vmItems, gsl::index nIndex, const std::wstring& sValue) override - { - auto* vmItem = dynamic_cast(vmItems.GetViewModelAt(nIndex)); - if (vmItem != nullptr) - { - std::wstring sError; - if (!vmItem->SetCurrentValue(sValue, sError)) - { - ra::ui::viewmodels::MessageBoxViewModel::ShowWarningMessage(L"Invalid Input", sError); - return false; - } - } - - return true; - } -}; - -// ------------------------------------ - -class GridBookmarkFormatColumnBinding : public ra::ui::win32::bindings::GridLookupColumnBinding -{ -public: - GridBookmarkFormatColumnBinding(const IntModelProperty& pBoundProperty, const ra::ui::viewmodels::LookupItemViewModelCollection& vmItems) noexcept - : ra::ui::win32::bindings::GridLookupColumnBinding(pBoundProperty, vmItems) - { - } - - std::wstring GetText(const ra::ui::ViewModelCollectionBase& vmItems, gsl::index nIndex) const override - { - if (IsHidden(vmItems, nIndex)) - return L""; - - return ra::ui::win32::bindings::GridLookupColumnBinding::GetText(vmItems, nIndex); - } - - bool DependsOn(const ra::ui::IntModelProperty& pProperty) const noexcept override - { - if (pProperty == MemoryBookmarksViewModel::MemoryBookmarkViewModel::SizeProperty) - return true; - - return ra::ui::win32::bindings::GridLookupColumnBinding::DependsOn(pProperty); - } - - HWND CreateInPlaceEditor(HWND hParent, InPlaceEditorInfo& pInfo) override - { - auto* pGridBinding = static_cast(pInfo.pGridBinding); - Expects(pGridBinding != nullptr); - - if (IsHidden(pGridBinding->GetItems(), pInfo.nItemIndex)) - return nullptr; - - return GridLookupColumnBinding::CreateInPlaceEditor(hParent, pInfo); - } - -private: - static bool IsHidden(const ra::ui::ViewModelCollectionBase& vmItems, gsl::index nIndex) - { - const auto nSize = ra::itoe(vmItems.GetItemValue(nIndex, MemoryBookmarksViewModel::MemoryBookmarkViewModel::SizeProperty)); - switch (nSize) - { - case MemSize::Float: - case MemSize::FloatBigEndian: - case MemSize::Double32: - case MemSize::Double32BigEndian: - case MemSize::MBF32: - case MemSize::MBF32LE: - case MemSize::Text: - return true; - - default: - return false; - } - } -}; - -// ------------------------------------ - MemoryBookmarksDialog::MemoryBookmarksDialog(MemoryBookmarksViewModel& vmMemoryBookmarks) : DialogBase(vmMemoryBookmarks), m_bindBookmarks(vmMemoryBookmarks) @@ -213,14 +123,14 @@ MemoryBookmarksDialog::MemoryBookmarksDialog(MemoryBookmarksViewModel& vmMemoryB pSizeColumn->SetReadOnly(false); m_bindBookmarks.BindColumn(2, std::move(pSizeColumn)); - auto pFormatColumn = std::make_unique( + auto pFormatColumn = std::make_unique( MemoryBookmarksViewModel::MemoryBookmarkViewModel::FormatProperty, vmMemoryBookmarks.Formats()); pFormatColumn->SetHeader(L"Format"); pFormatColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 32); pFormatColumn->SetReadOnly(false); m_bindBookmarks.BindColumn(3, std::move(pFormatColumn)); - auto pValueColumn = std::make_unique( + auto pValueColumn = std::make_unique( MemoryBookmarksViewModel::MemoryBookmarkViewModel::CurrentValueProperty); pValueColumn->SetHeader(L"Value"); pValueColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 72); @@ -228,7 +138,7 @@ MemoryBookmarksDialog::MemoryBookmarksDialog(MemoryBookmarksViewModel& vmMemoryB pValueColumn->SetReadOnly(false); m_bindBookmarks.BindColumn(4, std::move(pValueColumn)); - auto pPriorColumn = std::make_unique( + auto pPriorColumn = std::make_unique( MemoryBookmarksViewModel::MemoryBookmarkViewModel::PreviousValueProperty); pPriorColumn->SetHeader(L"Prior"); pPriorColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 72); diff --git a/src/ui/win32/PointerFinderDialog.hh b/src/ui/win32/PointerFinderDialog.hh index 2fd6b4798..ade82669d 100644 --- a/src/ui/win32/PointerFinderDialog.hh +++ b/src/ui/win32/PointerFinderDialog.hh @@ -21,7 +21,7 @@ namespace win32 { class PointerFinderDialog : public DialogBase { public: - explicit PointerFinderDialog(viewmodels::PointerFinderViewModel& vmNewAsset); + explicit PointerFinderDialog(viewmodels::PointerFinderViewModel& vmPointerFinder); virtual ~PointerFinderDialog() noexcept = default; PointerFinderDialog(const PointerFinderDialog&) noexcept = delete; PointerFinderDialog& operator=(const PointerFinderDialog&) noexcept = delete; diff --git a/src/ui/win32/PointerInspectorDialog.cpp b/src/ui/win32/PointerInspectorDialog.cpp new file mode 100644 index 000000000..ba781cb73 --- /dev/null +++ b/src/ui/win32/PointerInspectorDialog.cpp @@ -0,0 +1,169 @@ +#include "PointerInspectorDialog.hh" + +#include "RA_Defs.h" +#include "RA_Log.h" +#include "RA_Resource.h" + +#include "ui\viewmodels\WindowManager.hh" + +#include "ui\win32\bindings\GridBookmarkFormatColumnBinding.hh" +#include "ui\win32\bindings\GridBookmarkValueColumnBinding.hh" +#include "ui\win32\bindings\GridLookupColumnBinding.hh" +#include "ui\win32\bindings\GridTextColumnBinding.hh" + +using ra::ui::viewmodels::MemoryViewerViewModel; +using ra::ui::viewmodels::PointerInspectorViewModel; +using ra::ui::win32::bindings::GridColumnBinding; + +namespace ra { +namespace ui { +namespace win32 { + +bool PointerInspectorDialog::Presenter::IsSupported(const ra::ui::WindowViewModelBase& vmViewModel) noexcept +{ + return (dynamic_cast(&vmViewModel) != nullptr); +} + +void PointerInspectorDialog::Presenter::ShowModal(ra::ui::WindowViewModelBase& vmViewModel, HWND) +{ + ShowWindow(vmViewModel); +} + +void PointerInspectorDialog::Presenter::ShowWindow(ra::ui::WindowViewModelBase& vmViewModel) +{ + auto* vmPointerFinder = dynamic_cast(&vmViewModel); + Expects(vmPointerFinder != nullptr); + + if (m_pDialog == nullptr) + { + m_pDialog.reset(new PointerInspectorDialog(*vmPointerFinder)); + if (!m_pDialog->CreateDialogWindow(MAKEINTRESOURCE(IDD_RA_POINTERINSPECTOR), this)) + RA_LOG_ERR("Could not create Pointer Inspector dialog!"); + } + + m_pDialog->ShowDialogWindow(); +} + +void PointerInspectorDialog::Presenter::OnClosed() noexcept { m_pDialog.reset(); } + +// ------------------------------------ + +PointerInspectorDialog::PointerInspectorDialog(PointerInspectorViewModel& vmPointerFinder) + : DialogBase(vmPointerFinder), + m_bindAddress(vmPointerFinder), + m_bindNodes(vmPointerFinder), + m_bindDescription(vmPointerFinder), + m_bindFields(vmPointerFinder) +{ + m_bindWindow.SetInitialPosition(RelativePosition::After, RelativePosition::Near, "Pointer Finder"); + + m_bindAddress.BindText(PointerInspectorViewModel::CurrentAddressTextProperty, ra::ui::win32::bindings::TextBoxBinding::UpdateMode::Typing); + m_bindNodes.BindItems(vmPointerFinder.Nodes()); + m_bindNodes.BindSelectedItem(PointerInspectorViewModel::SelectedNodeProperty); + m_bindDescription.BindText(PointerInspectorViewModel::CurrentAddressNoteProperty); + + auto pOffsetColumn = std::make_unique( + PointerInspectorViewModel::StructFieldViewModel::OffsetProperty); + pOffsetColumn->SetHeader(L"Offset"); + pOffsetColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 80); + m_bindFields.BindColumn(0, std::move(pOffsetColumn)); + + auto pDescriptionColumn = std::make_unique( + PointerInspectorViewModel::StructFieldViewModel::DescriptionProperty); + pDescriptionColumn->SetHeader(L"Description"); + pDescriptionColumn->SetWidth(GridColumnBinding::WidthType::Fill, 80); + pDescriptionColumn->SetReadOnly(false); + m_bindFields.BindColumn(1, std::move(pDescriptionColumn)); + + auto pSizeColumn = std::make_unique( + PointerInspectorViewModel::StructFieldViewModel::SizeProperty, vmPointerFinder.Sizes()); + pSizeColumn->SetHeader(L"Size"); + pSizeColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 76); + pSizeColumn->SetAlignment(ra::ui::RelativePosition::Far); + pSizeColumn->SetReadOnly(false); + m_bindFields.BindColumn(2, std::move(pSizeColumn)); + + auto pFormatColumn = std::make_unique( + PointerInspectorViewModel::StructFieldViewModel::FormatProperty, vmPointerFinder.Formats()); + pFormatColumn->SetHeader(L"Format"); + pFormatColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 32); + pFormatColumn->SetReadOnly(false); + m_bindFields.BindColumn(3, std::move(pFormatColumn)); + + auto pValueColumn = std::make_unique( + PointerInspectorViewModel::StructFieldViewModel::CurrentValueProperty); + pValueColumn->SetHeader(L"Value"); + pValueColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 72); + pValueColumn->SetAlignment(ra::ui::RelativePosition::Center); + pValueColumn->SetReadOnly(false); + m_bindFields.BindColumn(4, std::move(pValueColumn)); + + auto pBehaviorColumn = std::make_unique( + PointerInspectorViewModel::StructFieldViewModel::BehaviorProperty, vmPointerFinder.Behaviors()); + pBehaviorColumn->SetHeader(L"Behavior"); + pBehaviorColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 60); + pBehaviorColumn->SetReadOnly(false); + m_bindFields.BindColumn(5, std::move(pBehaviorColumn)); + + m_bindFields.BindItems(vmPointerFinder.Fields()); + m_bindFields.BindIsSelected(PointerInspectorViewModel::StructFieldViewModel::IsSelectedProperty); + + // Resize behavior + using namespace ra::bitwise_ops; + SetAnchor(IDC_RA_ADDRESS, Anchor::Top | Anchor::Left); + SetAnchor(IDC_RA_DESCRIPTION, Anchor::Top | Anchor::Left | Anchor::Right); + SetAnchor(IDC_RA_LBX_ADDRESSES, Anchor::Top | Anchor::Left | Anchor::Bottom | Anchor::Right); + + + SetAnchor(IDC_RA_ADDBOOKMARK, Anchor::Left | Anchor::Bottom); + SetAnchor(IDC_RA_PAUSE, Anchor::Right | Anchor::Bottom); + SetAnchor(IDC_RA_FREEZE, Anchor::Right | Anchor::Bottom); + + SetMinimumSize(480, 192); +} + +BOOL PointerInspectorDialog::OnInitDialog() +{ + m_bindAddress.SetControl(*this, IDC_RA_ADDRESS); + m_bindNodes.SetControl(*this, IDC_RA_FILTER_VALUE); + m_bindDescription.SetControl(*this, IDC_RA_DESCRIPTION); + m_bindFields.SetControl(*this, IDC_RA_LBX_ADDRESSES); + + return DialogBase::OnInitDialog(); +} + +BOOL PointerInspectorDialog::OnCommand(WORD nCommand) +{ + //switch (nCommand) + //{ + //case IDC_RA_ADDBOOKMARK: { + // auto* vmPointerInspector = dynamic_cast(&m_vmWindow); + // if (vmPointerInspector) + // vmPointerInspector->Find(); + + // return TRUE; + //} + + //case IDC_RA_PAUSE: { + // auto* vmPointerInspector = dynamic_cast(&m_vmWindow); + // if (vmPointerInspector) + // vmPointerInspector->BookmarkSelected(); + + // return TRUE; + //} + + //case IDC_RA_FREEZE: { + // const auto* vmPointerInspector = dynamic_cast(&m_vmWindow); + // if (vmPointerInspector) + // vmPointerInspector->ExportResults(); + + // return TRUE; + //} + //} + + return DialogBase::OnCommand(nCommand); +} + +} // namespace win32 +} // namespace ui +} // namespace ra diff --git a/src/ui/win32/PointerInspectorDialog.hh b/src/ui/win32/PointerInspectorDialog.hh new file mode 100644 index 000000000..c06a401b1 --- /dev/null +++ b/src/ui/win32/PointerInspectorDialog.hh @@ -0,0 +1,57 @@ +#ifndef RA_UI_WIN32_DLG_POINTERINSPECTOR_H +#define RA_UI_WIN32_DLG_POINTERINSPECTOR_H +#pragma once + +#include "ui/viewmodels/PointerInspectorViewModel.hh" + +#include "ui/win32/DialogBase.hh" +#include "ui/win32/IDialogPresenter.hh" + +#include "ui/win32/bindings/GridBinding.hh" +#include "ui/win32/bindings/ComboBoxBinding.hh" +#include "ui/win32/bindings/TextBoxBinding.hh" + +#include + +namespace ra { +namespace ui { +namespace win32 { + +class PointerInspectorDialog : public DialogBase +{ +public: + explicit PointerInspectorDialog(viewmodels::PointerInspectorViewModel& vmPointerInspector); + virtual ~PointerInspectorDialog() noexcept = default; + PointerInspectorDialog(const PointerInspectorDialog&) noexcept = delete; + PointerInspectorDialog& operator=(const PointerInspectorDialog&) noexcept = delete; + PointerInspectorDialog(PointerInspectorDialog&&) noexcept = delete; + PointerInspectorDialog& operator=(PointerInspectorDialog&&) noexcept = delete; + + class Presenter : public IClosableDialogPresenter + { + public: + bool IsSupported(const ra::ui::WindowViewModelBase& viewModel) noexcept override; + void ShowWindow(ra::ui::WindowViewModelBase& viewModel) override; + void ShowModal(ra::ui::WindowViewModelBase& viewModel, HWND hParentWnd) override; + void OnClosed() noexcept override; + + private: + std::unique_ptr m_pDialog; + }; + +protected: + BOOL OnInitDialog() override; + BOOL OnCommand(WORD nCommand) override; + +private: + bindings::TextBoxBinding m_bindAddress; + bindings::ComboBoxBinding m_bindNodes; + bindings::TextBoxBinding m_bindDescription; + bindings::GridBinding m_bindFields; +}; + +} // namespace win32 +} // namespace ui +} // namespace ra + +#endif // !RA_UI_WIN32_DLG_POINTERINSPECTOR_H diff --git a/src/ui/win32/bindings/GridBinding.cpp b/src/ui/win32/bindings/GridBinding.cpp index b7a05f9f7..c7ed0044b 100644 --- a/src/ui/win32/bindings/GridBinding.cpp +++ b/src/ui/win32/bindings/GridBinding.cpp @@ -110,6 +110,7 @@ void GridBinding::UpdateLayout() for (gsl::index i = 0; ra::to_unsigned(i) < m_vColumns.size(); ++i) { const auto& pColumn = *m_vColumns.at(i); + Expects(&pColumn != nullptr); switch (pColumn.GetWidthType()) { case GridColumnBinding::WidthType::Pixels: diff --git a/src/ui/win32/bindings/GridBookmarkFormatColumnBinding.hh b/src/ui/win32/bindings/GridBookmarkFormatColumnBinding.hh new file mode 100644 index 000000000..bdacf5bf6 --- /dev/null +++ b/src/ui/win32/bindings/GridBookmarkFormatColumnBinding.hh @@ -0,0 +1,77 @@ +#ifndef RA_UI_WIN32_GRIDBOOKMARKFORMATCOLUMNBINDING_H +#define RA_UI_WIN32_GRIDBOOKMARKFORMATCOLUMNBINDING_H +#pragma once + +#include "GridBinding.hh" +#include "GridLookupColumnBinding.hh" + +#include "ui\viewmodels\MemoryBookmarksViewModel.hh" + +namespace ra { +namespace ui { +namespace win32 { +namespace bindings { + +class GridBookmarkFormatColumnBinding : public ra::ui::win32::bindings::GridLookupColumnBinding +{ +public: + GridBookmarkFormatColumnBinding(const IntModelProperty& pBoundProperty, + const ra::ui::viewmodels::LookupItemViewModelCollection& vmItems) noexcept : + ra::ui::win32::bindings::GridLookupColumnBinding(pBoundProperty, vmItems) + {} + + std::wstring GetText(const ra::ui::ViewModelCollectionBase& vmItems, gsl::index nIndex) const override + { + if (IsHidden(vmItems, nIndex)) + return L""; + + return ra::ui::win32::bindings::GridLookupColumnBinding::GetText(vmItems, nIndex); + } + + bool DependsOn(const ra::ui::IntModelProperty& pProperty) const noexcept override + { + if (pProperty == ra::ui::viewmodels::MemoryBookmarksViewModel::MemoryBookmarkViewModel::SizeProperty) + return true; + + return ra::ui::win32::bindings::GridLookupColumnBinding::DependsOn(pProperty); + } + + HWND CreateInPlaceEditor(HWND hParent, InPlaceEditorInfo& pInfo) override + { + auto* pGridBinding = static_cast(pInfo.pGridBinding); + Expects(pGridBinding != nullptr); + + if (IsHidden(pGridBinding->GetItems(), pInfo.nItemIndex)) + return nullptr; + + return GridLookupColumnBinding::CreateInPlaceEditor(hParent, pInfo); + } + +private: + static bool IsHidden(const ra::ui::ViewModelCollectionBase& vmItems, gsl::index nIndex) + { + const auto nSize = ra::itoe( + vmItems.GetItemValue(nIndex, ra::ui::viewmodels::MemoryBookmarksViewModel::MemoryBookmarkViewModel::SizeProperty)); + switch (nSize) + { + case MemSize::Float: + case MemSize::FloatBigEndian: + case MemSize::Double32: + case MemSize::Double32BigEndian: + case MemSize::MBF32: + case MemSize::MBF32LE: + case MemSize::Text: + return true; + + default: + return false; + } + } +}; + +} // namespace bindings +} // namespace win32 +} // namespace ui +} // namespace ra + +#endif // !RA_UI_WIN32_GRIDBOOKMARKFORMATCOLUMNBINDING_H diff --git a/src/ui/win32/bindings/GridBookmarkValueColumnBinding.hh b/src/ui/win32/bindings/GridBookmarkValueColumnBinding.hh new file mode 100644 index 000000000..3f5c2349c --- /dev/null +++ b/src/ui/win32/bindings/GridBookmarkValueColumnBinding.hh @@ -0,0 +1,55 @@ +#ifndef RA_UI_WIN32_GRIDBOOKMARKVALUECOLUMNBINDING_H +#define RA_UI_WIN32_GRIDBOOKMARKVALUECOLUMNBINDING_H +#pragma once + +#include "GridBinding.hh" +#include "GridTextColumnBinding.hh" + +#include "ui\viewmodels\MemoryBookmarksViewModel.hh" +#include "ui\viewmodels\MessageBoxViewModel.hh" + +namespace ra { +namespace ui { +namespace win32 { +namespace bindings { + +class GridBookmarkValueColumnBinding : public ra::ui::win32::bindings::GridTextColumnBinding +{ +public: + GridBookmarkValueColumnBinding(const StringModelProperty& pBoundProperty) noexcept : + ra::ui::win32::bindings::GridTextColumnBinding(pBoundProperty) + {} + + HWND CreateInPlaceEditor(HWND hParent, InPlaceEditorInfo& pInfo) override + { + const auto& vmItems = static_cast(pInfo.pGridBinding)->GetItems(); + if (vmItems.GetItemValue(pInfo.nItemIndex, ra::ui::viewmodels::MemoryBookmarksViewModel::MemoryBookmarkViewModel::ReadOnlyProperty)) + return nullptr; + + return ra::ui::win32::bindings::GridTextColumnBinding::CreateInPlaceEditor(hParent, pInfo); + } + + bool SetText(ra::ui::ViewModelCollectionBase& vmItems, gsl::index nIndex, const std::wstring& sValue) override + { + auto* vmItem = dynamic_cast( + vmItems.GetViewModelAt(nIndex)); + if (vmItem != nullptr) + { + std::wstring sError; + if (!vmItem->SetCurrentValue(sValue, sError)) + { + ra::ui::viewmodels::MessageBoxViewModel::ShowWarningMessage(L"Invalid Input", sError); + return false; + } + } + + return true; + } +}; + +} // namespace bindings +} // namespace win32 +} // namespace ui +} // namespace ra + +#endif // !RA_UI_WIN32_GRIDBOOKMARKVALUECOLUMNBINDING_H diff --git a/tests/Exports_Tests.cpp b/tests/Exports_Tests.cpp index f2873bad8..ae72c81b0 100644 --- a/tests/Exports_Tests.cpp +++ b/tests/Exports_Tests.cpp @@ -469,7 +469,7 @@ TEST_CLASS(Exports_Tests) ra::data::context::mocks::MockUserContext mockUserContext; RA_MenuItem menu[32]; - Assert::AreEqual(17, _RA_GetPopupMenuItems(menu)); + Assert::AreEqual(18, _RA_GetPopupMenuItems(menu)); AssertMenuItem(&menu[0], IDM_RA_FILES_LOGIN, L"&Login"); AssertMenuItem(&menu[1], 0, nullptr); AssertMenuItem(&menu[2], IDM_RA_HARDCORE_MODE, L"&Hardcore Mode"); @@ -487,6 +487,7 @@ TEST_CLASS(Exports_Tests) AssertMenuItem(&menu[14], IDM_RA_PARSERICHPRESENCE, L"Rich &Presence Monitor"); AssertMenuItem(&menu[15], 0, nullptr); AssertMenuItem(&menu[16], IDM_RA_FILES_POINTERFINDER, L"Pointer &Finder"); + AssertMenuItem(&menu[17], IDM_RA_FILES_POINTERINSPECTOR, L"Pointer &Inspector"); } TEST_METHOD(TestGetPopupMenuItemsLoggedIn) @@ -496,7 +497,7 @@ TEST_CLASS(Exports_Tests) mockUserContext.Initialize("User", "TOKEN"); RA_MenuItem menu[32]; - Assert::AreEqual(23, _RA_GetPopupMenuItems(menu)); + Assert::AreEqual(24, _RA_GetPopupMenuItems(menu)); AssertMenuItem(&menu[0], IDM_RA_FILES_LOGOUT, L"Log&out"); AssertMenuItem(&menu[1], 0, nullptr); AssertMenuItem(&menu[2], IDM_RA_OPENUSERPAGE, L"Open my &User Page"); @@ -517,9 +518,10 @@ TEST_CLASS(Exports_Tests) AssertMenuItem(&menu[17], IDM_RA_PARSERICHPRESENCE, L"Rich &Presence Monitor"); AssertMenuItem(&menu[18], 0, nullptr); AssertMenuItem(&menu[19], IDM_RA_FILES_POINTERFINDER, L"Pointer &Finder"); - AssertMenuItem(&menu[20], 0, nullptr); - AssertMenuItem(&menu[21], IDM_RA_REPORTBROKENACHIEVEMENTS, L"&Report Achievement Problem"); - AssertMenuItem(&menu[22], IDM_RA_GETROMCHECKSUM, L"View Game H&ash"); + AssertMenuItem(&menu[20], IDM_RA_FILES_POINTERINSPECTOR, L"Pointer &Inspector"); + AssertMenuItem(&menu[21], 0, nullptr); + AssertMenuItem(&menu[22], IDM_RA_REPORTBROKENACHIEVEMENTS, L"&Report Achievement Problem"); + AssertMenuItem(&menu[23], IDM_RA_GETROMCHECKSUM, L"View Game H&ash"); } TEST_METHOD(TestGetPopupMenuItemsChecked) @@ -532,7 +534,7 @@ TEST_CLASS(Exports_Tests) mockConfiguration.SetFeatureEnabled(ra::services::Feature::Leaderboards, true); RA_MenuItem menu[32]; - Assert::AreEqual(23, _RA_GetPopupMenuItems(menu)); + Assert::AreEqual(24, _RA_GetPopupMenuItems(menu)); AssertMenuItem(&menu[0], IDM_RA_FILES_LOGOUT, L"Log&out"); AssertMenuItem(&menu[1], 0, nullptr); AssertMenuItem(&menu[2], IDM_RA_OPENUSERPAGE, L"Open my &User Page"); @@ -553,9 +555,10 @@ TEST_CLASS(Exports_Tests) AssertMenuItem(&menu[17], IDM_RA_PARSERICHPRESENCE, L"Rich &Presence Monitor"); AssertMenuItem(&menu[18], 0, nullptr); AssertMenuItem(&menu[19], IDM_RA_FILES_POINTERFINDER, L"Pointer &Finder"); - AssertMenuItem(&menu[20], 0, nullptr); - AssertMenuItem(&menu[21], IDM_RA_REPORTBROKENACHIEVEMENTS, L"&Report Achievement Problem"); - AssertMenuItem(&menu[22], IDM_RA_GETROMCHECKSUM, L"View Game H&ash"); + AssertMenuItem(&menu[20], IDM_RA_FILES_POINTERINSPECTOR, L"Pointer &Inspector"); + AssertMenuItem(&menu[21], 0, nullptr); + AssertMenuItem(&menu[22], IDM_RA_REPORTBROKENACHIEVEMENTS, L"&Report Achievement Problem"); + AssertMenuItem(&menu[23], IDM_RA_GETROMCHECKSUM, L"View Game H&ash"); } TEST_METHOD(TestUpdateAppTitle) diff --git a/tests/services/AchievementRuntimeExports_Tests.cpp b/tests/services/AchievementRuntimeExports_Tests.cpp index d84254769..a9e5699aa 100644 --- a/tests/services/AchievementRuntimeExports_Tests.cpp +++ b/tests/services/AchievementRuntimeExports_Tests.cpp @@ -312,7 +312,7 @@ TEST_CLASS(AchievementRuntimeExports_Tests) const rc_client_raintegration_menu_t* pMenu; pMenu = _Rcheevos_RAIntegrationGetMenu(); - Assert::AreEqual(12U, pMenu->num_items); + Assert::AreEqual(13U, pMenu->num_items); AssertMenuItem(pMenu, 0, IDM_RA_HARDCORE_MODE, "&Hardcore Mode"); AssertMenuItem(pMenu, 1, IDM_RA_NON_HARDCORE_WARNING, "Non-Hardcore &Warning"); AssertMenuSeparator(pMenu, 2); @@ -325,13 +325,14 @@ TEST_CLASS(AchievementRuntimeExports_Tests) AssertMenuItem(pMenu, 9, IDM_RA_PARSERICHPRESENCE, "Rich &Presence Monitor"); AssertMenuSeparator(pMenu, 10); AssertMenuItem(pMenu, 11, IDM_RA_FILES_POINTERFINDER, "Pointer &Finder"); + AssertMenuItem(pMenu, 12, IDM_RA_FILES_POINTERINSPECTOR, "Pointer &Inspector"); runtime.mockUserContext.Initialize("User", "ApiToken"); runtime.AssertMenuChangedEventSeen(); runtime.ResetSeenEvents(); pMenu = _Rcheevos_RAIntegrationGetMenu(); - Assert::AreEqual(18U, pMenu->num_items); + Assert::AreEqual(19U, pMenu->num_items); AssertMenuItem(pMenu, 0, IDM_RA_OPENUSERPAGE, "Open my &User Page"); AssertMenuItem(pMenu, 1, IDM_RA_OPENGAMEPAGE, "Open this &Game's Page"); AssertMenuSeparator(pMenu, 2); @@ -347,15 +348,16 @@ TEST_CLASS(AchievementRuntimeExports_Tests) AssertMenuItem(pMenu, 12, IDM_RA_PARSERICHPRESENCE, "Rich &Presence Monitor"); AssertMenuSeparator(pMenu, 13); AssertMenuItem(pMenu, 14, IDM_RA_FILES_POINTERFINDER, "Pointer &Finder"); - AssertMenuSeparator(pMenu, 15); - AssertMenuItem(pMenu, 16, IDM_RA_REPORTBROKENACHIEVEMENTS, "&Report Achievement Problem"); - AssertMenuItem(pMenu, 17, IDM_RA_GETROMCHECKSUM, "View Game H&ash"); + AssertMenuItem(pMenu, 15, IDM_RA_FILES_POINTERINSPECTOR, "Pointer &Inspector"); + AssertMenuSeparator(pMenu, 16); + AssertMenuItem(pMenu, 17, IDM_RA_REPORTBROKENACHIEVEMENTS, "&Report Achievement Problem"); + AssertMenuItem(pMenu, 18, IDM_RA_GETROMCHECKSUM, "View Game H&ash"); runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::Hardcore, true); runtime.mockConfiguration.SetFeatureEnabled(ra::services::Feature::NonHardcoreWarning, true); pMenu = _Rcheevos_RAIntegrationGetMenu(); - Assert::AreEqual(18U, pMenu->num_items); + Assert::AreEqual(19U, pMenu->num_items); AssertMenuItem(pMenu, 0, IDM_RA_OPENUSERPAGE, "Open my &User Page"); AssertMenuItem(pMenu, 1, IDM_RA_OPENGAMEPAGE, "Open this &Game's Page"); AssertMenuSeparator(pMenu, 2); @@ -371,9 +373,10 @@ TEST_CLASS(AchievementRuntimeExports_Tests) AssertMenuItem(pMenu, 12, IDM_RA_PARSERICHPRESENCE, "Rich &Presence Monitor"); AssertMenuSeparator(pMenu, 13); AssertMenuItem(pMenu, 14, IDM_RA_FILES_POINTERFINDER, "Pointer &Finder"); - AssertMenuSeparator(pMenu, 15); - AssertMenuItem(pMenu, 16, IDM_RA_REPORTBROKENACHIEVEMENTS, "&Report Achievement Problem"); - AssertMenuItem(pMenu, 17, IDM_RA_GETROMCHECKSUM, "View Game H&ash"); + AssertMenuItem(pMenu, 15, IDM_RA_FILES_POINTERINSPECTOR, "Pointer &Inspector"); + AssertMenuSeparator(pMenu, 16); + AssertMenuItem(pMenu, 17, IDM_RA_REPORTBROKENACHIEVEMENTS, "&Report Achievement Problem"); + AssertMenuItem(pMenu, 18, IDM_RA_GETROMCHECKSUM, "View Game H&ash"); } diff --git a/tests/ui/viewmodels/IntegrationMenuViewModel_Tests.cpp b/tests/ui/viewmodels/IntegrationMenuViewModel_Tests.cpp index 4a1f60c8b..ca6c974c1 100644 --- a/tests/ui/viewmodels/IntegrationMenuViewModel_Tests.cpp +++ b/tests/ui/viewmodels/IntegrationMenuViewModel_Tests.cpp @@ -134,7 +134,7 @@ TEST_CLASS(IntegrationMenuViewModel_Tests) menu.BuildMenu(); - menu.AssertMenuSize(17); + menu.AssertMenuSize(18); menu.AssertMenuItem(0, IDM_RA_FILES_LOGIN, L"&Login"); menu.AssertMenuSeparator(1); menu.AssertMenuItem(2, IDM_RA_HARDCORE_MODE, L"&Hardcore Mode"); @@ -152,6 +152,7 @@ TEST_CLASS(IntegrationMenuViewModel_Tests) menu.AssertMenuItem(14, IDM_RA_PARSERICHPRESENCE, L"Rich &Presence Monitor"); menu.AssertMenuSeparator(15); menu.AssertMenuItem(16, IDM_RA_FILES_POINTERFINDER, L"Pointer &Finder"); + menu.AssertMenuItem(17, IDM_RA_FILES_POINTERINSPECTOR, L"Pointer &Inspector"); } TEST_METHOD(TestBuildMenuLoggedIn) @@ -161,7 +162,7 @@ TEST_CLASS(IntegrationMenuViewModel_Tests) menu.BuildMenu(); - menu.AssertMenuSize(23); + menu.AssertMenuSize(24); menu.AssertMenuItem(0, IDM_RA_FILES_LOGOUT, L"Log&out"); menu.AssertMenuSeparator(1); menu.AssertMenuItem(2, IDM_RA_OPENUSERPAGE, L"Open my &User Page"); @@ -182,9 +183,10 @@ TEST_CLASS(IntegrationMenuViewModel_Tests) menu.AssertMenuItem(17, IDM_RA_PARSERICHPRESENCE, L"Rich &Presence Monitor"); menu.AssertMenuSeparator(18); menu.AssertMenuItem(19, IDM_RA_FILES_POINTERFINDER, L"Pointer &Finder"); - menu.AssertMenuSeparator(20); - menu.AssertMenuItem(21, IDM_RA_REPORTBROKENACHIEVEMENTS, L"&Report Achievement Problem"); - menu.AssertMenuItem(22, IDM_RA_GETROMCHECKSUM, L"View Game H&ash"); + menu.AssertMenuItem(20, IDM_RA_FILES_POINTERINSPECTOR, L"Pointer &Inspector"); + menu.AssertMenuSeparator(21); + menu.AssertMenuItem(22, IDM_RA_REPORTBROKENACHIEVEMENTS, L"&Report Achievement Problem"); + menu.AssertMenuItem(23, IDM_RA_GETROMCHECKSUM, L"View Game H&ash"); } TEST_METHOD(TestBuildMenuOffline) @@ -194,7 +196,7 @@ TEST_CLASS(IntegrationMenuViewModel_Tests) menu.BuildMenu(); - menu.AssertMenuSize(17); + menu.AssertMenuSize(18); menu.AssertMenuItem(0, IDM_RA_HARDCORE_MODE, L"&Hardcore Mode"); menu.AssertMenuItem(1, IDM_RA_NON_HARDCORE_WARNING, L"Non-Hardcore &Warning"); menu.AssertMenuSeparator(2); @@ -210,8 +212,9 @@ TEST_CLASS(IntegrationMenuViewModel_Tests) menu.AssertMenuItem(12, IDM_RA_PARSERICHPRESENCE, L"Rich &Presence Monitor"); menu.AssertMenuSeparator(13); menu.AssertMenuItem(14, IDM_RA_FILES_POINTERFINDER, L"Pointer &Finder"); - menu.AssertMenuSeparator(15); - menu.AssertMenuItem(16, IDM_RA_GETROMCHECKSUM, L"View Game H&ash"); + menu.AssertMenuItem(15, IDM_RA_FILES_POINTERINSPECTOR, L"Pointer &Inspector"); + menu.AssertMenuSeparator(16); + menu.AssertMenuItem(17, IDM_RA_GETROMCHECKSUM, L"View Game H&ash"); } TEST_METHOD(TestLoginHardcoreValidClient) @@ -562,6 +565,34 @@ TEST_CLASS(IntegrationMenuViewModel_Tests) menu.AssertShowWindow(IDM_RA_PARSERICHPRESENCE, false, "", DialogResult::None); } + TEST_METHOD(TestShowPointerFinderHardcoreAbort) + { + IntegrationMenuViewModelHarness menu; + menu.AssertShowWindow( + IDM_RA_FILES_POINTERFINDER, true, "find pointers", DialogResult::No); + } + + TEST_METHOD(TestShowPointerFinderNonHardcore) + { + IntegrationMenuViewModelHarness menu; + menu.AssertShowWindow( + IDM_RA_FILES_POINTERFINDER, false, "find pointer", DialogResult::None); + } + + TEST_METHOD(TestShowPointerInspectorHardcoreAbort) + { + IntegrationMenuViewModelHarness menu; + menu.AssertShowWindow( + IDM_RA_FILES_POINTERINSPECTOR, true, "inspect pointers", DialogResult::No); + } + + TEST_METHOD(TestShowPointerInspectorNonHardcore) + { + IntegrationMenuViewModelHarness menu; + menu.AssertShowWindow( + IDM_RA_FILES_POINTERINSPECTOR, false, "inspect pointers", DialogResult::None); + } + TEST_METHOD(TestOpenAllHardcore) { IntegrationMenuViewModelHarness menu; diff --git a/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp index 06de02d2e..76140e714 100644 --- a/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp +++ b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp @@ -26,6 +26,8 @@ namespace ui { namespace viewmodels { namespace tests { +constexpr int RootNodeID = -1; + TEST_CLASS(PointerInspectorViewModel_Tests) { private: @@ -60,11 +62,33 @@ TEST_CLASS(PointerInspectorViewModel_Tests) PointerInspectorViewModelHarness(PointerInspectorViewModelHarness&&) noexcept = delete; PointerInspectorViewModelHarness& operator=(PointerInspectorViewModelHarness&&) noexcept = delete; + void DoFrame() override + { + mockGameContext.DoFrame(); // ensure note pointers get updated + PointerInspectorViewModel::DoFrame(); + } + const std::wstring* FindCodeNote(ra::ByteAddress nAddress) const { const auto* pCodeNotes = mockGameContext.Assets().FindCodeNotes(); return (pCodeNotes != nullptr) ? pCodeNotes->FindCodeNote(nAddress) : nullptr; } + + void AssertField(gsl::index nIndex, int32_t nOffset, ra::ByteAddress nAddress, + const std::wstring& sOffset, const std::wstring& sDescription, + MemSize nSize, MemFormat nFormat, const std::wstring& sCurrentValue) + { + const auto *pField = Fields().GetItemAt(nIndex); + Assert::IsNotNull(pField); + Ensures(pField != nullptr); + Assert::AreEqual(nOffset, pField->m_nOffset); + Assert::AreEqual(nAddress, pField->GetAddress()); + Assert::AreEqual(sOffset, pField->GetOffset()); + Assert::AreEqual(sDescription, pField->GetDescription()); + Assert::AreEqual(nSize, pField->GetSize()); + Assert::AreEqual(nFormat, pField->GetFormat()); + Assert::AreEqual(sCurrentValue, pField->GetCurrentValue()); + } }; @@ -147,25 +171,111 @@ TEST_CLASS(PointerInspectorViewModel_Tests) Assert::AreEqual({ 2U }, inspector.Fields().Count()); - const auto* pField = inspector.Fields().GetItemAt(0); - Expects(pField != nullptr); - Assert::AreEqual(4, pField->m_nOffset); - Assert::AreEqual({ 16U }, pField->GetAddress()); - Assert::AreEqual(std::wstring(L"+0004"), pField->GetOffset()); - Assert::AreEqual(std::wstring(L"[32-bit] Current HP"), pField->GetDescription()); - Assert::AreEqual(MemSize::ThirtyTwoBit, pField->GetSize()); - Assert::AreEqual(ra::MemFormat::Hex, pField->GetFormat()); - Assert::AreEqual(std::wstring(L"00000010"), pField->GetCurrentValue()); - - pField = inspector.Fields().GetItemAt(1); - Expects(pField != nullptr); - Assert::AreEqual(8, pField->m_nOffset); - Assert::AreEqual({ 20U }, pField->GetAddress()); - Assert::AreEqual(std::wstring(L"+0008"), pField->GetOffset()); - Assert::AreEqual(std::wstring(L"[32-bit] Max HP"), pField->GetDescription()); - Assert::AreEqual(MemSize::ThirtyTwoBit, pField->GetSize()); - Assert::AreEqual(ra::MemFormat::Hex, pField->GetFormat()); - Assert::AreEqual(std::wstring(L"00000014"), pField->GetCurrentValue()); + inspector.AssertField(0, 4, 16U, L"+0004", L"[32-bit] Current HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000010"); + inspector.AssertField(1, 8, 20U, L"+0008", L"[32-bit] Max HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000014"); + } + + TEST_METHOD(TestSelectedNode) + { + PointerInspectorViewModelHarness inspector; + inspector.mockGameContext.SetGameId(1); + inspector.mockGameContext.NotifyActiveGameChanged(); // enable note support + + std::array memory = {}; + for (uint8_t i = 4; i < memory.size(); i += 4) + memory.at(i) = i + 8; + inspector.mockEmulatorContext.MockMemory(memory); + + inspector.SetCurrentAddress({4U}); + inspector.mockGameContext.Assets().FindCodeNotes()->SetCodeNote({4U}, + L"[8-bit pointer] Player data\n" + L"+0x00: [8-bit pointer] Row 1\n" + L".+0x00: [8-bit] Column 1a\n" + L".+0x04: [8-bit pointer] Column 1b\n" + L"..+0x00: [8-bit] Column 1a\n" + L".+0x08: [8-bit] Column 1c\n" + L"+0x08: [8-bit pointer] Row 2\n" + L"+0x10: [8-bit pointer] Row 3\n" + L"+0x18: Generic data" + ); + + Assert::AreEqual({4U}, inspector.GetCurrentAddress()); + Assert::AreEqual(std::wstring(L"0x0004"), inspector.GetCurrentAddressText()); + Assert::AreEqual(std::wstring(L"[8-bit pointer] Player data"), inspector.GetCurrentAddressNote()); + + Assert::AreEqual({5U}, inspector.Nodes().Count()); + Assert::AreEqual(RootNodeID, inspector.Nodes().GetItemAt(0)->GetId()); + Assert::AreEqual(std::wstring(L"[8-bit pointer] Player data"), inspector.Nodes().GetItemAt(0)->GetLabel()); + Assert::AreEqual(0x00000000, inspector.Nodes().GetItemAt(1)->GetId()); + Assert::AreEqual(std::wstring(L"+0000 | [8-bit pointer] Row 1"), inspector.Nodes().GetItemAt(1)->GetLabel()); + Assert::AreEqual(0x01000004, inspector.Nodes().GetItemAt(2)->GetId()); + Assert::AreEqual(std::wstring(L" +0004 | [8-bit pointer] Column 1b"), inspector.Nodes().GetItemAt(2)->GetLabel()); + Assert::AreEqual(0x00000008, inspector.Nodes().GetItemAt(3)->GetId()); + Assert::AreEqual(std::wstring(L"+0008 | [8-bit pointer] Row 2"), inspector.Nodes().GetItemAt(3)->GetLabel()); + Assert::AreEqual(0x00000010, inspector.Nodes().GetItemAt(4)->GetId()); + Assert::AreEqual(std::wstring(L"+0010 | [8-bit pointer] Row 3"), inspector.Nodes().GetItemAt(4)->GetLabel()); + + Assert::AreEqual(RootNodeID, inspector.GetSelectedNode()); + Assert::AreEqual(std::wstring(L"[8-bit pointer] Player data"), inspector.GetCurrentAddressNote()); + Assert::AreEqual({4U}, inspector.Fields().Count()); + inspector.AssertField(0, 0, 0x0CU, L"+0000", L"[8-bit pointer] Row 1", MemSize::EightBit, MemFormat::Hex, L"14"); + inspector.AssertField(1, 8, 0x14U, L"+0008", L"[8-bit pointer] Row 2", MemSize::EightBit, MemFormat::Hex, L"1c"); + inspector.AssertField(2, 16, 0x1CU, L"+0010", L"[8-bit pointer] Row 3", MemSize::EightBit, MemFormat::Hex, L"24"); + inspector.AssertField(3, 24, 0x24U, L"+0018", L"Generic data", MemSize::EightBit, MemFormat::Hex, L"2c"); + + inspector.SetSelectedNode(0x00000008); + Assert::AreEqual(0x00000008, inspector.GetSelectedNode()); + Assert::AreEqual(std::wstring(L"[8-bit pointer] Row 2"), inspector.GetCurrentAddressNote()); + Assert::AreEqual({0U}, inspector.Fields().Count()); + + inspector.SetSelectedNode(0x00000000); + Assert::AreEqual(0x00000000, inspector.GetSelectedNode()); + Assert::AreEqual(std::wstring(L"[8-bit pointer] Row 1"), inspector.GetCurrentAddressNote()); + Assert::AreEqual({3U}, inspector.Fields().Count()); + inspector.AssertField(0, 0, 0x14U, L"+0000", L"[8-bit] Column 1a", MemSize::EightBit, MemFormat::Hex, L"1c"); + inspector.AssertField(1, 4, 0x18U, L"+0004", L"[8-bit pointer] Column 1b", MemSize::EightBit, MemFormat::Hex, L"20"); + inspector.AssertField(2, 8, 0x1CU, L"+0008", L"[8-bit] Column 1c", MemSize::EightBit, MemFormat::Hex, L"24"); + } + + TEST_METHOD(TestDoFrame) + { + PointerInspectorViewModelHarness inspector; + inspector.mockGameContext.SetGameId(1); + inspector.mockGameContext.NotifyActiveGameChanged(); // enable note support + + std::array memory = {}; + for (uint8_t i = 8; i < memory.size(); i += 4) + memory.at(i) = i; + inspector.mockEmulatorContext.MockMemory(memory); + memory.at(4) = 12; + + inspector.SetCurrentAddress({4U}); + inspector.mockGameContext.Assets().FindCodeNotes()->SetCodeNote({4U}, + L"[32-bit pointer] Player data\n" + L"+4: [32-bit] Current HP\n" + L"+8: [32-bit] Max HP"); + + Assert::AreEqual({4U}, inspector.GetCurrentAddress()); + Assert::AreEqual(std::wstring(L"0x0004"), inspector.GetCurrentAddressText()); + Assert::AreEqual(std::wstring(L"[32-bit pointer] Player data"), inspector.GetCurrentAddressNote()); + + Assert::AreEqual({2U}, inspector.Fields().Count()); + + inspector.AssertField(0, 4, 16U, L"+0004", L"[32-bit] Current HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000010"); + inspector.AssertField(1, 8, 20U, L"+0008", L"[32-bit] Max HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000014"); + + // data changes + memory.at(17) = 1; + memory.at(20) = 99; + inspector.DoFrame(); + inspector.AssertField(0, 4, 16U, L"+0004", L"[32-bit] Current HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000110"); + inspector.AssertField(1, 8, 20U, L"+0008", L"[32-bit] Max HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000063"); + + // pointer changes + memory.at(4) = 13; + inspector.DoFrame(); + inspector.AssertField(0, 4, 17U, L"+0004", L"[32-bit] Current HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"63000001"); + inspector.AssertField(1, 8, 21U, L"+0008", L"[32-bit] Max HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"18000000"); } }; From 86d1a60f1ea27e5f1693f74f55416d50481bfc67 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Mon, 25 Nov 2024 14:06:59 -0700 Subject: [PATCH 4/9] add Copy Def button --- src/RA_Shared.rc | 1 + .../viewmodels/PointerInspectorViewModel.cpp | 133 ++++++++++++++++-- .../viewmodels/PointerInspectorViewModel.hh | 4 + src/ui/win32/PointerInspectorDialog.cpp | 21 ++- 4 files changed, 143 insertions(+), 16 deletions(-) diff --git a/src/RA_Shared.rc b/src/RA_Shared.rc index 377dc5990..16d0b6796 100644 --- a/src/RA_Shared.rc +++ b/src/RA_Shared.rc @@ -276,6 +276,7 @@ BEGIN EDITTEXT IDC_RA_DESCRIPTION,55,18,283,12,ES_AUTOHSCROLL CONTROL "",IDC_RA_LBX_ADDRESSES,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_CLIPCHILDREN | WS_BORDER,4,32,334,116 PUSHBUTTON "Bookmark Selected",IDC_RA_ADDBOOKMARK,4,149,70,14,BS_MULTILINE + PUSHBUTTON "Copy Def",IDC_RA_COPY_ALL,73,149,70,13 PUSHBUTTON "Pause",IDC_RA_PAUSE,236,149,50,14,BS_MULTILINE PUSHBUTTON "Freeze",IDC_RA_FREEZE,288,149,50,14,BS_MULTILINE END diff --git a/src/ui/viewmodels/PointerInspectorViewModel.cpp b/src/ui/viewmodels/PointerInspectorViewModel.cpp index 2e0ff94a4..1a7462653 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.cpp +++ b/src/ui/viewmodels/PointerInspectorViewModel.cpp @@ -2,6 +2,8 @@ #include "RA_Defs.h" +#include "data\context\ConsoleContext.hh" + #include "services\ServiceLocator.hh" namespace ra { @@ -96,31 +98,35 @@ void PointerInspectorViewModel::OnCurrentAddressChanged(ra::ByteAddress nNewAddr } } -const ra::data::models::CodeNoteModel* PointerInspectorViewModel::FindNestedCodeNoteModel( - const ra::data::models::CodeNoteModel& pRootNote, int nNewNode) +void PointerInspectorViewModel::GetPointerChain(gsl::index nIndex, std::stack& sChain) const { - auto nIndex = m_vNodes.FindItemIndex(LookupItemViewModel::IdProperty, nNewNode); - if (nIndex == -1) - return nullptr; - const auto* pNode = m_vNodes.GetItemAt(nIndex); Expects(pNode != nullptr); - std::stack sChain; sChain.push(pNode); - auto nDepth = nNewNode >> 24; + auto nDepth = pNode->GetId() >> 24; while (nDepth > 0 && nIndex > 1) { const auto* pNestedNode = m_vNodes.GetItemAt(--nIndex); - const auto nNestedDepth = pNode->GetId() >> 24; + const auto nNestedDepth = pNestedNode->GetId() >> 24; if (nNestedDepth < nDepth) { sChain.push(pNestedNode); nDepth = nNestedDepth; - break; } } +} + +const ra::data::models::CodeNoteModel* PointerInspectorViewModel::FindNestedCodeNoteModel( + const ra::data::models::CodeNoteModel& pRootNote, int nNewNode) +{ + auto nIndex = m_vNodes.FindItemIndex(LookupItemViewModel::IdProperty, nNewNode); + if (nIndex == -1) + return nullptr; + + std::stack sChain; + GetPointerChain(nIndex, sChain); const auto* pParentNote = &pRootNote; do @@ -284,6 +290,113 @@ void PointerInspectorViewModel::DoFrame() UpdateValues(); } +void PointerInspectorViewModel::CopyDefinition() const +{ + const auto sDefinition = GetDefinition(); +} + +static void AppendMaskedPointer(ra::StringBuilder& builder, uint32_t nMaskBits, MemSize nSize, const std::string& sAddress) +{ + const auto nSizeBits = ra::data::MemSizeBits(nSize); + if (nSizeBits < nMaskBits) + nMaskBits = nSizeBits; + + const auto nBytes = (nMaskBits + 7) / 8; + switch (nBytes) + { + default: builder.Append('H'); break; + case 2: builder.Append(' '); break; + case 3: builder.Append('W'); break; + case 4: builder.Append('X'); break; + } + + builder.AppendSubString(sAddress.c_str() + 2, sAddress.length() - 2); + + if (nMaskBits != nBytes * 8) + { + builder.Append('&'); + builder.Append((1 << nMaskBits) - 1); + } +} + +static void AppendPointer(ra::StringBuilder& builder, const ra::data::models::CodeNoteModel* pNote) +{ + MemSize nSize; + uint32_t nMask; + uint32_t nOffset; + + const auto& pConsoleContext = ra::services::ServiceLocator::Get(); + if (!pConsoleContext.GetRealAddressConversion(&nSize, &nMask, &nOffset)) + { + nSize = pNote->GetMemSize(); + nMask = 0xFFFFFFFF; + nOffset = pConsoleContext.RealAddressFromByteAddress(0); + } + + builder.Append("I:0x"); + switch (nSize) + { + case MemSize::ThirtyTwoBit: builder.Append('X'); break; + case MemSize::TwentyFourBit: builder.Append('W'); break; + case MemSize::SixteenBit: builder.Append(' '); break; + case MemSize::EightBit: builder.Append('H'); break; + case MemSize::ThirtyTwoBitBigEndian: builder.Append('G'); break; + case MemSize::SixteenBitBigEndian: builder.Append('I'); break; + case MemSize::TwentyFourBitBigEndian:builder.Append('J'); break; + } + + const auto sAddress = ra::ByteAddressToString(pNote->GetPointerAddress()); + builder.Append(sAddress.c_str() + 2, sAddress.length() - 2); + + if (nOffset != 0) + { + builder.Append('-'); + builder.Append(nOffset); + } + else if (nMask != 0xFFFFFFFF && nMask != ((1 << ra::data::MemSizeBits(nSize)) - 1)) + { + builder.Append('&'); + builder.Append(nMask); + } +} + +std::string PointerInspectorViewModel::GetDefinition() const +{ + ra::StringBuilder builder; + + const auto& pGameContext = ra::services::ServiceLocator::Get(); + const auto* pCodeNotes = pGameContext.Assets().FindCodeNotes(); + if (pCodeNotes != nullptr) + { + const auto* pRootNote = pCodeNotes->FindCodeNoteModel(GetCurrentAddress()); + + std::stack sChain; + GetPointerChain(GetSelectedNode(), sChain); + + const auto* pParentNote = pRootNote; + builder.Append("A:"); + AppendPointer(builder, pRootNote); + do + { + const auto* pNestedNode = sChain.top(); + const auto nOffset = pNestedNode->GetId() & 0x00FFFFFF; + + const auto* pNestedNote = pParentNote->GetPointerNoteAtOffset(nOffset); + if (!pNestedNote) + return builder.ToString(); + + AppendPointer(builder, pNestedNote); + + pParentNote = pNestedNote; + sChain.pop(); + } while (!sChain.empty()); + + // TODO: append field + } + + return builder.ToString(); +} + } // namespace viewmodels } // namespace ui } // namespace ra diff --git a/src/ui/viewmodels/PointerInspectorViewModel.hh b/src/ui/viewmodels/PointerInspectorViewModel.hh index b22198f08..d081b517f 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.hh +++ b/src/ui/viewmodels/PointerInspectorViewModel.hh @@ -125,6 +125,8 @@ public: void DoFrame() override; + void CopyDefinition() const; + protected: void OnValueChanged(const IntModelProperty::ChangeArgs& args) override; void OnValueChanged(const StringModelProperty::ChangeArgs& args) override; @@ -139,8 +141,10 @@ private: void LoadNote(const ra::data::models::CodeNoteModel* pNote); void LoadNodes(const ra::data::models::CodeNoteModel* pNote); const ra::data::models::CodeNoteModel* FindNestedCodeNoteModel(const ra::data::models::CodeNoteModel& pRootNote, int nNewNode); + void GetPointerChain(gsl::index nIndex, std::stack& sChain) const; void SyncField(StructFieldViewModel& pFieldViewModel, const ra::data::models::CodeNoteModel& pOffsetNote); void UpdateValues(); + std::string GetDefinition() const; LookupItemViewModelCollection m_vNodes; ViewModelCollection m_vFields; diff --git a/src/ui/win32/PointerInspectorDialog.cpp b/src/ui/win32/PointerInspectorDialog.cpp index ba781cb73..621b6d3f7 100644 --- a/src/ui/win32/PointerInspectorDialog.cpp +++ b/src/ui/win32/PointerInspectorDialog.cpp @@ -116,6 +116,7 @@ PointerInspectorDialog::PointerInspectorDialog(PointerInspectorViewModel& vmPoin SetAnchor(IDC_RA_ADDBOOKMARK, Anchor::Left | Anchor::Bottom); + SetAnchor(IDC_RA_COPY_ALL, Anchor::Left | Anchor::Bottom); SetAnchor(IDC_RA_PAUSE, Anchor::Right | Anchor::Bottom); SetAnchor(IDC_RA_FREEZE, Anchor::Right | Anchor::Bottom); @@ -134,20 +135,28 @@ BOOL PointerInspectorDialog::OnInitDialog() BOOL PointerInspectorDialog::OnCommand(WORD nCommand) { - //switch (nCommand) - //{ + switch (nCommand) + { //case IDC_RA_ADDBOOKMARK: { // auto* vmPointerInspector = dynamic_cast(&m_vmWindow); // if (vmPointerInspector) - // vmPointerInspector->Find(); + // vmPointerInspector->AddBookmark(); // return TRUE; //} + case IDC_RA_COPY_ALL: { + auto* vmPointerInspector = dynamic_cast(&m_vmWindow); + if (vmPointerInspector) + vmPointerInspector->CopyDefinition(); + + return TRUE; + } + //case IDC_RA_PAUSE: { // auto* vmPointerInspector = dynamic_cast(&m_vmWindow); // if (vmPointerInspector) - // vmPointerInspector->BookmarkSelected(); + // vmPointerInspector->TogglePause(); // return TRUE; //} @@ -155,11 +164,11 @@ BOOL PointerInspectorDialog::OnCommand(WORD nCommand) //case IDC_RA_FREEZE: { // const auto* vmPointerInspector = dynamic_cast(&m_vmWindow); // if (vmPointerInspector) - // vmPointerInspector->ExportResults(); + // vmPointerInspector->ToggleFreeze(); // return TRUE; //} - //} + } return DialogBase::OnCommand(nCommand); } From c894c67e736fdfd1b3b98a9bce508f269b58a76c Mon Sep 17 00:00:00 2001 From: Jamiras Date: Mon, 23 Dec 2024 15:43:41 -0700 Subject: [PATCH 5/9] add CopyDefinition --- src/RA_Shared.rc | 2 +- src/data/ModelCollectionBase.hh | 17 +++ src/ui/viewmodels/MemoryBookmarksViewModel.hh | 5 + .../viewmodels/PointerInspectorViewModel.cpp | 139 +++++++----------- .../PointerInspectorViewModel_Tests.cpp | 51 +++++++ 5 files changed, 124 insertions(+), 90 deletions(-) diff --git a/src/RA_Shared.rc b/src/RA_Shared.rc index 16d0b6796..91c02feaf 100644 --- a/src/RA_Shared.rc +++ b/src/RA_Shared.rc @@ -276,7 +276,7 @@ BEGIN EDITTEXT IDC_RA_DESCRIPTION,55,18,283,12,ES_AUTOHSCROLL CONTROL "",IDC_RA_LBX_ADDRESSES,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_CLIPCHILDREN | WS_BORDER,4,32,334,116 PUSHBUTTON "Bookmark Selected",IDC_RA_ADDBOOKMARK,4,149,70,14,BS_MULTILINE - PUSHBUTTON "Copy Def",IDC_RA_COPY_ALL,73,149,70,13 + PUSHBUTTON "Copy Chain",IDC_RA_COPY_ALL,73,149,70,14 PUSHBUTTON "Pause",IDC_RA_PAUSE,236,149,50,14,BS_MULTILINE PUSHBUTTON "Freeze",IDC_RA_FREEZE,288,149,50,14,BS_MULTILINE END diff --git a/src/data/ModelCollectionBase.hh b/src/data/ModelCollectionBase.hh index fe9a575d8..33b5f9de8 100644 --- a/src/data/ModelCollectionBase.hh +++ b/src/data/ModelCollectionBase.hh @@ -197,6 +197,23 @@ public: return -1; } + /// + /// Finds the index of the first item where the specified property has the specified value. + /// + /// The property to query. + /// The value to find. + /// Index of the first matching item, -1 if not found. + gsl::index FindItemIndex(const BoolModelProperty& pProperty, bool bValue) const + { + for (gsl::index nIndex = 0; nIndex < gsl::narrow(m_nSize); ++nIndex) + { + if (m_vItems.at(nIndex)->GetValue(pProperty) == bValue) + return nIndex; + } + + return -1; + } + /// /// Calls the OnBeginModelCollectionUpdate method of any attached NotifyTargets. /// diff --git a/src/ui/viewmodels/MemoryBookmarksViewModel.hh b/src/ui/viewmodels/MemoryBookmarksViewModel.hh index 38afa4a28..1bbd8bf38 100644 --- a/src/ui/viewmodels/MemoryBookmarksViewModel.hh +++ b/src/ui/viewmodels/MemoryBookmarksViewModel.hh @@ -135,6 +135,11 @@ public: /// bool SetCurrentValue(const std::wstring& sValue, _Out_ std::wstring& sError); + /// + /// Gets the unformatted current value of the bookmarked address. + /// + uint32_t GetCurrentValueRaw() const { return m_nValue; } + /// /// The for the previous value of the bookmarked address. /// diff --git a/src/ui/viewmodels/PointerInspectorViewModel.cpp b/src/ui/viewmodels/PointerInspectorViewModel.cpp index 1a7462653..32d9e422e 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.cpp +++ b/src/ui/viewmodels/PointerInspectorViewModel.cpp @@ -4,6 +4,8 @@ #include "data\context\ConsoleContext.hh" +#include "services\AchievementLogicSerializer.hh" +#include "services\IClipboard.hh" #include "services\ServiceLocator.hh" namespace ra { @@ -82,7 +84,8 @@ void PointerInspectorViewModel::OnCurrentAddressChanged(ra::ByteAddress nNewAddr const auto* pCodeNotes = pGameContext.Assets().FindCodeNotes(); if (pCodeNotes != nullptr) { - SetSelectedNode(RootNodeID); + // select an invalid node to force LoadNodes to select the new root node after it's been updated + SetSelectedNode(-2); const auto* pNote = pCodeNotes->FindCodeNoteModel(nNewAddress); if (pNote) @@ -261,7 +264,7 @@ void PointerInspectorViewModel::LoadNodes(const ra::data::models::CodeNoteModel* // if the selected item is no longer available, select the root node if (m_vNodes.FindItemIndex(LookupItemViewModel::IdProperty, nSelectedNode) == -1) - SetSelectedNode(0); + SetSelectedNode(RootNodeID); } void PointerInspectorViewModel::UpdateValues() @@ -293,108 +296,66 @@ void PointerInspectorViewModel::DoFrame() void PointerInspectorViewModel::CopyDefinition() const { const auto sDefinition = GetDefinition(); + ra::services::ServiceLocator::Get().SetText(ra::Widen(sDefinition)); } -static void AppendMaskedPointer(ra::StringBuilder& builder, uint32_t nMaskBits, MemSize nSize, const std::string& sAddress) -{ - const auto nSizeBits = ra::data::MemSizeBits(nSize); - if (nSizeBits < nMaskBits) - nMaskBits = nSizeBits; - - const auto nBytes = (nMaskBits + 7) / 8; - switch (nBytes) - { - default: builder.Append('H'); break; - case 2: builder.Append(' '); break; - case 3: builder.Append('W'); break; - case 4: builder.Append('X'); break; - } - - builder.AppendSubString(sAddress.c_str() + 2, sAddress.length() - 2); - - if (nMaskBits != nBytes * 8) - { - builder.Append('&'); - builder.Append((1 << nMaskBits) - 1); - } -} - -static void AppendPointer(ra::StringBuilder& builder, const ra::data::models::CodeNoteModel* pNote) +std::string PointerInspectorViewModel::GetDefinition() const { - MemSize nSize; - uint32_t nMask; - uint32_t nOffset; - - const auto& pConsoleContext = ra::services::ServiceLocator::Get(); - if (!pConsoleContext.GetRealAddressConversion(&nSize, &nMask, &nOffset)) - { - nSize = pNote->GetMemSize(); - nMask = 0xFFFFFFFF; - nOffset = pConsoleContext.RealAddressFromByteAddress(0); - } + std::string sBuffer; - builder.Append("I:0x"); - switch (nSize) - { - case MemSize::ThirtyTwoBit: builder.Append('X'); break; - case MemSize::TwentyFourBit: builder.Append('W'); break; - case MemSize::SixteenBit: builder.Append(' '); break; - case MemSize::EightBit: builder.Append('H'); break; - case MemSize::ThirtyTwoBitBigEndian: builder.Append('G'); break; - case MemSize::SixteenBitBigEndian: builder.Append('I'); break; - case MemSize::TwentyFourBitBigEndian:builder.Append('J'); break; - } + const auto nSelectedFieldIndex = Fields().FindItemIndex(LookupItemViewModel::IsSelectedProperty, true); + if (nSelectedFieldIndex == -1) + return sBuffer; - const auto sAddress = ra::ByteAddressToString(pNote->GetPointerAddress()); - builder.Append(sAddress.c_str() + 2, sAddress.length() - 2); - - if (nOffset != 0) - { - builder.Append('-'); - builder.Append(nOffset); - } - else if (nMask != 0xFFFFFFFF && nMask != ((1 << ra::data::MemSizeBits(nSize)) - 1)) - { - builder.Append('&'); - builder.Append(nMask); - } -} - -std::string PointerInspectorViewModel::GetDefinition() const -{ - ra::StringBuilder builder; + const auto nSelectedNodeIndex = Nodes().FindItemIndex(LookupItemViewModel::IdProperty, GetSelectedNode()); + if (nSelectedNodeIndex == -1) + return sBuffer; const auto& pGameContext = ra::services::ServiceLocator::Get(); const auto* pCodeNotes = pGameContext.Assets().FindCodeNotes(); - if (pCodeNotes != nullptr) - { - const auto* pRootNote = pCodeNotes->FindCodeNoteModel(GetCurrentAddress()); + if (pCodeNotes == nullptr) + return sBuffer; - std::stack sChain; - GetPointerChain(GetSelectedNode(), sChain); + std::stack sChain; + GetPointerChain(nSelectedNodeIndex, sChain); - const auto* pParentNote = pRootNote; - builder.Append("A:"); - AppendPointer(builder, pRootNote); - do - { - const auto* pNestedNode = sChain.top(); - const auto nOffset = pNestedNode->GetId() & 0x00FFFFFF; + const auto* pRootNote = pCodeNotes->FindCodeNoteModel(GetCurrentAddress()); + const auto* pParentNote = pRootNote; + ra::services::AchievementLogicSerializer::AppendConditionType(sBuffer, ra::services::TriggerConditionType::AddAddress); + ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, ra::services::TriggerOperandType::Address, + pRootNote->GetMemSize(), pRootNote->GetAddress()); + ra::services::AchievementLogicSerializer::AppendConditionSeparator(sBuffer); - const auto* pNestedNote = pParentNote->GetPointerNoteAtOffset(nOffset); - if (!pNestedNote) - return builder.ToString(); + do + { + const auto* pNestedNode = sChain.top(); + const auto nId = pNestedNode->GetId(); + if (nId == RootNodeID) + break; + + const auto nOffset = nId & 0x00FFFFFF; + const auto* pNestedNote = pParentNote->GetPointerNoteAtOffset(nOffset); + if (!pNestedNote) + return sBuffer; - AppendPointer(builder, pNestedNote); + ra::services::AchievementLogicSerializer::AppendConditionType(sBuffer, ra::services::TriggerConditionType::AddAddress); + ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, ra::services::TriggerOperandType::Address, + pNestedNote->GetMemSize(), pNestedNote->GetAddress()); + ra::services::AchievementLogicSerializer::AppendConditionSeparator(sBuffer); - pParentNote = pNestedNote; - sChain.pop(); - } while (!sChain.empty()); + pParentNote = pNestedNote; + sChain.pop(); + } while (!sChain.empty()); - // TODO: append field - } + const auto* vmField = Fields().GetItemAt(nSelectedFieldIndex); + Expects(vmField != nullptr); + ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, ra::services::TriggerOperandType::Address, + vmField->GetSize(), ra::to_unsigned(vmField->m_nOffset)); + ra::services::AchievementLogicSerializer::AppendOperator(sBuffer, ra::services::TriggerOperatorType::Equals); + ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, ra::services::TriggerOperandType::Value, + MemSize::ThirtyTwoBit, vmField->GetCurrentValueRaw()); - return builder.ToString(); + return sBuffer; } } // namespace viewmodels diff --git a/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp index 76140e714..11fdd1ad6 100644 --- a/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp +++ b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp @@ -8,6 +8,7 @@ #include "tests\ui\UIAsserts.hh" #include "tests\RA_UnitTestHelpers.h" +#include "tests\mocks\MockClipboard.hh" #include "tests\mocks\MockConfiguration.hh" #include "tests\mocks\MockConsoleContext.hh" #include "tests\mocks\MockDesktop.hh" @@ -38,6 +39,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) ra::data::context::mocks::MockGameContext mockGameContext; ra::data::context::mocks::MockUserContext mockUserContext; ra::data::context::mocks::MockEmulatorContext mockEmulatorContext; + ra::services::mocks::MockClipboard mockClipboard; std::array memory{}; @@ -237,6 +239,55 @@ TEST_CLASS(PointerInspectorViewModel_Tests) inspector.AssertField(2, 8, 0x1CU, L"+0008", L"[8-bit] Column 1c", MemSize::EightBit, MemFormat::Hex, L"24"); } + TEST_METHOD(TestCopyDefinition) + { + PointerInspectorViewModelHarness inspector; + inspector.mockGameContext.SetGameId(1); + inspector.mockGameContext.NotifyActiveGameChanged(); // enable note support + + std::array memory = {}; + for (uint8_t i = 4; i < memory.size(); i += 4) + memory.at(i) = i + 8; + inspector.mockEmulatorContext.MockMemory(memory); + + inspector.SetCurrentAddress({4U}); + inspector.mockGameContext.Assets().FindCodeNotes()->SetCodeNote({4U}, + L"[8-bit pointer] Player data\n" + L"+0x00: [8-bit pointer] Row 1\n" + L".+0x00: [8-bit] Column 1a\n" + L".+0x04: [8-bit pointer] Column 1b\n" + L"..+0x00: [8-bit] Column 1a\n" + L".+0x08: [8-bit] Column 1c\n" + L"+0x08: [8-bit pointer] Row 2\n" + L"+0x10: [8-bit pointer] Row 3\n" + L"+0x18: Generic data" + ); + + Assert::AreEqual({4U}, inspector.GetCurrentAddress()); + + Assert::AreEqual(RootNodeID, inspector.GetSelectedNode()); + Assert::AreEqual(std::wstring(L"[8-bit pointer] Player data"), inspector.GetCurrentAddressNote()); + inspector.AssertField(1, 8, 0x14U, L"+0008", L"[8-bit pointer] Row 2", MemSize::EightBit, MemFormat::Hex, L"1c"); + + inspector.CopyDefinition(); + Assert::AreEqual(std::wstring(), inspector.mockClipboard.GetText()); + + inspector.Fields().GetItemAt(1)->SetSelected(true); + inspector.CopyDefinition(); + Assert::AreEqual(std::wstring(L"I:0xH0004_0xH0008=28"), inspector.mockClipboard.GetText()); + + inspector.SetSelectedNode(0x01000004); + Assert::AreEqual(0x01000004, inspector.GetSelectedNode()); + Assert::AreEqual(std::wstring(L"[8-bit pointer] Column 1b"), inspector.GetCurrentAddressNote()); + inspector.AssertField(0, 0, 0x20U, L"+0000", L"[8-bit] Column 1a", MemSize::EightBit, MemFormat::Hex, L"28"); + inspector.CopyDefinition(); + Assert::AreEqual(std::wstring(), inspector.mockClipboard.GetText()); + + inspector.Fields().GetItemAt(0)->SetSelected(true); + inspector.CopyDefinition(); + Assert::AreEqual(std::wstring(L"I:0xH0004_I:0xH0000_I:0xH0004_0xH0000=40"), inspector.mockClipboard.GetText()); + } + TEST_METHOD(TestDoFrame) { PointerInspectorViewModelHarness inspector; From e94eb6e1d1f7433c8052d08c365c47fd9bd0a625 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Tue, 24 Dec 2024 09:03:19 -0700 Subject: [PATCH 6/9] hook up selected items --- src/RA_Shared.rc | 2 +- src/ui/ViewModelCollection.hh | 24 ++++++++++++ .../viewmodels/PointerInspectorViewModel.cpp | 20 +++++----- .../viewmodels/PointerInspectorViewModel.hh | 11 ------ src/ui/win32/PointerInspectorDialog.cpp | 39 +++++++++++-------- .../PointerInspectorViewModel_Tests.cpp | 16 ++++---- 6 files changed, 66 insertions(+), 46 deletions(-) diff --git a/src/RA_Shared.rc b/src/RA_Shared.rc index 91c02feaf..bfbd753e6 100644 --- a/src/RA_Shared.rc +++ b/src/RA_Shared.rc @@ -271,7 +271,7 @@ BEGIN LTEXT "Root Address:",IDC_STATIC,6,5,63,11 EDITTEXT IDC_RA_ADDRESS,55,3,54,12,ES_AUTOHSCROLL,WS_EX_RIGHT LTEXT "Viewing Node:",IDC_STATIC,120,5,83,11 - COMBOBOX IDC_RA_FILTER_VALUE,170,3,136,12,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_RA_FILTER_VALUE,170,3,168,12,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP LTEXT "Description:",IDC_STATIC,6,20,63,11 EDITTEXT IDC_RA_DESCRIPTION,55,18,283,12,ES_AUTOHSCROLL CONTROL "",IDC_RA_LBX_ADDRESSES,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_CLIPCHILDREN | WS_BORDER,4,32,334,116 diff --git a/src/ui/ViewModelCollection.hh b/src/ui/ViewModelCollection.hh index a58da04e5..bb7056a7a 100644 --- a/src/ui/ViewModelCollection.hh +++ b/src/ui/ViewModelCollection.hh @@ -128,6 +128,18 @@ public: return dynamic_cast(ModelCollectionBase::AddItem(std::move(pItem))); } + /// + /// Adds an item to the end of the collection. + /// + template + T2& Add(Args&&... args) + { + static_assert(std::is_base_of::value, "T2 not derived from base class T"); + + auto pItem = std::make_unique(std::forward(args)...); + return dynamic_cast(ModelCollectionBase::AddItem(std::move(pItem))); + } + /// /// Adds an item to the end of the collection. /// @@ -148,6 +160,18 @@ public: /// Gets the item at the specified index. /// const T* GetItemAt(gsl::index nIndex) const { return dynamic_cast(GetModelAt(nIndex)); } + + /// + /// Gets the item at the specified index. + /// + template + T2* GetItemAt(gsl::index nIndex) { return dynamic_cast(GetModelAt(nIndex)); } + + /// + /// Gets the item at the specified index. + /// + template + const T2* GetItemAt(gsl::index nIndex) const { return dynamic_cast(GetModelAt(nIndex)); } }; } // namespace ui diff --git a/src/ui/viewmodels/PointerInspectorViewModel.cpp b/src/ui/viewmodels/PointerInspectorViewModel.cpp index 32d9e422e..08a5702fd 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.cpp +++ b/src/ui/viewmodels/PointerInspectorViewModel.cpp @@ -183,10 +183,10 @@ void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* m_pCurrentNote = pNote; const auto nBaseAddress = m_pCurrentNote->GetPointerAddress(); - gsl::index nCount = gsl::narrow_cast(Fields().Count()); + gsl::index nCount = gsl::narrow_cast(Bookmarks().Count()); gsl::index nInsertIndex = 0; - Fields().BeginUpdate(); + Bookmarks().BeginUpdate(); pNote->EnumeratePointerNotes([this, &nCount, &nInsertIndex, nBaseAddress] (ra::ByteAddress nAddress, const ra::data::models::CodeNoteModel& pOffsetNote) { @@ -196,12 +196,12 @@ void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* StructFieldViewModel* pItem; if (nInsertIndex < nCount) { - pItem = Fields().GetItemAt(nInsertIndex); + pItem = Bookmarks().GetItemAt(nInsertIndex); } else { ++nCount; - pItem = &Fields().Add(); + pItem = &Bookmarks().Add(); } pItem->m_nOffset = nOffset; @@ -213,10 +213,10 @@ void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* }); while (nCount > nInsertIndex) - Fields().RemoveAt(--nCount); + Bookmarks().RemoveAt(--nCount); UpdateValues(); - Fields().EndUpdate(); + Bookmarks().EndUpdate(); } static void LoadSubNotes(LookupItemViewModelCollection& vNodes, @@ -274,10 +274,10 @@ void PointerInspectorViewModel::UpdateValues() auto& pEmulatorContext = ra::services::ServiceLocator::GetMutable(); pEmulatorContext.RemoveNotifyTarget(*this); - const auto nCount = gsl::narrow_cast(Fields().Count()); + const auto nCount = gsl::narrow_cast(Bookmarks().Count()); for (gsl::index nIndex = 0; nIndex < nCount; ++nIndex) { - auto* pField = Fields().GetItemAt(nIndex); + auto* pField = Bookmarks().GetItemAt(nIndex); if (pField != nullptr) { pField->SetAddress(nBaseAddress + pField->m_nOffset); @@ -303,7 +303,7 @@ std::string PointerInspectorViewModel::GetDefinition() const { std::string sBuffer; - const auto nSelectedFieldIndex = Fields().FindItemIndex(LookupItemViewModel::IsSelectedProperty, true); + const auto nSelectedFieldIndex = Bookmarks().FindItemIndex(LookupItemViewModel::IsSelectedProperty, true); if (nSelectedFieldIndex == -1) return sBuffer; @@ -347,7 +347,7 @@ std::string PointerInspectorViewModel::GetDefinition() const sChain.pop(); } while (!sChain.empty()); - const auto* vmField = Fields().GetItemAt(nSelectedFieldIndex); + const auto* vmField = Bookmarks().GetItemAt(nSelectedFieldIndex); Expects(vmField != nullptr); ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, ra::services::TriggerOperandType::Address, vmField->GetSize(), ra::to_unsigned(vmField->m_nOffset)); diff --git a/src/ui/viewmodels/PointerInspectorViewModel.hh b/src/ui/viewmodels/PointerInspectorViewModel.hh index d081b517f..9b55da0e9 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.hh +++ b/src/ui/viewmodels/PointerInspectorViewModel.hh @@ -88,16 +88,6 @@ public: int32_t m_nOffset; }; - /// - /// Gets the list of fields. - /// - ViewModelCollection& Fields() noexcept { return m_vFields; } - - /// - /// Gets the list of fields. - /// - const ViewModelCollection& Fields() const noexcept { return m_vFields; } - /// /// Gets the list of available nodes. /// @@ -147,7 +137,6 @@ private: std::string GetDefinition() const; LookupItemViewModelCollection m_vNodes; - ViewModelCollection m_vFields; bool m_bSyncingAddress = false; const ra::data::models::CodeNoteModel* m_pCurrentNote = nullptr; diff --git a/src/ui/win32/PointerInspectorDialog.cpp b/src/ui/win32/PointerInspectorDialog.cpp index 621b6d3f7..ce57f6835 100644 --- a/src/ui/win32/PointerInspectorDialog.cpp +++ b/src/ui/win32/PointerInspectorDialog.cpp @@ -56,6 +56,12 @@ PointerInspectorDialog::PointerInspectorDialog(PointerInspectorViewModel& vmPoin m_bindFields(vmPointerFinder) { m_bindWindow.SetInitialPosition(RelativePosition::After, RelativePosition::Near, "Pointer Finder"); + m_bindWindow.BindLabel(IDC_RA_PAUSE, PointerInspectorViewModel::PauseButtonTextProperty); + m_bindWindow.BindLabel(IDC_RA_FREEZE, PointerInspectorViewModel::FreezeButtonTextProperty); + m_bindWindow.BindEnabled(IDC_RA_ADDBOOKMARK, PointerInspectorViewModel::HasSelectionProperty); + m_bindWindow.BindEnabled(IDC_RA_COPY_ALL, PointerInspectorViewModel::HasSelectionProperty); + m_bindWindow.BindEnabled(IDC_RA_PAUSE, PointerInspectorViewModel::HasSelectionProperty); + m_bindWindow.BindEnabled(IDC_RA_FREEZE, PointerInspectorViewModel::HasSelectionProperty); m_bindAddress.BindText(PointerInspectorViewModel::CurrentAddressTextProperty, ra::ui::win32::bindings::TextBoxBinding::UpdateMode::Typing); m_bindNodes.BindItems(vmPointerFinder.Nodes()); @@ -105,16 +111,17 @@ PointerInspectorDialog::PointerInspectorDialog(PointerInspectorViewModel& vmPoin pBehaviorColumn->SetReadOnly(false); m_bindFields.BindColumn(5, std::move(pBehaviorColumn)); - m_bindFields.BindItems(vmPointerFinder.Fields()); + m_bindFields.BindItems(vmPointerFinder.Bookmarks()); m_bindFields.BindIsSelected(PointerInspectorViewModel::StructFieldViewModel::IsSelectedProperty); + m_bindFields.BindRowColor(PointerInspectorViewModel::StructFieldViewModel::RowColorProperty); + m_bindFields.SetShowGridLines(true); + - // Resize behavior using namespace ra::bitwise_ops; SetAnchor(IDC_RA_ADDRESS, Anchor::Top | Anchor::Left); + SetAnchor(IDC_RA_FILTER_VALUE, Anchor::Top | Anchor::Left | Anchor::Right); SetAnchor(IDC_RA_DESCRIPTION, Anchor::Top | Anchor::Left | Anchor::Right); SetAnchor(IDC_RA_LBX_ADDRESSES, Anchor::Top | Anchor::Left | Anchor::Bottom | Anchor::Right); - - SetAnchor(IDC_RA_ADDBOOKMARK, Anchor::Left | Anchor::Bottom); SetAnchor(IDC_RA_COPY_ALL, Anchor::Left | Anchor::Bottom); SetAnchor(IDC_RA_PAUSE, Anchor::Right | Anchor::Bottom); @@ -153,21 +160,21 @@ BOOL PointerInspectorDialog::OnCommand(WORD nCommand) return TRUE; } - //case IDC_RA_PAUSE: { - // auto* vmPointerInspector = dynamic_cast(&m_vmWindow); - // if (vmPointerInspector) - // vmPointerInspector->TogglePause(); + case IDC_RA_PAUSE: { + auto* vmPointerInspector = dynamic_cast(&m_vmWindow); + if (vmPointerInspector) + vmPointerInspector->TogglePauseSelected(); - // return TRUE; - //} + return TRUE; + } - //case IDC_RA_FREEZE: { - // const auto* vmPointerInspector = dynamic_cast(&m_vmWindow); - // if (vmPointerInspector) - // vmPointerInspector->ToggleFreeze(); + case IDC_RA_FREEZE: { + auto* vmPointerInspector = dynamic_cast(&m_vmWindow); + if (vmPointerInspector) + vmPointerInspector->ToggleFreezeSelected(); - // return TRUE; - //} + return TRUE; + } } return DialogBase::OnCommand(nCommand); diff --git a/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp index 11fdd1ad6..a0e6289c5 100644 --- a/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp +++ b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp @@ -80,7 +80,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) const std::wstring& sOffset, const std::wstring& sDescription, MemSize nSize, MemFormat nFormat, const std::wstring& sCurrentValue) { - const auto *pField = Fields().GetItemAt(nIndex); + const auto *pField = dynamic_cast(Bookmarks().GetItemAt(nIndex)); Assert::IsNotNull(pField); Ensures(pField != nullptr); Assert::AreEqual(nOffset, pField->m_nOffset); @@ -171,7 +171,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) Assert::AreEqual(std::wstring(L"0x0004"), inspector.GetCurrentAddressText()); Assert::AreEqual(std::wstring(L"[32-bit pointer] Player data"), inspector.GetCurrentAddressNote()); - Assert::AreEqual({ 2U }, inspector.Fields().Count()); + Assert::AreEqual({ 2U }, inspector.Bookmarks().Count()); inspector.AssertField(0, 4, 16U, L"+0004", L"[32-bit] Current HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000010"); inspector.AssertField(1, 8, 20U, L"+0008", L"[32-bit] Max HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000014"); @@ -219,7 +219,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) Assert::AreEqual(RootNodeID, inspector.GetSelectedNode()); Assert::AreEqual(std::wstring(L"[8-bit pointer] Player data"), inspector.GetCurrentAddressNote()); - Assert::AreEqual({4U}, inspector.Fields().Count()); + Assert::AreEqual({4U}, inspector.Bookmarks().Count()); inspector.AssertField(0, 0, 0x0CU, L"+0000", L"[8-bit pointer] Row 1", MemSize::EightBit, MemFormat::Hex, L"14"); inspector.AssertField(1, 8, 0x14U, L"+0008", L"[8-bit pointer] Row 2", MemSize::EightBit, MemFormat::Hex, L"1c"); inspector.AssertField(2, 16, 0x1CU, L"+0010", L"[8-bit pointer] Row 3", MemSize::EightBit, MemFormat::Hex, L"24"); @@ -228,12 +228,12 @@ TEST_CLASS(PointerInspectorViewModel_Tests) inspector.SetSelectedNode(0x00000008); Assert::AreEqual(0x00000008, inspector.GetSelectedNode()); Assert::AreEqual(std::wstring(L"[8-bit pointer] Row 2"), inspector.GetCurrentAddressNote()); - Assert::AreEqual({0U}, inspector.Fields().Count()); + Assert::AreEqual({0U}, inspector.Bookmarks().Count()); inspector.SetSelectedNode(0x00000000); Assert::AreEqual(0x00000000, inspector.GetSelectedNode()); Assert::AreEqual(std::wstring(L"[8-bit pointer] Row 1"), inspector.GetCurrentAddressNote()); - Assert::AreEqual({3U}, inspector.Fields().Count()); + Assert::AreEqual({3U}, inspector.Bookmarks().Count()); inspector.AssertField(0, 0, 0x14U, L"+0000", L"[8-bit] Column 1a", MemSize::EightBit, MemFormat::Hex, L"1c"); inspector.AssertField(1, 4, 0x18U, L"+0004", L"[8-bit pointer] Column 1b", MemSize::EightBit, MemFormat::Hex, L"20"); inspector.AssertField(2, 8, 0x1CU, L"+0008", L"[8-bit] Column 1c", MemSize::EightBit, MemFormat::Hex, L"24"); @@ -272,7 +272,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) inspector.CopyDefinition(); Assert::AreEqual(std::wstring(), inspector.mockClipboard.GetText()); - inspector.Fields().GetItemAt(1)->SetSelected(true); + inspector.Bookmarks().GetItemAt(1)->SetSelected(true); inspector.CopyDefinition(); Assert::AreEqual(std::wstring(L"I:0xH0004_0xH0008=28"), inspector.mockClipboard.GetText()); @@ -283,7 +283,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) inspector.CopyDefinition(); Assert::AreEqual(std::wstring(), inspector.mockClipboard.GetText()); - inspector.Fields().GetItemAt(0)->SetSelected(true); + inspector.Bookmarks().GetItemAt(0)->SetSelected(true); inspector.CopyDefinition(); Assert::AreEqual(std::wstring(L"I:0xH0004_I:0xH0000_I:0xH0004_0xH0000=40"), inspector.mockClipboard.GetText()); } @@ -310,7 +310,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) Assert::AreEqual(std::wstring(L"0x0004"), inspector.GetCurrentAddressText()); Assert::AreEqual(std::wstring(L"[32-bit pointer] Player data"), inspector.GetCurrentAddressNote()); - Assert::AreEqual({2U}, inspector.Fields().Count()); + Assert::AreEqual({2U}, inspector.Bookmarks().Count()); inspector.AssertField(0, 4, 16U, L"+0004", L"[32-bit] Current HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000010"); inspector.AssertField(1, 8, 20U, L"+0008", L"[32-bit] Max HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000014"); From a6fc39c928271401c65081247f8b9926426ea112 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Tue, 24 Dec 2024 10:43:24 -0700 Subject: [PATCH 7/9] support for implied pointer chain --- src/data/models/CodeNoteModel.cpp | 82 ++++++++++++++++------- src/data/models/CodeNoteModel.hh | 2 +- tests/data/models/CodeNoteModel_Tests.cpp | 23 +++++++ 3 files changed, 80 insertions(+), 27 deletions(-) diff --git a/src/data/models/CodeNoteModel.cpp b/src/data/models/CodeNoteModel.cpp index 447d9f7a0..042e7b81c 100644 --- a/src/data/models/CodeNoteModel.cpp +++ b/src/data/models/CodeNoteModel.cpp @@ -317,7 +317,7 @@ std::wstring CodeNoteModel::GetPrimaryNote() const noexcept return m_sNote; } -void CodeNoteModel::SetNote(const std::wstring& sNote) +void CodeNoteModel::SetNote(const std::wstring& sNote, bool bImpliedPointer) { if (m_sNote == sNote) return; @@ -331,34 +331,53 @@ void CodeNoteModel::SetNote(const std::wstring& sNote) const auto nNextIndex = sNote.find(L'\n', nIndex); sLine = (nNextIndex == std::string::npos) ? sNote.substr(nIndex) : sNote.substr(nIndex, nNextIndex - nIndex); - StringMakeLowercase(sLine); - ExtractSize(sLine); - if (sLine.find(L"pointer") != std::string::npos) + if (!sLine.empty()) { - if (m_nMemSize == MemSize::Unknown) + if (sLine[0] == '+' && bImpliedPointer) { - // pointer size not specified. assume 32-bit m_nMemSize = MemSize::ThirtyTwoBit; m_nBytes = 4; + + // found a line starting with a plus sign, bit no pointer annotation. bImpliedPointer + // must be true. assume the parent note is not described. pass -1 as the note size + // because we already skipped over the newline character + ProcessIndirectNotes(sNote, gsl::narrow_cast(-1)); + m_pPointerData->HeaderLength = 0; + break; } - // if there are any lines starting with a plus sign, extract the indirect code notes - nIndex = sNote.find(L"\n+", nIndex + 1); - if (nIndex != std::string::npos) - ProcessIndirectNotes(sNote, nIndex); - - // failed to find nested code notes. create a PointerData object so the note still gets treated as a pointer - if (!m_pPointerData) + StringMakeLowercase(sLine); + ExtractSize(sLine); + + if (sLine.find(L"pointer") != std::string::npos) { - m_pPointerData.reset(new PointerData()); - m_pPointerData->HeaderLength = gsl::narrow_cast(sNote.length()); + if (m_nMemSize == MemSize::Unknown) + { + // pointer size not specified. assume 32-bit + m_nMemSize = MemSize::ThirtyTwoBit; + m_nBytes = 4; + } + + // if there are any lines starting with a plus sign, extract the indirect code notes + nIndex = sNote.find(L"\n+", nIndex + 1); + if (nIndex != std::string::npos) + ProcessIndirectNotes(sNote, nIndex); + + // failed to find nested code notes. create a PointerData object so the note still + // gets treated as a pointer + if (!m_pPointerData) + { + m_pPointerData.reset(new PointerData()); + m_pPointerData->HeaderLength = gsl::narrow_cast(sNote.length()); + } + + break; } - break; - } - if (m_nMemSize != MemSize::Unknown) // found a size. stop processing. - break; + if (m_nMemSize != MemSize::Unknown) // found a size. stop processing. + break; + } if (nNextIndex == std::string::npos) // end of string break; @@ -687,16 +706,27 @@ void CodeNoteModel::ProcessIndirectNotes(const std::wstring& sNote, size_t nInde // skip over [whitespace] [optional separator] [whitespace] const wchar_t* pStop = sNextNote.c_str() + sNextNote.length(); - while (pEnd < pStop && isspace(*pEnd)) - pEnd++; - if (pEnd < pStop && !isalnum(*pEnd)) + while (pEnd < pStop && isspace(*pEnd) && *pEnd != '\n') + ++pEnd; + + if (pEnd < pStop) { - pEnd++; - while (pEnd < pStop && isspace(*pEnd)) - pEnd++; + if (*pEnd == '\n') + { + // no separator. found an unannotated note + ++pEnd; + } + else if (!isalnum(*pEnd)) + { + // found a separator. skip it and any following whitespace + ++pEnd; + + while (pEnd < pStop && isspace(*pEnd)) + ++pEnd; + } } - offsetNote.SetNote(sNextNote.substr(pEnd - sNextNote.c_str())); + offsetNote.SetNote(sNextNote.substr(pEnd - sNextNote.c_str()), true); pointerData->HasPointers |= offsetNote.IsPointer(); offsetNote.SetAddress(gsl::narrow_cast(nOffset)); diff --git a/src/data/models/CodeNoteModel.hh b/src/data/models/CodeNoteModel.hh index 6394791e8..d0a58a19f 100644 --- a/src/data/models/CodeNoteModel.hh +++ b/src/data/models/CodeNoteModel.hh @@ -28,7 +28,7 @@ public: void SetAuthor(const std::string& sAuthor) { m_sAuthor = sAuthor; } void SetAddress(ra::ByteAddress nAddress) noexcept { m_nAddress = nAddress; } - void SetNote(const std::wstring& sNote); + void SetNote(const std::wstring& sNote, bool bImpliedPointer = false); bool IsPointer() const noexcept { return m_pPointerData != nullptr; } std::wstring GetPointerDescription() const; diff --git a/tests/data/models/CodeNoteModel_Tests.cpp b/tests/data/models/CodeNoteModel_Tests.cpp index 610453ce2..1e8fba45e 100644 --- a/tests/data/models/CodeNoteModel_Tests.cpp +++ b/tests/data/models/CodeNoteModel_Tests.cpp @@ -335,6 +335,29 @@ TEST_CLASS(CodeNoteModel_Tests) AssertIndirectNote(note, 0x448, L"[32-bit BE] Not-nested number", MemSize::ThirtyTwoBitBigEndian, 4); } + + TEST_METHOD(TestCodeNoteImpliedPointerChain) + { + CodeNoteModelHarness note; + const std::wstring sNote = + L"Root Pointer [24 bits]\n" + L"\n" + L"+0x8318\n" + L"++0x8014\n" + L"+++0x8004 = First [32-bits]\n" + L"+++0x8008 = Second [32-bits]"; + note.SetNote(sNote); + + Assert::AreEqual(MemSize::TwentyFourBit, note.GetMemSize()); + Assert::AreEqual(sNote, note.GetNote()); // full note for pointer address + + const auto* nestedNote = AssertIndirectNote(note, 0x8318, + L"+0x8014\n++0x8004 = First [32-bits]\n++0x8008 = Second [32-bits]", MemSize::ThirtyTwoBit, 4); + const auto* nestedNote2 = AssertIndirectNote(*nestedNote, 0x8014, + L"+0x8004 = First [32-bits]\n+0x8008 = Second [32-bits]", MemSize::ThirtyTwoBit, 4); + AssertIndirectNote(*nestedNote2, 0x8004, L"First [32-bits]", MemSize::ThirtyTwoBit, 4); + AssertIndirectNote(*nestedNote2, 0x8008, L"Second [32-bits]", MemSize::ThirtyTwoBit, 4); + } }; } // namespace tests From 874247811101b03db6271d3ed0025c7d1eb94bed Mon Sep 17 00:00:00 2001 From: Jamiras Date: Tue, 24 Dec 2024 17:42:01 -0700 Subject: [PATCH 8/9] add pointer chain --- src/RA_Shared.rc | 13 +- src/ui/viewmodels/LookupItemViewModel.hh | 24 ++ .../viewmodels/MemoryBookmarksViewModel.cpp | 11 + src/ui/viewmodels/MemoryBookmarksViewModel.hh | 2 + .../viewmodels/PointerInspectorViewModel.cpp | 233 ++++++++++++++---- .../viewmodels/PointerInspectorViewModel.hh | 49 +++- src/ui/win32/PointerInspectorDialog.cpp | 33 ++- src/ui/win32/PointerInspectorDialog.hh | 1 + .../PointerInspectorViewModel_Tests.cpp | 152 +++++++++++- 9 files changed, 458 insertions(+), 60 deletions(-) diff --git a/src/RA_Shared.rc b/src/RA_Shared.rc index bfbd753e6..233530080 100644 --- a/src/RA_Shared.rc +++ b/src/RA_Shared.rc @@ -263,7 +263,7 @@ BEGIN PUSHBUTTON "Move Down",IDC_RA_MOVE_BOOKMARK_DOWN,290,137,50,14 END -IDD_RA_POINTERINSPECTOR DIALOGEX 0, 0, 341, 166 +IDD_RA_POINTERINSPECTOR DIALOGEX 0, 0, 341, 232 STYLE DS_SETFONT | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME CAPTION "Pointer Inspector" FONT 8, "MS Shell Dlg", 400, 0, 0x0 @@ -274,11 +274,12 @@ BEGIN COMBOBOX IDC_RA_FILTER_VALUE,170,3,168,12,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP LTEXT "Description:",IDC_STATIC,6,20,63,11 EDITTEXT IDC_RA_DESCRIPTION,55,18,283,12,ES_AUTOHSCROLL - CONTROL "",IDC_RA_LBX_ADDRESSES,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_CLIPCHILDREN | WS_BORDER,4,32,334,116 - PUSHBUTTON "Bookmark Selected",IDC_RA_ADDBOOKMARK,4,149,70,14,BS_MULTILINE - PUSHBUTTON "Copy Chain",IDC_RA_COPY_ALL,73,149,70,14 - PUSHBUTTON "Pause",IDC_RA_PAUSE,236,149,50,14,BS_MULTILINE - PUSHBUTTON "Freeze",IDC_RA_FREEZE,288,149,50,14,BS_MULTILINE + CONTROL "",IDC_RA_LBX_GROUPS,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_CLIPCHILDREN | WS_BORDER,4,32,334,60 + CONTROL "",IDC_RA_LBX_ADDRESSES,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_CLIPCHILDREN | WS_BORDER,4,95,334,118 + PUSHBUTTON "Bookmark Selected",IDC_RA_ADDBOOKMARK,4,215,70,14,BS_MULTILINE + PUSHBUTTON "Copy Chain",IDC_RA_COPY_ALL,75,215,70,14 + PUSHBUTTON "Pause",IDC_RA_PAUSE,236,215,50,14,BS_MULTILINE + PUSHBUTTON "Freeze",IDC_RA_FREEZE,288,215,50,14,BS_MULTILINE END IDD_RA_CODENOTES DIALOGEX 0, 0, 287, 230 diff --git a/src/ui/viewmodels/LookupItemViewModel.hh b/src/ui/viewmodels/LookupItemViewModel.hh index 96bd494f7..4032b704d 100644 --- a/src/ui/viewmodels/LookupItemViewModel.hh +++ b/src/ui/viewmodels/LookupItemViewModel.hh @@ -83,6 +83,18 @@ public: return dynamic_cast(ViewModelCollectionBase::AddItem(std::move(pItem))); } + /// + /// Adds an item to the end of the collection. + /// + template + GSL_SUPPRESS_C128 T& Add(Args&&... args) + { + static_assert(std::is_base_of::value, "T not derived from base class LookupItemViewModel"); + + auto pItem = std::make_unique(std::forward(args)...); + return dynamic_cast(ViewModelCollectionBase::AddItem(std::move(pItem))); + } + /// /// Gets the item at the specified index. /// @@ -93,6 +105,18 @@ public: /// const LookupItemViewModel* GetItemAt(gsl::index nIndex) const { return dynamic_cast(GetModelAt(nIndex)); } + /// + /// Gets the item at the specified index. + /// + template + T* GetItemAt(gsl::index nIndex) { return dynamic_cast(GetModelAt(nIndex)); } + + /// + /// Gets the item at the specified index. + /// + template + const T* GetItemAt(gsl::index nIndex) const { return dynamic_cast(GetModelAt(nIndex)); } + /// /// Gets the label for item specified by the provided ID. /// diff --git a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp index 982e12ebf..82ce3ed0a 100644 --- a/src/ui/viewmodels/MemoryBookmarksViewModel.cpp +++ b/src/ui/viewmodels/MemoryBookmarksViewModel.cpp @@ -89,6 +89,17 @@ void MemoryBookmarksViewModel::InitializeNotifyTargets() pEmulatorContext.AddNotifyTarget(*this); } +void MemoryBookmarksViewModel::MemoryBookmarkViewModel::SetAddressWithoutUpdatingValue(ra::ByteAddress nNewAddress) +{ + // set m_bInitialized to false while updating the address to prevent synchronizing the value + const bool bInitialized = m_bInitialized; + m_bInitialized = false; + + SetAddress(nNewAddress); + + m_bInitialized = bInitialized; +} + GSL_SUPPRESS_F6 void MemoryBookmarksViewModel::MemoryBookmarkViewModel::OnValueChanged(const IntModelProperty::ChangeArgs& args) { diff --git a/src/ui/viewmodels/MemoryBookmarksViewModel.hh b/src/ui/viewmodels/MemoryBookmarksViewModel.hh index 1bbd8bf38..eb4dfab77 100644 --- a/src/ui/viewmodels/MemoryBookmarksViewModel.hh +++ b/src/ui/viewmodels/MemoryBookmarksViewModel.hh @@ -269,6 +269,8 @@ public: unsigned ReadValue() const; + void SetAddressWithoutUpdatingValue(ra::ByteAddress nNewAddress); + private: std::wstring BuildCurrentValue() const; diff --git a/src/ui/viewmodels/PointerInspectorViewModel.cpp b/src/ui/viewmodels/PointerInspectorViewModel.cpp index 08a5702fd..b226f399c 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.cpp +++ b/src/ui/viewmodels/PointerInspectorViewModel.cpp @@ -18,8 +18,6 @@ const StringModelProperty PointerInspectorViewModel::CurrentAddressNoteProperty( const StringModelProperty PointerInspectorViewModel::StructFieldViewModel::OffsetProperty("StructFieldViewModel", "Offset", L"+0000"); const IntModelProperty PointerInspectorViewModel::SelectedNodeProperty("PointerInspectorViewModel", "SelectedNode", -2); -constexpr int RootNodeID = -1; - PointerInspectorViewModel::PointerInspectorViewModel() : MemoryBookmarksViewModel() { @@ -98,27 +96,24 @@ void PointerInspectorViewModel::OnCurrentAddressChanged(ra::ByteAddress nNewAddr LoadNodes(nullptr); LoadNote(nullptr); } + + UpdatePointerChain(GetSelectedNode()); } } -void PointerInspectorViewModel::GetPointerChain(gsl::index nIndex, std::stack& sChain) const +void PointerInspectorViewModel::GetPointerChain(gsl::index nIndex, std::stack& sChain) const { - const auto* pNode = m_vNodes.GetItemAt(nIndex); + const auto* pNode = m_vNodes.GetItemAt(nIndex); Expects(pNode != nullptr); - sChain.push(pNode); - - auto nDepth = pNode->GetId() >> 24; - while (nDepth > 0 && nIndex > 1) + while (!pNode->IsRootNode()) { - const auto* pNestedNode = m_vNodes.GetItemAt(--nIndex); - const auto nNestedDepth = pNestedNode->GetId() >> 24; - if (nNestedDepth < nDepth) - { - sChain.push(pNestedNode); - nDepth = nNestedDepth; - } + sChain.push(pNode); + pNode = m_vNodes.GetItemAt(pNode->GetParentIndex()); + Expects(pNode != nullptr); } + + sChain.push(pNode); } const ra::data::models::CodeNoteModel* PointerInspectorViewModel::FindNestedCodeNoteModel( @@ -128,14 +123,14 @@ const ra::data::models::CodeNoteModel* PointerInspectorViewModel::FindNestedCode if (nIndex == -1) return nullptr; - std::stack sChain; + std::stack sChain; GetPointerChain(nIndex, sChain); const auto* pParentNote = &pRootNote; do { const auto* pNestedNode = sChain.top(); - const auto nOffset = pNestedNode->GetId() & 0x00FFFFFF; + const auto nOffset = pNestedNode->GetOffset(); const auto* pNestedNote = pParentNote->GetPointerNoteAtOffset(nOffset); if (!pNestedNote) @@ -149,24 +144,103 @@ const ra::data::models::CodeNoteModel* PointerInspectorViewModel::FindNestedCode } void PointerInspectorViewModel::OnSelectedNodeChanged(int nNewNode) +{ + const auto* pNote = UpdatePointerChain(nNewNode); + LoadNote(pNote); +} + +const ra::data::models::CodeNoteModel* PointerInspectorViewModel::UpdatePointerChain(int nNewNode) { const auto& pGameContext = ra::services::ServiceLocator::Get(); const auto* pCodeNotes = pGameContext.Assets().FindCodeNotes(); - if (pCodeNotes != nullptr) + const auto nCurrentAddress = GetCurrentAddress(); + const auto* pNote = pCodeNotes ? pCodeNotes->FindCodeNoteModel(nCurrentAddress) : nullptr; + + auto nNewNodeIndex = Nodes().FindItemIndex(PointerNodeViewModel::IdProperty, nNewNode); + if (nNewNodeIndex == -1) + nNewNodeIndex = 0; + + std::stack sChain; + GetPointerChain(nNewNodeIndex, sChain); + + gsl::index nCount = gsl::narrow_cast(PointerChain().Count()); + gsl::index nInsertIndex = 0; + PointerChain().BeginUpdate(); + + do { - const auto* pNote = pCodeNotes->FindCodeNoteModel(GetCurrentAddress()); - if (pNote != nullptr && nNewNode != RootNodeID) - pNote = FindNestedCodeNoteModel(*pNote, nNewNode); + StructFieldViewModel* pItem; + if (nInsertIndex < nCount) + { + pItem = PointerChain().GetItemAt(nInsertIndex); + } + else + { + ++nCount; + pItem = &PointerChain().Add(); + } - LoadNote(pNote); - } + pItem->BeginInitialization(); + + const auto* pNode = sChain.top(); + if (pNode->IsRootNode()) + { + pItem->m_nOffset = nCurrentAddress; + pItem->SetOffset(L""); + } + else + { + pItem->m_nOffset = pNode->GetOffset(); + + std::wstring sLabel; + if (nInsertIndex > 1) + sLabel = std::wstring(nInsertIndex - 1, ' '); + sLabel += ra::StringPrintf(L"+%04x", pItem->m_nOffset); + pItem->SetOffset(sLabel); + + pNote = pNote ? pNote->GetPointerNoteAtOffset(pItem->m_nOffset) : nullptr; + } + + if (pNote) + { + pItem->SetDescription(pNote->GetPointerDescription()); + pItem->SetSize(pNote->GetMemSize()); + } + else + { + pItem->SetDescription(L""); + pItem->SetSize(MemSize::EightBit); + } + + UpdatePointerChainRowColor(*pItem); + pItem->EndInitialization(); + + ++nInsertIndex; + + sChain.pop(); + } while (!sChain.empty()); + + while (nCount > nInsertIndex) + Bookmarks().RemoveAt(--nCount); + + UpdatePointerChainValues(); + + PointerChain().EndUpdate(); + + return pNote; } void PointerInspectorViewModel::SyncField(PointerInspectorViewModel::StructFieldViewModel& pFieldViewModel, const ra::data::models::CodeNoteModel& pOffsetNote) { const auto nSize = pOffsetNote.GetMemSize(); pFieldViewModel.SetSize((nSize == MemSize::Unknown) ? MemSize::EightBit : nSize); - pFieldViewModel.SetDescription(pOffsetNote.GetPrimaryNote()); + + const auto& sDescription = pOffsetNote.GetPrimaryNote(); + const auto nIndex = sDescription.find('\n'); + if (nIndex == std::string::npos) + pFieldViewModel.SetDescription(sDescription); + else + pFieldViewModel.SetDescription(sDescription.substr(0, nIndex)); } void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* pNote) @@ -197,6 +271,9 @@ void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* if (nInsertIndex < nCount) { pItem = Bookmarks().GetItemAt(nInsertIndex); + Expects(pItem != nullptr); + pItem->SetSelected(false); + pItem->SetFormat(MemFormat::Hex); } else { @@ -204,10 +281,14 @@ void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* pItem = &Bookmarks().Add(); } + pItem->BeginInitialization(); + pItem->m_nOffset = nOffset; pItem->SetOffset(sOffset); SyncField(*pItem, pOffsetNote); + pItem->EndInitialization(); + ++nInsertIndex; return true; }); @@ -233,7 +314,7 @@ static void LoadSubNotes(LookupItemViewModelCollection& vNodes, sLabel = std::wstring(nDepth - 1, ' '); sLabel += ra::StringPrintf(L"+%04x | %s", nOffset, pOffsetNote.GetPointerDescription()); - vNodes.Add(nParentIndex << 24 | nOffset & 0x00FFFFFF, sLabel); + vNodes.Add(nParentIndex, nOffset, sLabel); if (pOffsetNote.HasNestedPointers()) LoadSubNotes(vNodes, pOffsetNote, pOffsetNote.GetPointerAddress(), nDepth + 1, gsl::narrow_cast(vNodes.Count() - 1)); @@ -251,11 +332,11 @@ void PointerInspectorViewModel::LoadNodes(const ra::data::models::CodeNoteModel* if (pNote == nullptr) { - m_vNodes.Add(RootNodeID, L"Root"); + m_vNodes.Add(-1, ra::to_signed(GetCurrentAddress()), L"Root"); } else { - m_vNodes.Add(RootNodeID, pNote->GetPrimaryNote()); + m_vNodes.Add(-1, ra::to_signed(GetCurrentAddress()), pNote->GetPrimaryNote()); if (pNote->HasNestedPointers()) LoadSubNotes(m_vNodes, *pNote, pNote->GetPointerAddress(), 1, 0); } @@ -264,7 +345,75 @@ void PointerInspectorViewModel::LoadNodes(const ra::data::models::CodeNoteModel* // if the selected item is no longer available, select the root node if (m_vNodes.FindItemIndex(LookupItemViewModel::IdProperty, nSelectedNode) == -1) - SetSelectedNode(RootNodeID); + SetSelectedNode(PointerNodeViewModel::RootNodeId); +} + +void PointerInspectorViewModel::UpdatePointerChainValues() +{ + ra::ByteAddress nAddress = 0; + const auto nCount = gsl::narrow_cast(PointerChain().Count()); + + PointerChain().BeginUpdate(); + + for (gsl::index nIndex = 0; nIndex < nCount; ++nIndex) + { + auto* pPointer = PointerChain().GetItemAt(nIndex); + if (pPointer != nullptr) + { + nAddress += pPointer->m_nOffset; + pPointer->SetAddressWithoutUpdatingValue(nAddress); + + if (pPointer->MemoryChanged()) + UpdatePointerChainRowColor(*pPointer); + + nAddress = pPointer->GetCurrentValueRaw(); + } + } + + PointerChain().EndUpdate(); +} + +void PointerInspectorViewModel::UpdatePointerChainRowColor(PointerInspectorViewModel::StructFieldViewModel& pPointer) +{ + const auto nPointerValue = pPointer.GetCurrentValueRaw(); + if (nPointerValue == 0) + { + // pointer is null + pPointer.SetRowColor(ra::ui::Color(0xFFFFC0C0)); + return; + } + + const auto& pConsoleContext = ra::services::ServiceLocator::Get(); + bool bValid; + + MemSize nMemSize = MemSize::Unknown; + uint32_t nMask = 0xFFFFFFFF; + uint32_t nOffset = 0; + if (pConsoleContext.GetRealAddressConversion(&nMemSize, &nMask, &nOffset)) + { + if (nMemSize == MemSize::TwentyFourBit) + nMemSize = MemSize::ThirtyTwoBit; + + const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); + const auto nRawPointer = pEmulatorContext.ReadMemory(pPointer.GetAddress(), nMemSize); + bValid = (pConsoleContext.ByteAddressFromRealAddress(nRawPointer) != 0xFFFFFFFF); + } + else + { + bValid = (nPointerValue <= pConsoleContext.MaxAddress()); + } + + if (!bValid) + { + // pointer is invalid + pPointer.SetRowColor(ra::ui::Color(0xFFFFFFC0)); + } + else + { + // pointer is valid + pPointer.SetRowColor( + ra::ui::Color(ra::to_unsigned(MemoryBookmarkViewModel::RowColorProperty.GetDefaultValue()))); + } } void PointerInspectorViewModel::UpdateValues() @@ -274,6 +423,8 @@ void PointerInspectorViewModel::UpdateValues() auto& pEmulatorContext = ra::services::ServiceLocator::GetMutable(); pEmulatorContext.RemoveNotifyTarget(*this); + Bookmarks().BeginUpdate(); + const auto nCount = gsl::narrow_cast(Bookmarks().Count()); for (gsl::index nIndex = 0; nIndex < nCount; ++nIndex) { @@ -284,12 +435,14 @@ void PointerInspectorViewModel::UpdateValues() UpdateBookmark(*pField); } } + Bookmarks().EndUpdate(); pEmulatorContext.AddNotifyTarget(*this); } void PointerInspectorViewModel::DoFrame() { + UpdatePointerChainValues(); UpdateValues(); } @@ -316,34 +469,24 @@ std::string PointerInspectorViewModel::GetDefinition() const if (pCodeNotes == nullptr) return sBuffer; - std::stack sChain; + std::stack sChain; GetPointerChain(nSelectedNodeIndex, sChain); - const auto* pRootNote = pCodeNotes->FindCodeNoteModel(GetCurrentAddress()); - const auto* pParentNote = pRootNote; - ra::services::AchievementLogicSerializer::AppendConditionType(sBuffer, ra::services::TriggerConditionType::AddAddress); - ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, ra::services::TriggerOperandType::Address, - pRootNote->GetMemSize(), pRootNote->GetAddress()); - ra::services::AchievementLogicSerializer::AppendConditionSeparator(sBuffer); + auto* pNote = pCodeNotes->FindCodeNoteModel(GetCurrentAddress()); do { - const auto* pNestedNode = sChain.top(); - const auto nId = pNestedNode->GetId(); - if (nId == RootNodeID) - break; + const auto* pNode = sChain.top(); - const auto nOffset = nId & 0x00FFFFFF; - const auto* pNestedNote = pParentNote->GetPointerNoteAtOffset(nOffset); - if (!pNestedNote) - return sBuffer; + if (!pNode->IsRootNode()) + pNote = pNote->GetPointerNoteAtOffset(pNode->GetOffset()); + Expects(pNote != nullptr); ra::services::AchievementLogicSerializer::AppendConditionType(sBuffer, ra::services::TriggerConditionType::AddAddress); ra::services::AchievementLogicSerializer::AppendOperand(sBuffer, ra::services::TriggerOperandType::Address, - pNestedNote->GetMemSize(), pNestedNote->GetAddress()); + pNote->GetMemSize(), pNote->GetAddress()); ra::services::AchievementLogicSerializer::AppendConditionSeparator(sBuffer); - pParentNote = pNestedNote; sChain.pop(); } while (!sChain.empty()); diff --git a/src/ui/viewmodels/PointerInspectorViewModel.hh b/src/ui/viewmodels/PointerInspectorViewModel.hh index 9b55da0e9..ff0e33c50 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.hh +++ b/src/ui/viewmodels/PointerInspectorViewModel.hh @@ -7,6 +7,16 @@ #include "ui\viewmodels\MemoryBookmarksViewModel.hh" +namespace ra { +namespace data { +namespace models { + +class CodeNoteModel; + +} // namespace models +} // namespace data +} // namespace ra + namespace ra { namespace ui { namespace viewmodels { @@ -85,9 +95,32 @@ public: /// void SetOffset(const std::wstring& sValue) { SetValue(OffsetProperty, sValue); } + using MemoryBookmarkViewModel::SetAddressWithoutUpdatingValue; + int32_t m_nOffset; }; + class PointerNodeViewModel : public LookupItemViewModel + { + public: + static const int RootNodeId = -1; + + PointerNodeViewModel(gsl::index nParentIndex, int32_t nOffset, const std::wstring& sLabel) noexcept + : LookupItemViewModel(nParentIndex < 0 ? RootNodeId : gsl::narrow_cast((nParentIndex << 24) | (nOffset & 0x00FFFFFF)), sLabel), + m_nParentIndex(nParentIndex), + m_nOffset(nOffset) + { + } + + bool IsRootNode() const { return m_nParentIndex == -1; } + gsl::index GetParentIndex() const { return m_nParentIndex; } + int32_t GetOffset() const { return m_nOffset; } + + private: + gsl::index m_nParentIndex = -1; + int32_t m_nOffset = 0; + }; + /// /// Gets the list of available nodes. /// @@ -113,6 +146,16 @@ public: /// void SetSelectedNode(int nValue) { SetValue(SelectedNodeProperty, nValue); } + /// + /// Gets the pointer chain list. + /// + ViewModelCollection& PointerChain() noexcept { return m_vPointerChain; } + + /// + /// Gets the pointer chain list. + /// + const ViewModelCollection& PointerChain() const noexcept { return m_vPointerChain; } + void DoFrame() override; void CopyDefinition() const; @@ -131,12 +174,16 @@ private: void LoadNote(const ra::data::models::CodeNoteModel* pNote); void LoadNodes(const ra::data::models::CodeNoteModel* pNote); const ra::data::models::CodeNoteModel* FindNestedCodeNoteModel(const ra::data::models::CodeNoteModel& pRootNote, int nNewNode); - void GetPointerChain(gsl::index nIndex, std::stack& sChain) const; + void GetPointerChain(gsl::index nIndex, std::stack& sChain) const; void SyncField(StructFieldViewModel& pFieldViewModel, const ra::data::models::CodeNoteModel& pOffsetNote); + const ra::data::models::CodeNoteModel* UpdatePointerChain(int nNewNode); + void UpdatePointerChainRowColor(StructFieldViewModel& pPointer); + void UpdatePointerChainValues(); void UpdateValues(); std::string GetDefinition() const; LookupItemViewModelCollection m_vNodes; + ViewModelCollection m_vPointerChain; bool m_bSyncingAddress = false; const ra::data::models::CodeNoteModel* m_pCurrentNote = nullptr; diff --git a/src/ui/win32/PointerInspectorDialog.cpp b/src/ui/win32/PointerInspectorDialog.cpp index ce57f6835..d746c4cb2 100644 --- a/src/ui/win32/PointerInspectorDialog.cpp +++ b/src/ui/win32/PointerInspectorDialog.cpp @@ -6,6 +6,7 @@ #include "ui\viewmodels\WindowManager.hh" +#include "ui\win32\bindings\GridAddressColumnBinding.hh" #include "ui\win32\bindings\GridBookmarkFormatColumnBinding.hh" #include "ui\win32\bindings\GridBookmarkValueColumnBinding.hh" #include "ui\win32\bindings\GridLookupColumnBinding.hh" @@ -53,6 +54,7 @@ PointerInspectorDialog::PointerInspectorDialog(PointerInspectorViewModel& vmPoin m_bindAddress(vmPointerFinder), m_bindNodes(vmPointerFinder), m_bindDescription(vmPointerFinder), + m_bindPointerChain(vmPointerFinder), m_bindFields(vmPointerFinder) { m_bindWindow.SetInitialPosition(RelativePosition::After, RelativePosition::Near, "Pointer Finder"); @@ -116,18 +118,46 @@ PointerInspectorDialog::PointerInspectorDialog(PointerInspectorViewModel& vmPoin m_bindFields.BindRowColor(PointerInspectorViewModel::StructFieldViewModel::RowColorProperty); m_bindFields.SetShowGridLines(true); + pOffsetColumn = std::make_unique( + PointerInspectorViewModel::StructFieldViewModel::OffsetProperty); + pOffsetColumn->SetHeader(L"Offset"); + pOffsetColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 80); + m_bindPointerChain.BindColumn(0, std::move(pOffsetColumn)); + + auto pAddressColumn = std::make_unique( + PointerInspectorViewModel::StructFieldViewModel::AddressProperty); + pAddressColumn->SetHeader(L"Address"); + pAddressColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 80); + m_bindPointerChain.BindColumn(1, std::move(pAddressColumn)); + + pValueColumn = std::make_unique( + PointerInspectorViewModel::StructFieldViewModel::CurrentValueProperty); + pValueColumn->SetHeader(L"Value"); + pValueColumn->SetWidth(GridColumnBinding::WidthType::Pixels, 72); + pValueColumn->SetAlignment(ra::ui::RelativePosition::Center); + m_bindPointerChain.BindColumn(2, std::move(pValueColumn)); + + pDescriptionColumn = std::make_unique( + PointerInspectorViewModel::StructFieldViewModel::DescriptionProperty); + pDescriptionColumn->SetHeader(L"Description"); + pDescriptionColumn->SetWidth(GridColumnBinding::WidthType::Fill, 80); + m_bindPointerChain.BindColumn(3, std::move(pDescriptionColumn)); + + m_bindPointerChain.BindItems(vmPointerFinder.PointerChain()); + m_bindPointerChain.BindRowColor(PointerInspectorViewModel::StructFieldViewModel::RowColorProperty); using namespace ra::bitwise_ops; SetAnchor(IDC_RA_ADDRESS, Anchor::Top | Anchor::Left); SetAnchor(IDC_RA_FILTER_VALUE, Anchor::Top | Anchor::Left | Anchor::Right); SetAnchor(IDC_RA_DESCRIPTION, Anchor::Top | Anchor::Left | Anchor::Right); + SetAnchor(IDC_RA_LBX_GROUPS, Anchor::Top | Anchor::Left | Anchor::Right); SetAnchor(IDC_RA_LBX_ADDRESSES, Anchor::Top | Anchor::Left | Anchor::Bottom | Anchor::Right); SetAnchor(IDC_RA_ADDBOOKMARK, Anchor::Left | Anchor::Bottom); SetAnchor(IDC_RA_COPY_ALL, Anchor::Left | Anchor::Bottom); SetAnchor(IDC_RA_PAUSE, Anchor::Right | Anchor::Bottom); SetAnchor(IDC_RA_FREEZE, Anchor::Right | Anchor::Bottom); - SetMinimumSize(480, 192); + SetMinimumSize(480, 258); } BOOL PointerInspectorDialog::OnInitDialog() @@ -135,6 +165,7 @@ BOOL PointerInspectorDialog::OnInitDialog() m_bindAddress.SetControl(*this, IDC_RA_ADDRESS); m_bindNodes.SetControl(*this, IDC_RA_FILTER_VALUE); m_bindDescription.SetControl(*this, IDC_RA_DESCRIPTION); + m_bindPointerChain.SetControl(*this, IDC_RA_LBX_GROUPS); m_bindFields.SetControl(*this, IDC_RA_LBX_ADDRESSES); return DialogBase::OnInitDialog(); diff --git a/src/ui/win32/PointerInspectorDialog.hh b/src/ui/win32/PointerInspectorDialog.hh index c06a401b1..466ba7f6f 100644 --- a/src/ui/win32/PointerInspectorDialog.hh +++ b/src/ui/win32/PointerInspectorDialog.hh @@ -47,6 +47,7 @@ private: bindings::TextBoxBinding m_bindAddress; bindings::ComboBoxBinding m_bindNodes; bindings::TextBoxBinding m_bindDescription; + bindings::GridBinding m_bindPointerChain; bindings::GridBinding m_bindFields; }; diff --git a/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp index a0e6289c5..d18b54b29 100644 --- a/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp +++ b/tests/ui/viewmodels/PointerInspectorViewModel_Tests.cpp @@ -27,8 +27,6 @@ namespace ui { namespace viewmodels { namespace tests { -constexpr int RootNodeID = -1; - TEST_CLASS(PointerInspectorViewModel_Tests) { private: @@ -80,7 +78,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) const std::wstring& sOffset, const std::wstring& sDescription, MemSize nSize, MemFormat nFormat, const std::wstring& sCurrentValue) { - const auto *pField = dynamic_cast(Bookmarks().GetItemAt(nIndex)); + const auto* pField = Bookmarks().GetItemAt(nIndex); Assert::IsNotNull(pField); Ensures(pField != nullptr); Assert::AreEqual(nOffset, pField->m_nOffset); @@ -91,6 +89,43 @@ TEST_CLASS(PointerInspectorViewModel_Tests) Assert::AreEqual(nFormat, pField->GetFormat()); Assert::AreEqual(sCurrentValue, pField->GetCurrentValue()); } + + void AssertPointerChain(gsl::index nIndex, const std::wstring& sOffset, + ra::ByteAddress nAddress, const std::wstring& sDescription, + const std::wstring& sValue) + { + const auto* pPointer = PointerChain().GetItemAt(nIndex); + Assert::IsNotNull(pPointer); + Ensures(pPointer != nullptr); + Assert::AreEqual(sOffset, pPointer->GetOffset()); + Assert::AreEqual(nAddress, pPointer->GetAddress()); + Assert::AreEqual(sDescription, pPointer->GetDescription()); + Assert::AreEqual(sValue, pPointer->GetCurrentValue()); + } + + void AssertPointerChainAddressValid(gsl::index nIndex) + { + const auto* pPointer = PointerChain().GetItemAt(nIndex); + Assert::IsNotNull(pPointer); + Ensures(pPointer != nullptr); + Assert::AreEqual(ra::ui::Color(0x00000000).ARGB, pPointer->GetRowColor().ARGB); + } + + void AssertPointerChainAddressInvalid(gsl::index nIndex) + { + const auto* pPointer = PointerChain().GetItemAt(nIndex); + Assert::IsNotNull(pPointer); + Ensures(pPointer != nullptr); + Assert::AreEqual(ra::ui::Color(0xFFFFFFC0).ARGB, pPointer->GetRowColor().ARGB); + } + + void AssertPointerChainAddressNull(gsl::index nIndex) + { + const auto* pPointer = PointerChain().GetItemAt(nIndex); + Assert::IsNotNull(pPointer); + Ensures(pPointer != nullptr); + Assert::AreEqual(ra::ui::Color(0xFFFFC0C0).ARGB, pPointer->GetRowColor().ARGB); + } }; @@ -206,7 +241,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) Assert::AreEqual(std::wstring(L"[8-bit pointer] Player data"), inspector.GetCurrentAddressNote()); Assert::AreEqual({5U}, inspector.Nodes().Count()); - Assert::AreEqual(RootNodeID, inspector.Nodes().GetItemAt(0)->GetId()); + Assert::AreEqual(PointerInspectorViewModel::PointerNodeViewModel::RootNodeId, inspector.Nodes().GetItemAt(0)->GetId()); Assert::AreEqual(std::wstring(L"[8-bit pointer] Player data"), inspector.Nodes().GetItemAt(0)->GetLabel()); Assert::AreEqual(0x00000000, inspector.Nodes().GetItemAt(1)->GetId()); Assert::AreEqual(std::wstring(L"+0000 | [8-bit pointer] Row 1"), inspector.Nodes().GetItemAt(1)->GetLabel()); @@ -217,7 +252,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) Assert::AreEqual(0x00000010, inspector.Nodes().GetItemAt(4)->GetId()); Assert::AreEqual(std::wstring(L"+0010 | [8-bit pointer] Row 3"), inspector.Nodes().GetItemAt(4)->GetLabel()); - Assert::AreEqual(RootNodeID, inspector.GetSelectedNode()); + Assert::AreEqual(PointerInspectorViewModel::PointerNodeViewModel::RootNodeId, inspector.GetSelectedNode()); Assert::AreEqual(std::wstring(L"[8-bit pointer] Player data"), inspector.GetCurrentAddressNote()); Assert::AreEqual({4U}, inspector.Bookmarks().Count()); inspector.AssertField(0, 0, 0x0CU, L"+0000", L"[8-bit pointer] Row 1", MemSize::EightBit, MemFormat::Hex, L"14"); @@ -225,11 +260,18 @@ TEST_CLASS(PointerInspectorViewModel_Tests) inspector.AssertField(2, 16, 0x1CU, L"+0010", L"[8-bit pointer] Row 3", MemSize::EightBit, MemFormat::Hex, L"24"); inspector.AssertField(3, 24, 0x24U, L"+0018", L"Generic data", MemSize::EightBit, MemFormat::Hex, L"2c"); + Assert::AreEqual({1U}, inspector.PointerChain().Count()); + inspector.AssertPointerChain(0, L"", 0x04, L"[8-bit pointer] Player data", L"0c"); + inspector.SetSelectedNode(0x00000008); Assert::AreEqual(0x00000008, inspector.GetSelectedNode()); Assert::AreEqual(std::wstring(L"[8-bit pointer] Row 2"), inspector.GetCurrentAddressNote()); Assert::AreEqual({0U}, inspector.Bookmarks().Count()); + Assert::AreEqual({2U}, inspector.PointerChain().Count()); + inspector.AssertPointerChain(0, L"", 0x04, L"[8-bit pointer] Player data", L"0c"); + inspector.AssertPointerChain(1, L"+0008", 0x14, L"[8-bit pointer] Row 2", L"1c"); + inspector.SetSelectedNode(0x00000000); Assert::AreEqual(0x00000000, inspector.GetSelectedNode()); Assert::AreEqual(std::wstring(L"[8-bit pointer] Row 1"), inspector.GetCurrentAddressNote()); @@ -237,6 +279,10 @@ TEST_CLASS(PointerInspectorViewModel_Tests) inspector.AssertField(0, 0, 0x14U, L"+0000", L"[8-bit] Column 1a", MemSize::EightBit, MemFormat::Hex, L"1c"); inspector.AssertField(1, 4, 0x18U, L"+0004", L"[8-bit pointer] Column 1b", MemSize::EightBit, MemFormat::Hex, L"20"); inspector.AssertField(2, 8, 0x1CU, L"+0008", L"[8-bit] Column 1c", MemSize::EightBit, MemFormat::Hex, L"24"); + + Assert::AreEqual({2U}, inspector.PointerChain().Count()); + inspector.AssertPointerChain(0, L"", 0x04, L"[8-bit pointer] Player data", L"0c"); + inspector.AssertPointerChain(1, L"+0000", 0x0C, L"[8-bit pointer] Row 1", L"14"); } TEST_METHOD(TestCopyDefinition) @@ -265,7 +311,7 @@ TEST_CLASS(PointerInspectorViewModel_Tests) Assert::AreEqual({4U}, inspector.GetCurrentAddress()); - Assert::AreEqual(RootNodeID, inspector.GetSelectedNode()); + Assert::AreEqual(PointerInspectorViewModel::PointerNodeViewModel::RootNodeId, inspector.GetSelectedNode()); Assert::AreEqual(std::wstring(L"[8-bit pointer] Player data"), inspector.GetCurrentAddressNote()); inspector.AssertField(1, 8, 0x14U, L"+0008", L"[8-bit pointer] Row 2", MemSize::EightBit, MemFormat::Hex, L"1c"); @@ -311,10 +357,12 @@ TEST_CLASS(PointerInspectorViewModel_Tests) Assert::AreEqual(std::wstring(L"[32-bit pointer] Player data"), inspector.GetCurrentAddressNote()); Assert::AreEqual({2U}, inspector.Bookmarks().Count()); - inspector.AssertField(0, 4, 16U, L"+0004", L"[32-bit] Current HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000010"); inspector.AssertField(1, 8, 20U, L"+0008", L"[32-bit] Max HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000014"); + Assert::AreEqual({1U}, inspector.PointerChain().Count()); + inspector.AssertPointerChain(0, L"", 0x04, L"[32-bit pointer] Player data", L"0000000c"); + // data changes memory.at(17) = 1; memory.at(20) = 99; @@ -322,11 +370,101 @@ TEST_CLASS(PointerInspectorViewModel_Tests) inspector.AssertField(0, 4, 16U, L"+0004", L"[32-bit] Current HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000110"); inspector.AssertField(1, 8, 20U, L"+0008", L"[32-bit] Max HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000063"); + Assert::AreEqual({1U}, inspector.PointerChain().Count()); + inspector.AssertPointerChain(0, L"", 0x04, L"[32-bit pointer] Player data", L"0000000c"); + // pointer changes memory.at(4) = 13; inspector.DoFrame(); inspector.AssertField(0, 4, 17U, L"+0004", L"[32-bit] Current HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"63000001"); inspector.AssertField(1, 8, 21U, L"+0008", L"[32-bit] Max HP", MemSize::ThirtyTwoBit, MemFormat::Hex, L"18000000"); + + Assert::AreEqual({1U}, inspector.PointerChain().Count()); + inspector.AssertPointerChain(0, L"", 0x04, L"[32-bit pointer] Player data", L"0000000d"); + } + + TEST_METHOD(TestDoFrameNested) + { + PointerInspectorViewModelHarness inspector; + inspector.mockGameContext.SetGameId(1); + inspector.mockGameContext.NotifyActiveGameChanged(); // enable note support + + std::array memory = {}; + inspector.mockConsoleContext.AddMemoryRegion(0x00, 0x3F, ra::data::context::ConsoleContext::AddressType::SystemRAM); + inspector.mockEmulatorContext.MockMemory(memory); + + inspector.mockGameContext.Assets().FindCodeNotes()->SetCodeNote({4U}, + L"[32-bit pointer] Player data\n" + L"+4: [32-bit pointer] 1st player\n" + L"++8: [32-bit pointer] Health\n" + L"+++0: [32-bit] Current HP\n" + L"+++8: [32-bit] Max HP"); + + memory.at(0x0004) = 0x0C; + memory.at(0x0008) = 0x08; + memory.at(0x000C) = 0x30; + memory.at(0x0010) = 0x34; + memory.at(0x0038) = 0x24; + memory.at(0x003C) = 0x20; + memory.at(0x0020) = 0x56; + memory.at(0x0024) = 0x60; + memory.at(0x0028) = 0x64; + + // update pointer values in note + inspector.mockGameContext.Assets().FindCodeNotes()->DoFrame(); + + inspector.SetCurrentAddress({4U}); + Assert::AreEqual({4U}, inspector.GetCurrentAddress()); + Assert::AreEqual(std::wstring(L"0x0004"), inspector.GetCurrentAddressText()); + Assert::AreEqual(std::wstring(L"[32-bit pointer] Player data"), inspector.GetCurrentAddressNote()); + + inspector.SetSelectedNode(0x01000008); + Assert::AreEqual(std::wstring(L"[32-bit pointer] Health"), inspector.GetCurrentAddressNote()); + + // all pointers are good + Assert::AreEqual({2U}, inspector.Bookmarks().Count()); + inspector.AssertField(0, 0, 0x20U, L"+0000", L"[32-bit] Current HP", // 20+00=20 (56) + MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000056"); + inspector.AssertField(1, 8, 0x28U, L"+0008", L"[32-bit] Max HP", // 20+08=28 (64) + MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000064"); + + Assert::AreEqual({3U}, inspector.PointerChain().Count()); + inspector.AssertPointerChain(0, L"", 0x04, L"[32-bit pointer] Player data", L"0000000c"); // 04 (0C) + inspector.AssertPointerChain(1, L"+0004", 0x10, L"[32-bit pointer] 1st player", L"00000034"); // 0C+04=10 (34) + inspector.AssertPointerChain(2, L" +0008", 0x3C, L"[32-bit pointer] Health", L"00000020"); // 34+08=3C (20) + inspector.AssertPointerChainAddressValid(0); + inspector.AssertPointerChainAddressValid(1); + inspector.AssertPointerChainAddressValid(2); + + // second level is null + memory.at(0x0010) = 0x00; + inspector.DoFrame(); + + inspector.AssertPointerChain(0, L"", 0x04, L"[32-bit pointer] Player data", L"0000000c"); // 04 (0C) + inspector.AssertPointerChain(1, L"+0004", 0x10, L"[32-bit pointer] 1st player", L"00000000"); // 0C+04=10 (00) + inspector.AssertPointerChain(2, L" +0008", 0x08, L"[32-bit pointer] Health", L"00000008"); // 00+08=08 (08) + inspector.AssertPointerChainAddressValid(0); + inspector.AssertPointerChainAddressNull(1); + inspector.AssertPointerChainAddressValid(2); + inspector.AssertField(0, 0, 0x08U, L"+0000", L"[32-bit] Current HP", // 08+00=08 (08) + MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000008"); + inspector.AssertField(1, 8, 0x10U, L"+0008", L"[32-bit] Max HP", // 08+08=10 (00) + MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000000"); + + // third level is out of range + memory.at(0x0004) = 0x20; + inspector.DoFrame(); + + inspector.AssertPointerChain(0, L"", 0x04, L"[32-bit pointer] Player data", L"00000020"); // 04 (20) + inspector.AssertPointerChain(1, L"+0004", 0x24, L"[32-bit pointer] 1st player", L"00000060"); // 20+04=24 (60) + inspector.AssertPointerChain(2, L" +0008", 0x68, L"[32-bit pointer] Health", L"00000000"); // 60+08=68 (00) + inspector.AssertPointerChainAddressValid(0); + inspector.AssertPointerChainAddressInvalid(1); + inspector.AssertPointerChainAddressNull(2); + inspector.AssertField(0, 0, 0x00U, L"+0000", L"[32-bit] Current HP", // 00+00=00 (00) + MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000000"); + inspector.AssertField(1, 8, 0x08U, L"+0008", L"[32-bit] Max HP", // 00+08=08 (08) + MemSize::ThirtyTwoBit, MemFormat::Hex, L"00000008"); } }; From 7735c28affdaba441fcb090b4336590ff7998b58 Mon Sep 17 00:00:00 2001 From: Jamiras Date: Fri, 27 Dec 2024 07:53:36 -0700 Subject: [PATCH 9/9] address analysis warnings --- src/data/models/CodeNoteModel.cpp | 4 ++-- src/data/models/CodeNoteModel.hh | 2 +- src/ui/viewmodels/MemoryBookmarksViewModel.hh | 2 +- src/ui/viewmodels/PointerInspectorViewModel.cpp | 15 ++++++++++----- src/ui/viewmodels/PointerInspectorViewModel.hh | 8 ++++---- src/ui/win32/PointerInspectorDialog.cpp | 2 +- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/data/models/CodeNoteModel.cpp b/src/data/models/CodeNoteModel.cpp index 042e7b81c..76d014a20 100644 --- a/src/data/models/CodeNoteModel.cpp +++ b/src/data/models/CodeNoteModel.cpp @@ -305,7 +305,7 @@ bool CodeNoteModel::GetNextAddress(ra::ByteAddress nAfterAddress, ra::ByteAddres return bResult; } -std::wstring CodeNoteModel::GetPrimaryNote() const noexcept +std::wstring CodeNoteModel::GetPrimaryNote() const { if (m_pPointerData != nullptr) { @@ -334,7 +334,7 @@ void CodeNoteModel::SetNote(const std::wstring& sNote, bool bImpliedPointer) if (!sLine.empty()) { - if (sLine[0] == '+' && bImpliedPointer) + if (sLine.at(0) == '+' && bImpliedPointer) { m_nMemSize = MemSize::ThirtyTwoBit; m_nBytes = 4; diff --git a/src/data/models/CodeNoteModel.hh b/src/data/models/CodeNoteModel.hh index d0a58a19f..d017eb189 100644 --- a/src/data/models/CodeNoteModel.hh +++ b/src/data/models/CodeNoteModel.hh @@ -46,7 +46,7 @@ public: bool GetPreviousAddress(ra::ByteAddress nBeforeAddress, ra::ByteAddress& nPreviousAddress) const; bool GetNextAddress(ra::ByteAddress nAfterAddress, ra::ByteAddress& nNextAddress) const; - std::wstring GetPrimaryNote() const noexcept; + std::wstring GetPrimaryNote() const; void EnumeratePointerNotes(ra::ByteAddress nPointerAddress, std::function fCallback) const; void EnumeratePointerNotes(std::function fCallback) const; diff --git a/src/ui/viewmodels/MemoryBookmarksViewModel.hh b/src/ui/viewmodels/MemoryBookmarksViewModel.hh index eb4dfab77..472c3a04e 100644 --- a/src/ui/viewmodels/MemoryBookmarksViewModel.hh +++ b/src/ui/viewmodels/MemoryBookmarksViewModel.hh @@ -138,7 +138,7 @@ public: /// /// Gets the unformatted current value of the bookmarked address. /// - uint32_t GetCurrentValueRaw() const { return m_nValue; } + uint32_t GetCurrentValueRaw() const noexcept { return m_nValue; } /// /// The for the previous value of the bookmarked address. diff --git a/src/ui/viewmodels/PointerInspectorViewModel.cpp b/src/ui/viewmodels/PointerInspectorViewModel.cpp index b226f399c..51b749f0c 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.cpp +++ b/src/ui/viewmodels/PointerInspectorViewModel.cpp @@ -119,7 +119,7 @@ void PointerInspectorViewModel::GetPointerChain(gsl::index nIndex, std::stackGetOffset(); const auto* pNestedNote = pParentNote->GetPointerNoteAtOffset(nOffset); @@ -169,7 +170,7 @@ const ra::data::models::CodeNoteModel* PointerInspectorViewModel::UpdatePointerC do { - StructFieldViewModel* pItem; + StructFieldViewModel* pItem = nullptr; if (nInsertIndex < nCount) { pItem = PointerChain().GetItemAt(nInsertIndex); @@ -180,9 +181,11 @@ const ra::data::models::CodeNoteModel* PointerInspectorViewModel::UpdatePointerC pItem = &PointerChain().Add(); } + Expects(pItem != nullptr); pItem->BeginInitialization(); const auto* pNode = sChain.top(); + Expects(pNode != nullptr); if (pNode->IsRootNode()) { pItem->m_nOffset = nCurrentAddress; @@ -267,7 +270,7 @@ void PointerInspectorViewModel::LoadNote(const ra::data::models::CodeNoteModel* const auto nOffset = nAddress - nBaseAddress; const std::wstring sOffset = ra::StringPrintf(L"+%04x", nOffset); - StructFieldViewModel* pItem; + StructFieldViewModel* pItem = nullptr; if (nInsertIndex < nCount) { pItem = Bookmarks().GetItemAt(nInsertIndex); @@ -311,7 +314,7 @@ static void LoadSubNotes(LookupItemViewModelCollection& vNodes, std::wstring sLabel; if (nDepth > 1) - sLabel = std::wstring(nDepth - 1, ' '); + sLabel = std::wstring(gsl::narrow_cast(nDepth) - 1, ' '); sLabel += ra::StringPrintf(L"+%04x | %s", nOffset, pOffsetNote.GetPointerDescription()); vNodes.Add(nParentIndex, nOffset, sLabel); @@ -384,7 +387,7 @@ void PointerInspectorViewModel::UpdatePointerChainRowColor(PointerInspectorViewM } const auto& pConsoleContext = ra::services::ServiceLocator::Get(); - bool bValid; + bool bValid = false; MemSize nMemSize = MemSize::Unknown; uint32_t nMask = 0xFFFFFFFF; @@ -473,10 +476,12 @@ std::string PointerInspectorViewModel::GetDefinition() const GetPointerChain(nSelectedNodeIndex, sChain); auto* pNote = pCodeNotes->FindCodeNoteModel(GetCurrentAddress()); + Expects(pNote != nullptr); do { const auto* pNode = sChain.top(); + Expects(pNode != nullptr); if (!pNode->IsRootNode()) pNote = pNote->GetPointerNoteAtOffset(pNode->GetOffset()); diff --git a/src/ui/viewmodels/PointerInspectorViewModel.hh b/src/ui/viewmodels/PointerInspectorViewModel.hh index ff0e33c50..c622e7bae 100644 --- a/src/ui/viewmodels/PointerInspectorViewModel.hh +++ b/src/ui/viewmodels/PointerInspectorViewModel.hh @@ -97,7 +97,7 @@ public: using MemoryBookmarkViewModel::SetAddressWithoutUpdatingValue; - int32_t m_nOffset; + int32_t m_nOffset = 0; }; class PointerNodeViewModel : public LookupItemViewModel @@ -112,9 +112,9 @@ public: { } - bool IsRootNode() const { return m_nParentIndex == -1; } - gsl::index GetParentIndex() const { return m_nParentIndex; } - int32_t GetOffset() const { return m_nOffset; } + bool IsRootNode() const noexcept { return m_nParentIndex == -1; } + gsl::index GetParentIndex() const noexcept { return m_nParentIndex; } + int32_t GetOffset() const noexcept { return m_nOffset; } private: gsl::index m_nParentIndex = -1; diff --git a/src/ui/win32/PointerInspectorDialog.cpp b/src/ui/win32/PointerInspectorDialog.cpp index d746c4cb2..de4198471 100644 --- a/src/ui/win32/PointerInspectorDialog.cpp +++ b/src/ui/win32/PointerInspectorDialog.cpp @@ -184,7 +184,7 @@ BOOL PointerInspectorDialog::OnCommand(WORD nCommand) //} case IDC_RA_COPY_ALL: { - auto* vmPointerInspector = dynamic_cast(&m_vmWindow); + const auto* vmPointerInspector = dynamic_cast(&m_vmWindow); if (vmPointerInspector) vmPointerInspector->CopyDefinition();