From b81580bc66cffb936ca778b6d05c73ed00c72f44 Mon Sep 17 00:00:00 2001 From: Marc Van de Wiele Date: Wed, 24 Apr 2024 01:16:59 +0200 Subject: [PATCH 1/5] Add copy/paste feature to memory view Enhanced user experience by providing a way to copy and paste value directly in the memory view. --- .../bindings/MemoryViewerControlBinding.cpp | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp index 9cf8128d0..fa8341802 100644 --- a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp +++ b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp @@ -3,6 +3,7 @@ #include "ra_fwd.h" #include "ra_utility.h" +#include "services\IClipboard.hh" #include "ui/EditorTheme.hh" #include "ui/drawing/gdi/GDISurface.hh" @@ -231,6 +232,56 @@ bool MemoryViewerControlBinding::HandleNavigation(UINT nChar) } return true; + case 'C': + if (bControlHeld) + { + const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); + const auto iValue = pEmulatorContext.ReadMemory(m_pViewModel.GetAddress(), m_pViewModel.GetSize()); + std::wstring sValue = ra::data::MemSizeFormat(iValue, m_pViewModel.GetSize(), MemFormat::Hex); + + ra::services::ServiceLocator::Get().SetText(ra::Widen(sValue)); + } + return true; + + case 'V': + if (bControlHeld) + { + auto nAddress = m_pViewModel.GetAddress(); + const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); + std::wstring sClipboardText = ra::services::ServiceLocator::Get().GetText(); + auto n = m_pViewModel.GetSize(); + + if (sClipboardText.empty()) + return false; + + // Check if the string is a valid hexadecimal value + for (wchar_t ch : sClipboardText) + if (!iswxdigit(ch)) return false; + + // Padding zeroes depending if shift is pressed (strict mode) or not (replace mode) + if (bShiftHeld) + { + const auto nNibblesForSize = ra::data::MemSizeBytes(m_pViewModel.GetSize()) * 2; + + if (nNibblesForSize < sClipboardText.length()) + sClipboardText = sClipboardText.substr(sClipboardText.length() - nNibblesForSize); + else + { + std::wstring sPadding(nNibblesForSize - sClipboardText.length(), L'0'); + sClipboardText = (sPadding + sClipboardText); + } + }else + sClipboardText = sClipboardText.length() % 2 == 1 ? (L"0" + sClipboardText) : sClipboardText; + + // Writing every byte separately considerably improves stability and enables long sequences to be pasted + for (int i = sClipboardText.length(); i != 0; i-=2) + { + std::wstring sValue = sClipboardText.substr(i - 2, 2); + pEmulatorContext.WriteMemoryByte(nAddress++, std::stoi(sValue, 0, 16)); + } + } + return true; + default: return false; } @@ -308,7 +359,9 @@ void MemoryViewerControlBinding::RenderMemViewer() const auto& pRenderImage = m_pViewModel.GetRenderImage(); const auto& pEditorTheme = ra::services::ServiceLocator::Get(); - HBRUSH hBackground = CreateSolidBrush(RGB(pEditorTheme.ColorBackground().Channel.R, pEditorTheme.ColorBackground().Channel.G, pEditorTheme.ColorBackground().Channel.B)); + HBRUSH hBackground = CreateSolidBrush(RGB(pEditorTheme.ColorBackground().Channel.R, + pEditorTheme.ColorBackground().Channel.G, + pEditorTheme.ColorBackground().Channel.B)); // left margin RECT rcFill{ rcClient.left, rcClient.top, rcClient.left + MEMVIEW_MARGIN, rcClient.bottom - 1 }; From 39835141a7ece77ce5bf861e4c8f5e2f0885bb2a Mon Sep 17 00:00:00 2001 From: Marc Van de Wiele Date: Sat, 27 Apr 2024 22:10:49 +0200 Subject: [PATCH 2/5] Refactor copy/paste feature following review - New HandleShortcut function implemented to ViewModel - Improve code to match repository naming convention - Prevent from using paste feature in the pointer finder - Using of ParseHex instead of iswxdigit and refactoring logic based on this change --- src/ui/viewmodels/MemoryViewerViewModel.hh | 3 + .../bindings/MemoryViewerControlBinding.cpp | 60 ++++++++++++------- .../bindings/MemoryViewerControlBinding.hh | 2 + 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/ui/viewmodels/MemoryViewerViewModel.hh b/src/ui/viewmodels/MemoryViewerViewModel.hh index f295118cc..2c91623fa 100644 --- a/src/ui/viewmodels/MemoryViewerViewModel.hh +++ b/src/ui/viewmodels/MemoryViewerViewModel.hh @@ -160,6 +160,9 @@ public: /// void SetSize(MemSize value) { SetValue(SizeProperty, ra::etoi(value)); } + bool IsReadOnly() const noexcept { return m_bReadOnly; } + void SetReadOnly(bool value) noexcept { m_bReadOnly = value; } + void OnClick(int nX, int nY); void OnResized(int nWidth, int nHeight); bool OnChar(char c); diff --git a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp index fa8341802..fe3aa4a1a 100644 --- a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp +++ b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp @@ -140,6 +140,8 @@ bool MemoryViewerControlBinding::OnKeyDown(UINT nChar) if (!m_pViewModel.IsAddressFixed()) bHandled = HandleNavigation(nChar); + if (!bHandled) + bHandled = HandleShortcut(nChar); m_bSuppressMemoryViewerInvalidate = false; @@ -232,52 +234,64 @@ bool MemoryViewerControlBinding::HandleNavigation(UINT nChar) } return true; + default: + return false; + } +} + +bool MemoryViewerControlBinding::HandleShortcut(UINT nChar) +{ + const bool bShiftHeld = (GetKeyState(VK_SHIFT) < 0); + const bool bControlHeld = (GetKeyState(VK_CONTROL) < 0); + + switch (nChar) + { case 'C': if (bControlHeld) { const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); - const auto iValue = pEmulatorContext.ReadMemory(m_pViewModel.GetAddress(), m_pViewModel.GetSize()); - std::wstring sValue = ra::data::MemSizeFormat(iValue, m_pViewModel.GetSize(), MemFormat::Hex); + const auto nValue = pEmulatorContext.ReadMemory(m_pViewModel.GetAddress(), m_pViewModel.GetSize()); + std::wstring sValue = ra::data::MemSizeFormat(nValue, m_pViewModel.GetSize(), MemFormat::Hex); ra::services::ServiceLocator::Get().SetText(ra::Widen(sValue)); } return true; case 'V': - if (bControlHeld) + if (bControlHeld and !m_pViewModel.IsReadOnly()) { auto nAddress = m_pViewModel.GetAddress(); const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); std::wstring sClipboardText = ra::services::ServiceLocator::Get().GetText(); - auto n = m_pViewModel.GetSize(); + const auto nNibblesForSize = ra::data::MemSizeBytes(m_pViewModel.GetSize()); + std::vector v_nValues; + std::wstring sError; if (sClipboardText.empty()) return false; - // Check if the string is a valid hexadecimal value - for (wchar_t ch : sClipboardText) - if (!iswxdigit(ch)) return false; - - // Padding zeroes depending if shift is pressed (strict mode) or not (replace mode) - if (bShiftHeld) + // Split the clipboard value into substrings matching the current size and check if they're valid hexadecimal values + for (size_t i = 0; i < sClipboardText.size(); i += nNibblesForSize * 2) { - const auto nNibblesForSize = ra::data::MemSizeBytes(m_pViewModel.GetSize()) * 2; - - if (nNibblesForSize < sClipboardText.length()) - sClipboardText = sClipboardText.substr(sClipboardText.length() - nNibblesForSize); - else + std::wstring sSubString = sClipboardText.substr(i, nNibblesForSize * 2); + unsigned int nValue; + + if (!ra::ParseHex(sSubString, 0xFFFFFFFF, nValue, sError)) { - std::wstring sPadding(nNibblesForSize - sClipboardText.length(), L'0'); - sClipboardText = (sPadding + sClipboardText); + ra::ui::viewmodels::MessageBoxViewModel::ShowWarningMessage(L"Paste value failed", sError); + return false; } - }else - sClipboardText = sClipboardText.length() % 2 == 1 ? (L"0" + sClipboardText) : sClipboardText; - // Writing every byte separately considerably improves stability and enables long sequences to be pasted - for (int i = sClipboardText.length(); i != 0; i-=2) + v_nValues.push_back(nValue); + } + + for (auto nValue : v_nValues) { - std::wstring sValue = sClipboardText.substr(i - 2, 2); - pEmulatorContext.WriteMemoryByte(nAddress++, std::stoi(sValue, 0, 16)); + //Single mode writes only the first value, multi mode (shift) writes every values + pEmulatorContext.WriteMemory(nAddress, m_pViewModel.GetSize(), nValue); + if (!bShiftHeld) + break; + nAddress += nNibblesForSize; } } return true; diff --git a/src/ui/win32/bindings/MemoryViewerControlBinding.hh b/src/ui/win32/bindings/MemoryViewerControlBinding.hh index 627a0ba5c..023b9a4b6 100644 --- a/src/ui/win32/bindings/MemoryViewerControlBinding.hh +++ b/src/ui/win32/bindings/MemoryViewerControlBinding.hh @@ -5,6 +5,7 @@ #include "ControlBinding.hh" #include "ui/viewmodels/MemoryViewerViewModel.hh" +#include "ui\viewmodels\MessageBoxViewModel.hh" namespace ra { namespace ui { @@ -63,6 +64,7 @@ protected: private: bool HandleNavigation(UINT nChar); + bool HandleShortcut(UINT nChar); bool m_bSuppressMemoryViewerInvalidate = false; ra::ui::viewmodels::MemoryViewerViewModel& m_pViewModel; From 2ed7a8155a2a3c71c28913d79e3b5b4a447f0574 Mon Sep 17 00:00:00 2001 From: Marc Van de Wiele Date: Mon, 29 Apr 2024 01:56:24 +0200 Subject: [PATCH 3/5] Move copy/paste logic to their own functions OnCopy/OnPaste --- .../bindings/MemoryViewerControlBinding.cpp | 100 +++++++++++------- .../bindings/MemoryViewerControlBinding.hh | 3 + 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp index fe3aa4a1a..0c9c2f57e 100644 --- a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp +++ b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp @@ -249,50 +249,14 @@ bool MemoryViewerControlBinding::HandleShortcut(UINT nChar) case 'C': if (bControlHeld) { - const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); - const auto nValue = pEmulatorContext.ReadMemory(m_pViewModel.GetAddress(), m_pViewModel.GetSize()); - std::wstring sValue = ra::data::MemSizeFormat(nValue, m_pViewModel.GetSize(), MemFormat::Hex); - - ra::services::ServiceLocator::Get().SetText(ra::Widen(sValue)); + OnCopy(); } return true; case 'V': if (bControlHeld and !m_pViewModel.IsReadOnly()) { - auto nAddress = m_pViewModel.GetAddress(); - const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); - std::wstring sClipboardText = ra::services::ServiceLocator::Get().GetText(); - const auto nNibblesForSize = ra::data::MemSizeBytes(m_pViewModel.GetSize()); - std::vector v_nValues; - std::wstring sError; - - if (sClipboardText.empty()) - return false; - - // Split the clipboard value into substrings matching the current size and check if they're valid hexadecimal values - for (size_t i = 0; i < sClipboardText.size(); i += nNibblesForSize * 2) - { - std::wstring sSubString = sClipboardText.substr(i, nNibblesForSize * 2); - unsigned int nValue; - - if (!ra::ParseHex(sSubString, 0xFFFFFFFF, nValue, sError)) - { - ra::ui::viewmodels::MessageBoxViewModel::ShowWarningMessage(L"Paste value failed", sError); - return false; - } - - v_nValues.push_back(nValue); - } - - for (auto nValue : v_nValues) - { - //Single mode writes only the first value, multi mode (shift) writes every values - pEmulatorContext.WriteMemory(nAddress, m_pViewModel.GetSize(), nValue); - if (!bShiftHeld) - break; - nAddress += nNibblesForSize; - } + return OnPaste(bShiftHeld); } return true; @@ -355,6 +319,66 @@ void MemoryViewerControlBinding::OnViewModelIntValueChanged(const IntModelProper } } +void MemoryViewerControlBinding::OnCopy() +{ + const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); + const auto nValue = pEmulatorContext.ReadMemory(m_pViewModel.GetAddress(), m_pViewModel.GetSize()); + std::wstring sValue = ra::data::MemSizeFormat(nValue, m_pViewModel.GetSize(), MemFormat::Hex); + + ra::services::ServiceLocator::Get().SetText(ra::Widen(sValue)); +} + +bool MemoryViewerControlBinding::OnPaste(bool bShiftHeld) +{ + auto nAddress = m_pViewModel.GetAddress(); + const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); + std::wstring sClipboardText = ra::services::ServiceLocator::Get().GetText(); + const auto nBytesForSize = ra::data::MemSizeBytes(m_pViewModel.GetSize()); + std::vector vValues; + std::wstring sError; + + if (sClipboardText.empty()) + return false; + + // Split the clipboard value into substrings matching the current size and check if they're valid hexadecimal values + // If the clipboard text is smaller than a single write, it will be treated as a padded value: + // C => 0C, 000C, or 0000000C (depending on currently selected viewer size) + // If shift is held, and the clipboard string is bigger than a single write, multiple writes will occur. + // If the last write is not a complete chunk, it will be padded in the same way as described above: + // 12345678C => 12 34 56 78 0C, 1234 5678 000C, or 12345678 0000000C + for (size_t i = 0; i < sClipboardText.size(); i += nBytesForSize * 2) + { + std::wstring sSubString = sClipboardText.substr(i, nBytesForSize * 2); + unsigned int nValue; + + if (!ra::ParseHex(sSubString, 0xFFFFFFFF, nValue, sError)) + { + ra::ui::viewmodels::MessageBoxViewModel::ShowWarningMessage(L"Paste value failed", sError); + return false; + } + + // Single mode writes only the first value, + if (!bShiftHeld) + { + pEmulatorContext.WriteMemory(nAddress, m_pViewModel.GetSize(), nValue); + return true; + } + + vValues.push_back(nValue); + } + + // Multi mode (shift) writes every values + for (auto nValue : vValues) + { + pEmulatorContext.WriteMemory(nAddress, m_pViewModel.GetSize(), nValue); + if (!bShiftHeld) + break; + nAddress += nBytesForSize; + } + + return true; +} + void MemoryViewerControlBinding::Invalidate() { if (m_pViewModel.NeedsRedraw() && !m_bSuppressMemoryViewerInvalidate) diff --git a/src/ui/win32/bindings/MemoryViewerControlBinding.hh b/src/ui/win32/bindings/MemoryViewerControlBinding.hh index 023b9a4b6..1fffd0014 100644 --- a/src/ui/win32/bindings/MemoryViewerControlBinding.hh +++ b/src/ui/win32/bindings/MemoryViewerControlBinding.hh @@ -48,6 +48,9 @@ public: void OnGotFocus() override; void OnLostFocus() override; + void OnCopy(); + bool OnPaste(bool bShiftHeld); + void Invalidate(); void SetHWND(DialogBase& pDialog, HWND hControl) override; From 00659695017f82067144e8d858ded685d2d34e62 Mon Sep 17 00:00:00 2001 From: Marc Van de Wiele Date: Fri, 3 May 2024 18:27:12 +0200 Subject: [PATCH 4/5] Remove pointless array logic in OnPaste() function --- .../bindings/MemoryViewerControlBinding.cpp | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp index 0c9c2f57e..6aafcc124 100644 --- a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp +++ b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp @@ -325,7 +325,7 @@ void MemoryViewerControlBinding::OnCopy() const auto nValue = pEmulatorContext.ReadMemory(m_pViewModel.GetAddress(), m_pViewModel.GetSize()); std::wstring sValue = ra::data::MemSizeFormat(nValue, m_pViewModel.GetSize(), MemFormat::Hex); - ra::services::ServiceLocator::Get().SetText(ra::Widen(sValue)); + ra::services::ServiceLocator::Get().SetText(sValue); } bool MemoryViewerControlBinding::OnPaste(bool bShiftHeld) @@ -334,7 +334,6 @@ bool MemoryViewerControlBinding::OnPaste(bool bShiftHeld) const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); std::wstring sClipboardText = ra::services::ServiceLocator::Get().GetText(); const auto nBytesForSize = ra::data::MemSizeBytes(m_pViewModel.GetSize()); - std::vector vValues; std::wstring sError; if (sClipboardText.empty()) @@ -350,30 +349,18 @@ bool MemoryViewerControlBinding::OnPaste(bool bShiftHeld) { std::wstring sSubString = sClipboardText.substr(i, nBytesForSize * 2); unsigned int nValue; - if (!ra::ParseHex(sSubString, 0xFFFFFFFF, nValue, sError)) { ra::ui::viewmodels::MessageBoxViewModel::ShowWarningMessage(L"Paste value failed", sError); return false; } - // Single mode writes only the first value, - if (!bShiftHeld) - { - pEmulatorContext.WriteMemory(nAddress, m_pViewModel.GetSize(), nValue); - return true; - } - - vValues.push_back(nValue); - } - - // Multi mode (shift) writes every values - for (auto nValue : vValues) - { pEmulatorContext.WriteMemory(nAddress, m_pViewModel.GetSize(), nValue); - if (!bShiftHeld) - break; nAddress += nBytesForSize; + + // Single mode writes only the first value else multi mode (shift) writes every values + if (!bShiftHeld) + return true; } return true; From 9a76604fd6f3d0c4b7d2ca768f6edde986de41d1 Mon Sep 17 00:00:00 2001 From: Marc Van de Wiele Date: Tue, 21 May 2024 20:51:01 +0200 Subject: [PATCH 5/5] Fix CI / Win64-Analysis checks --- src/ui/win32/bindings/MemoryViewerControlBinding.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp index 6aafcc124..8388b8687 100644 --- a/src/ui/win32/bindings/MemoryViewerControlBinding.cpp +++ b/src/ui/win32/bindings/MemoryViewerControlBinding.cpp @@ -333,7 +333,7 @@ bool MemoryViewerControlBinding::OnPaste(bool bShiftHeld) auto nAddress = m_pViewModel.GetAddress(); const auto& pEmulatorContext = ra::services::ServiceLocator::Get(); std::wstring sClipboardText = ra::services::ServiceLocator::Get().GetText(); - const auto nBytesForSize = ra::data::MemSizeBytes(m_pViewModel.GetSize()); + const size_t nBytesForSize = ra::data::MemSizeBytes(m_pViewModel.GetSize()); std::wstring sError; if (sClipboardText.empty()) @@ -356,7 +356,7 @@ bool MemoryViewerControlBinding::OnPaste(bool bShiftHeld) } pEmulatorContext.WriteMemory(nAddress, m_pViewModel.GetSize(), nValue); - nAddress += nBytesForSize; + nAddress += gsl::narrow_cast(nBytesForSize); // Single mode writes only the first value else multi mode (shift) writes every values if (!bShiftHeld)