diff --git a/src/ui/viewmodels/MemoryViewerViewModel.cpp b/src/ui/viewmodels/MemoryViewerViewModel.cpp index b44ca9e8..61e01021 100644 --- a/src/ui/viewmodels/MemoryViewerViewModel.cpp +++ b/src/ui/viewmodels/MemoryViewerViewModel.cpp @@ -4,6 +4,7 @@ #include "data\context\EmulatorContext.hh" #include "data\context\GameContext.hh" +#include "data\context\ConsoleContext.hh" #include "ui\EditorTheme.hh" #include "ui\viewmodels\WindowManager.hh" @@ -895,6 +896,21 @@ void MemoryViewerViewModel::OnClick(int nX, int nY) } } +void MemoryViewerViewModel::OnShiftClick(int nX, int nY) +{ + const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); + const auto& pConsoleContext = ra::services::ServiceLocator::Get(); + + OnClick(nX, nY); + + ra::ByteAddress nAddress = (pEmulatorContext.ReadMemory(GetAddress(), GetSize())); + const auto nConvertedAddress = pConsoleContext.ByteAddressFromRealAddress(nAddress); + if (nConvertedAddress != 0xFFFFFFFF) + { + SetAddress(nConvertedAddress); + } +} + void MemoryViewerViewModel::OnResized(int nWidth, int nHeight) { if (s_pFontSurface == nullptr) diff --git a/src/ui/viewmodels/MemoryViewerViewModel.hh b/src/ui/viewmodels/MemoryViewerViewModel.hh index f295118c..642f81ba 100644 --- a/src/ui/viewmodels/MemoryViewerViewModel.hh +++ b/src/ui/viewmodels/MemoryViewerViewModel.hh @@ -161,6 +161,7 @@ public: void SetSize(MemSize value) { SetValue(SizeProperty, ra::etoi(value)); } void OnClick(int nX, int nY); + void OnShiftClick(int nX, int nY); void OnResized(int nWidth, int nHeight); bool OnChar(char c); void OnGotFocus(); diff --git a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp index 9cf8128d..fe69e73e 100644 --- a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp +++ b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp @@ -256,7 +256,10 @@ void MemoryViewerControlBinding::OnClick(POINT point) // multiple properties may change while typing, we'll do a single Invalidate after we're done m_bSuppressMemoryViewerInvalidate = true; - m_pViewModel.OnClick(point.x, point.y); + if (GetKeyState(VK_SHIFT) < 0) + m_pViewModel.OnShiftClick(point.x, point.y); + else + m_pViewModel.OnClick(point.x, point.y); m_bSuppressMemoryViewerInvalidate = false; SetFocus(m_hWnd); diff --git a/tests/ui/viewmodels/MemoryViewerViewModel_Tests.cpp b/tests/ui/viewmodels/MemoryViewerViewModel_Tests.cpp index b900ee33..26aa60e4 100644 --- a/tests/ui/viewmodels/MemoryViewerViewModel_Tests.cpp +++ b/tests/ui/viewmodels/MemoryViewerViewModel_Tests.cpp @@ -7,6 +7,7 @@ #include "tests\RA_UnitTestHelpers.h" #include "tests\mocks\MockEmulatorContext.hh" #include "tests\mocks\MockGameContext.hh" +#include "tests\mocks\MockConsoleContext.hh" #include "tests\mocks\MockWindowManager.hh" #undef GetMessage @@ -71,6 +72,9 @@ TEST_CLASS(MemoryViewerViewModel_Tests) bool IsReadOnly() const noexcept { return m_bReadOnly; } void SetReadOnly(bool value) noexcept { m_bReadOnly = value; } + bool IsAddressFixed() const noexcept { return m_bAddressFixed; } + void SetAddressFixed(bool value) noexcept { m_bAddressFixed = value; } + void InitializeMemory(size_t nSize) { unsigned char* pBytes = new unsigned char[nSize]; @@ -1918,8 +1922,120 @@ TEST_CLASS(MemoryViewerViewModel_Tests) Assert::IsTrue(viewer.NeedsRedraw()); viewer.MockRender(); } + + TEST_METHOD(TestOnShiftClickEightBit) + { + ra::data::context::mocks::MockConsoleContext mockConsole(PlayStation, L"PlayStation"); + + MemoryViewerViewModelHarness viewer; + viewer.InitializeMemory(0xFFFF); + + Assert::AreEqual(MemSize::EightBit, viewer.GetSize()); + Assert::AreEqual({ 0U }, viewer.GetAddress()); + Assert::AreEqual({ 0U }, viewer.GetSelectedNibble()); + + // If fixed address, ignore + viewer.mockEmulatorContext.WriteMemoryByte(0U, 0x20); + viewer.SetAddressFixed(true); + viewer.OnShiftClick(10 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({0x0U}, viewer.GetAddress()); + + // If not fixed address and Shift click on the first byte containing 0x20 should lead to address 0x20 + viewer.SetAddressFixed(false); + viewer.OnShiftClick(10 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({ 0x20U }, viewer.GetAddress()); + + // Shift click on the second byte containing 0x0 should do nothing + viewer.SetAddress(0); + viewer.mockEmulatorContext.WriteMemoryByte(1U, 0x0); + viewer.OnShiftClick(15 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({0x1U}, viewer.GetAddress()); + } + + TEST_METHOD(TestOnShiftClickSixteenBit) + { + ra::data::context::mocks::MockConsoleContext mockConsole(PlayStation, L"PlayStation"); + + MemoryViewerViewModelHarness viewer; + viewer.InitializeMemory(0x1FFF); + viewer.SetSize(MemSize::SixteenBit); + + Assert::AreEqual(MemSize::SixteenBit, viewer.GetSize()); + Assert::AreEqual({0U}, viewer.GetAddress()); + Assert::AreEqual({0U}, viewer.GetSelectedNibble()); + + // If fixed address, ignore + viewer.SetAddressFixed(true); + viewer.OnShiftClick(10 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({0x0U}, viewer.GetAddress()); + + // If not fixed address and Shift click on the first word containing 0x20 should lead to address 0x20 + viewer.SetAddress(0); + viewer.SetAddressFixed(false); + viewer.mockEmulatorContext.WriteMemory(0U, MemSize::SixteenBit, 0x0020); + viewer.OnShiftClick(10 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({0x20U}, viewer.GetAddress()); + + // Shift click on the first word containing 0x0140 should lead to address 0x0140 + viewer.SetAddress(0); + viewer.mockEmulatorContext.WriteMemory(0U, MemSize::SixteenBit, 0x0140); + viewer.OnShiftClick(10 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({0x0140U}, viewer.GetAddress()); + + // Shift click on the second word containing 0x0 should do nothing + viewer.SetAddress(0x4); + viewer.mockEmulatorContext.WriteMemory(4U, MemSize::SixteenBit, 0x0); + viewer.OnShiftClick(22 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({0x4U}, viewer.GetAddress()); + } + + TEST_METHOD(TestOnShiftClickThirtyTwoBit) + { + ra::data::context::mocks::MockConsoleContext mockConsole(PlayStation, L"Playstation"); + + MemoryViewerViewModelHarness viewer; + viewer.InitializeMemory(0x1FFF); // PSX full memory + viewer.SetSize(MemSize::ThirtyTwoBit); + + Assert::AreEqual(MemSize::ThirtyTwoBit, viewer.GetSize()); + Assert::AreEqual({0U}, viewer.GetAddress()); + Assert::AreEqual({0U}, viewer.GetSelectedNibble()); + + // If fixed address, ignore + viewer.SetAddressFixed(true); + viewer.OnShiftClick(10 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({0x0U}, viewer.GetAddress()); + + // If not fixed address and Shift click on the first dword containing 0x1234 should lead to address 0x1234 + viewer.SetAddress(0); + viewer.SetAddressFixed(false); + viewer.mockEmulatorContext.WriteMemory(0U, MemSize::ThirtyTwoBit, 0x1234); + viewer.OnShiftClick(10 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({0x1234U}, viewer.GetAddress()); + + // Shift click on the first dword containing 0x80000123 should lead to address 0x00000123 as PSX has mirrored RAM + // on 0x80000000-0x801FFFFF mapped to 0x00000000-0x001FFFFF + viewer.SetAddress(0); + viewer.mockEmulatorContext.WriteMemory(0U, MemSize::ThirtyTwoBit, 0x80000123); + viewer.OnShiftClick(10 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({0x00000123}, viewer.GetAddress()); + + // Shift click on the second dword containing 0x0 should do nothing + viewer.SetAddress(0x8); + viewer.mockEmulatorContext.WriteMemory(8U, MemSize::ThirtyTwoBit, 0x0); + viewer.OnShiftClick(28 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({0x8U}, viewer.GetAddress()); + + // Shift click on the first dword containing 0x005FFFFF should do nothing as 0x005FFFFF exceed the + // last address in the current console context (PSX : 0x1FFFFF) and can't be converted to real address + viewer.SetAddress(0); + viewer.mockEmulatorContext.WriteMemory(0U, MemSize::ThirtyTwoBit, 0x005FFFFF); + viewer.OnShiftClick(10 * CHAR_WIDTH, CHAR_HEIGHT + 4); + Assert::AreEqual({0x0}, viewer.GetAddress()); + } }; + } // namespace tests } // namespace viewmodels } // namespace ui