#include #include #include #include "ext/xxhash.h" #include "Common/StringUtils.h" #include "Core/Config.h" #include "Core/System.h" #include "Core/MemMap.h" #include "Core/Reporting.h" #include "Core/RetroAchievements.h" #include "Windows/W32Util/ContextMenu.h" #include "Windows/W32Util/Misc.h" #include "Windows/InputBox.h" #include "Windows/main.h" #include "Windows/resource.h" #include "Common/System/Display.h" #include "Debugger_Disasm.h" #include "DebuggerShared.h" #include "CtrlMemView.h" #include "DumpMemoryWindow.h" wchar_t CtrlMemView::szClassName[] = L"CtrlMemView"; static constexpr UINT_PTR IDT_REDRAW_DELAYED = 0xC0DE0001; static constexpr UINT REDRAW_DELAY = 1000 / 60; // We also redraw regularly, since data changes during runtime. static constexpr UINT_PTR IDT_REDRAW_AUTO = 0xC0DE0002; static constexpr UINT REDRAW_INTERVAL = 1000; CtrlMemView::CtrlMemView(HWND _wnd) { wnd=_wnd; SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)this); SetWindowLong(wnd, GWL_STYLE, GetWindowLong(wnd,GWL_STYLE) | WS_VSCROLL); SetScrollRange(wnd, SB_VERT, -1,1,TRUE); const float fontScale = 1.0f / g_display.dpi_scale_real_y; charWidth_ = g_Config.iFontWidth * fontScale; rowHeight_ = g_Config.iFontHeight * fontScale; offsetPositionY_ = offsetLine * rowHeight_; font = CreateFont(rowHeight_, charWidth_, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, L"Lucida Console"); underlineFont = CreateFont(rowHeight_, charWidth_, 0, 0, FW_DONTCARE, FALSE, TRUE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, L"Lucida Console"); windowStart_ = curAddress_; selectRangeStart_ = curAddress_; selectRangeEnd_ = curAddress_ + 1; lastSelectReset_ = curAddress_; addressStartX_ = charWidth_; hexStartX_ = addressStartX_ + 9 * charWidth_; asciiStartX_ = hexStartX_ + (rowSize_ * 3 + 1) * charWidth_; // set redraw timer SetTimer(wnd, IDT_REDRAW_AUTO, REDRAW_INTERVAL, nullptr); } CtrlMemView::~CtrlMemView() { DeleteObject(font); DeleteObject(underlineFont); } void CtrlMemView::init() { WNDCLASSEX wc; wc.cbSize = sizeof(wc); wc.lpszClassName = szClassName; wc.hInstance = GetModuleHandle(0); wc.lpfnWndProc = CtrlMemView::wndProc; wc.hCursor = LoadCursor (NULL, IDC_ARROW); wc.hIcon = 0; wc.lpszMenuName = 0; wc.hbrBackground = (HBRUSH)GetSysColorBrush(COLOR_WINDOW); wc.style = 0; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof( CtrlMemView * ); wc.hIconSm = 0; RegisterClassEx(&wc); } void CtrlMemView::deinit() { //UnregisterClass(szClassName, hInst) } LRESULT CALLBACK CtrlMemView::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { CtrlMemView *ccp = CtrlMemView::getFrom(hwnd); static bool lmbDown=false,rmbDown=false; switch(msg) { case WM_NCCREATE: // Allocate a new CustCtrl structure for this window. ccp = new CtrlMemView(hwnd); // Continue with window creation. return ccp != NULL; // Clean up when the window is destroyed. case WM_NCDESTROY: delete ccp; break; case WM_SETFONT: break; case WM_SIZE: ccp->redraw(); break; case WM_PAINT: ccp->onPaint(wParam,lParam); break; case WM_VSCROLL: ccp->onVScroll(wParam,lParam); break; case WM_MOUSEWHEEL: if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) { ccp->ScrollWindow(-3, GotoModeFromModifiers(false)); } else if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) { ccp->ScrollWindow(3, GotoModeFromModifiers(false)); } break; case WM_ERASEBKGND: return FALSE; case WM_KEYDOWN: ccp->onKeyDown(wParam,lParam); return 0; case WM_CHAR: ccp->onChar(wParam,lParam); return 0; case WM_KEYUP: return 0; case WM_LBUTTONDOWN: SetFocus(hwnd); lmbDown=true; ccp->onMouseDown(wParam,lParam,1); break; case WM_RBUTTONDOWN: SetFocus(hwnd); rmbDown=true; ccp->onMouseDown(wParam,lParam,2); break; case WM_MOUSEMOVE: ccp->onMouseMove(wParam,lParam,(lmbDown?1:0) | (rmbDown?2:0)); break; case WM_LBUTTONUP: lmbDown=false; ccp->onMouseUp(wParam,lParam,1); break; case WM_RBUTTONUP: rmbDown=false; ccp->onMouseUp(wParam,lParam,2); break; case WM_SETFOCUS: SetFocus(hwnd); ccp->hasFocus_ = true; ccp->redraw(); break; case WM_KILLFOCUS: ccp->hasFocus_ = false; ccp->redraw(); break; case WM_GETDLGCODE: // we want to process the arrow keys and all characters ourselves return DLGC_WANTARROWS|DLGC_WANTCHARS|DLGC_WANTTAB; break; case WM_TIMER: // This is actually delayed too, using another timer. That way we won't update twice. if (wParam == IDT_REDRAW_AUTO && IsWindowVisible(ccp->wnd)) ccp->redraw(); if (wParam == IDT_REDRAW_DELAYED) { InvalidateRect(hwnd, nullptr, FALSE); UpdateWindow(hwnd); ccp->redrawScheduled_ = false; KillTimer(hwnd, wParam); } break; default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); } CtrlMemView *CtrlMemView::getFrom(HWND hwnd) { return (CtrlMemView *)GetWindowLongPtr(hwnd, GWLP_USERDATA); } void CtrlMemView::onPaint(WPARAM wParam, LPARAM lParam) { if (Achievements::HardcoreModeActive()) return; auto memLock = Memory::Lock(); // draw to a bitmap for double buffering PAINTSTRUCT ps; HDC actualHdc = BeginPaint(wnd, &ps); HDC hdc = CreateCompatibleDC(actualHdc); HBITMAP hBM = CreateCompatibleBitmap(actualHdc, rect_.right - rect_.left, rect_.bottom - rect_.top); SelectObject(hdc, hBM); SetBkMode(hdc,OPAQUE); HPEN standardPen = CreatePen(0,0,0xFFFFFF); HBRUSH standardBrush = CreateSolidBrush(0xFFFFFF); COLORREF standardBG = GetBkColor(hdc); HPEN oldPen = (HPEN) SelectObject(hdc,standardPen); HBRUSH oldBrush = (HBRUSH) SelectObject(hdc,standardBrush); HFONT oldFont = (HFONT) SelectObject(hdc,(HGDIOBJ)font); // white background SelectObject(hdc,standardPen); SelectObject(hdc,standardBrush); Rectangle(hdc, 0, 0, rect_.right, rect_.bottom); if (displayOffsetScale_) drawOffsetScale(hdc); std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, windowStart_, (visibleRows_ + 1) * rowSize_); COLORREF lastTextCol = 0x000000; COLORREF lastBGCol = standardBG; auto setTextColors = [&](COLORREF fg, COLORREF bg) { if (lastTextCol != fg) { SetTextColor(hdc, fg); lastTextCol = fg; } if (lastBGCol != bg) { SetBkColor(hdc, bg); lastBGCol = bg; } }; _assert_msg_(((windowStart_ | rowSize_) & 3) == 0, "readMemory() can't handle unaligned reads"); // draw one extra row that may be partially visible for (int i = 0; i < visibleRows_ + 1; i++) { int rowY = rowHeight_ * i; // Skip the first X rows to make space for the offsets. if (displayOffsetScale_) rowY += rowHeight_ * offsetSpace; char temp[32]; uint32_t address = windowStart_ + i * rowSize_; snprintf(temp, sizeof(temp), "%08X", address); setTextColors(0x600000, standardBG); TextOutA(hdc, addressStartX_, rowY, temp, (int)strlen(temp)); union { uint32_t words[4]; uint8_t bytes[16]; } memory; int valid = debugger_ != nullptr && debugger_->isAlive() ? Memory::ValidSize(address, 16) / 4 : 0; for (int i = 0; i < valid; ++i) { memory.words[i] = debugger_->readMemory(address + i * 4); } for (int j = 0; j < rowSize_; j++) { const uint32_t byteAddress = (address + j) & ~0xC0000000; std::string tag; bool tagContinues = false; for (auto info : memRangeInfo) { if (info.start <= byteAddress && info.start + info.size > byteAddress) { tag = info.tag; tagContinues = byteAddress + 1 < info.start + info.size; } } int hexX = hexStartX_ + j * 3 * charWidth_; int hexLen = 2; int asciiX = asciiStartX_ + j * (charWidth_ + 2); char c; if (valid) { snprintf(temp, sizeof(temp), "%02X ", memory.bytes[j]); c = (char)memory.bytes[j]; if (memory.bytes[j] < 32 || memory.bytes[j] >= 128) c = '.'; } else { truncate_cpy(temp, "??"); c = '.'; } COLORREF hexBGCol = standardBG; COLORREF hexTextCol = 0x000000; COLORREF continueBGCol = standardBG; COLORREF asciiBGCol = standardBG; COLORREF asciiTextCol = 0x000000; int underline = -1; if (address + j >= selectRangeStart_ && address + j < selectRangeEnd_ && !searching_) { if (asciiSelected_) { hexBGCol = 0xC0C0C0; hexTextCol = 0x000000; asciiBGCol = hasFocus_ ? 0xFF9933 : 0xC0C0C0; asciiTextCol = hasFocus_ ? 0xFFFFFF : 0x000000; } else { hexBGCol = hasFocus_ ? 0xFF9933 : 0xC0C0C0; hexTextCol = hasFocus_ ? 0xFFFFFF : 0x000000; asciiBGCol = 0xC0C0C0; asciiTextCol = 0x000000; if (address + j == curAddress_) underline = selectedNibble_; } if (!tag.empty() && tagContinues) { continueBGCol = pickTagColor(tag); } } else if (!tag.empty()) { hexBGCol = pickTagColor(tag); continueBGCol = hexBGCol; asciiBGCol = pickTagColor(tag); hexLen = tagContinues ? 3 : 2; } setTextColors(hexTextCol, hexBGCol); if (underline >= 0) { SelectObject(hdc, underline == 0 ? (HGDIOBJ)underlineFont : (HGDIOBJ)font); TextOutA(hdc, hexX, rowY, &temp[0], 1); SelectObject(hdc, underline == 0 ? (HGDIOBJ)font : (HGDIOBJ)underlineFont); TextOutA(hdc, hexX + charWidth_, rowY, &temp[1], 1); SelectObject(hdc, (HGDIOBJ)font); // If the tag keeps going, draw the BG too. if (continueBGCol != standardBG) { setTextColors(0x000000, continueBGCol); TextOutA(hdc, hexX + charWidth_ * 2, rowY, &temp[2], 1); } } else { if (continueBGCol != hexBGCol) { TextOutA(hdc, hexX, rowY, temp, 2); setTextColors(0x000000, continueBGCol); TextOutA(hdc, hexX + charWidth_ * 2, rowY, &temp[2], 1); } else { TextOutA(hdc, hexX, rowY, temp, hexLen); } } setTextColors(asciiTextCol, asciiBGCol); TextOutA(hdc, asciiX, rowY, &c, 1); } } setTextColors(0x000000, standardBG); SelectObject(hdc,oldFont); SelectObject(hdc,oldPen); SelectObject(hdc,oldBrush); // copy bitmap to the actual hdc BitBlt(actualHdc, 0, 0, rect_.right, rect_.bottom, hdc, 0, 0, SRCCOPY); DeleteObject(hBM); DeleteDC(hdc); DeleteObject(standardPen); DeleteObject(standardBrush); EndPaint(wnd, &ps); } void CtrlMemView::onVScroll(WPARAM wParam, LPARAM lParam) { switch (wParam & 0xFFFF) { case SB_LINEDOWN: ScrollWindow(1, GotoModeFromModifiers(false)); break; case SB_LINEUP: ScrollWindow(-1, GotoModeFromModifiers(false)); break; case SB_PAGEDOWN: ScrollWindow(visibleRows_, GotoModeFromModifiers(false)); break; case SB_PAGEUP: ScrollWindow(-visibleRows_, GotoModeFromModifiers(false)); break; default: return; } } void CtrlMemView::onKeyDown(WPARAM wParam, LPARAM lParam) { if (KeyDownAsync(VK_CONTROL)) { switch (tolower(wParam & 0xFFFF)) { case 'g': { u32 addr; if (executeExpressionWindow(wnd, debugger_, addr) == false) return; gotoAddr(addr); return; } break; case 'f': case 's': search(false); return; case 'c': search(true); return; } } switch (wParam & 0xFFFF) { case VK_DOWN: ScrollCursor(rowSize_, GotoModeFromModifiers(false)); break; case VK_UP: ScrollCursor(-rowSize_, GotoModeFromModifiers(false)); break; case VK_LEFT: ScrollCursor(-1, GotoModeFromModifiers(false)); break; case VK_RIGHT: ScrollCursor(1, GotoModeFromModifiers(false)); break; case VK_NEXT: ScrollWindow(visibleRows_, GotoModeFromModifiers(false)); break; case VK_PRIOR: ScrollWindow(-visibleRows_, GotoModeFromModifiers(false)); break; case VK_TAB: SendMessage(GetParent(wnd),WM_DEB_TABPRESSED,0,0); break; default: return; } } void CtrlMemView::onChar(WPARAM wParam, LPARAM lParam) { auto memLock = Memory::Lock(); if (!PSP_IsInited()) return; if (KeyDownAsync(VK_CONTROL) || wParam == VK_TAB) return; if (!Memory::IsValidAddress(curAddress_)) { ScrollCursor(1, GotoMode::RESET); return; } bool active = Core_IsActive(); if (active) Core_Break("memory.access", curAddress_); if (asciiSelected_) { Memory::WriteUnchecked_U8((u8)wParam, curAddress_); ScrollCursor(1, GotoMode::RESET); } else { wParam = tolower(wParam); int inputValue = -1; if (wParam >= '0' && wParam <= '9') inputValue = wParam - '0'; if (wParam >= 'a' && wParam <= 'f') inputValue = wParam -'a' + 10; if (inputValue >= 0) { int shiftAmount = (1 - selectedNibble_) * 4; u8 oldValue = Memory::ReadUnchecked_U8(curAddress_); oldValue &= ~(0xF << shiftAmount); u8 newValue = oldValue | (inputValue << shiftAmount); Memory::WriteUnchecked_U8(newValue, curAddress_); ScrollCursor(1, GotoMode::RESET); } } Reporting::NotifyDebugger(); if (active) Core_Resume(); } void CtrlMemView::redraw() { GetClientRect(wnd, &rect_); visibleRows_ = rect_.bottom / rowHeight_; if (displayOffsetScale_) { // visibleRows_ is calculated based on the size of the control, but X rows have already been used for the offsets and are no longer usable visibleRows_ -= offsetSpace; } if (!redrawScheduled_) { SetTimer(wnd, IDT_REDRAW_DELAYED, REDRAW_DELAY, nullptr); redrawScheduled_ = true; } } CtrlMemView::GotoMode CtrlMemView::GotoModeFromModifiers(bool isRightClick) { GotoMode mode = GotoMode::RESET; if (isRightClick) { mode = GotoMode::RESET_IF_OUTSIDE; } else if (KeyDownAsync(VK_SHIFT)) { if (KeyDownAsync(VK_CONTROL)) mode = GotoMode::EXTEND; else mode = GotoMode::FROM_CUR; } return mode; } void CtrlMemView::onMouseDown(WPARAM wParam, LPARAM lParam, int button) { if (Achievements::HardcoreModeActive()) return; int x = LOWORD(lParam); int y = HIWORD(lParam); GotoPoint(x, y, GotoModeFromModifiers(button == 2)); } void CtrlMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { if (Achievements::HardcoreModeActive()) return; if (button == 2) { int32_t selectedSize = selectRangeEnd_ - selectRangeStart_; bool enable16 = !asciiSelected_ && (selectedSize == 1 || (selectedSize & 1) == 0); bool enable32 = !asciiSelected_ && (selectedSize == 1 || (selectedSize & 3) == 0); HMENU menu = GetContextMenu(ContextMenuID::MEMVIEW); EnableMenuItem(menu, ID_MEMVIEW_COPYVALUE_16, enable16 ? MF_ENABLED : MF_GRAYED); EnableMenuItem(menu, ID_MEMVIEW_COPYVALUE_32, enable32 ? MF_ENABLED : MF_GRAYED); EnableMenuItem(menu, ID_MEMVIEW_COPYFLOAT_32, enable32 ? MF_ENABLED : MF_GRAYED); switch (TriggerContextMenu(ContextMenuID::MEMVIEW, wnd, ContextPoint::FromEvent(lParam))) { case ID_MEMVIEW_DUMP: { DumpMemoryWindow dump(wnd, debugger_); dump.exec(); break; } case ID_MEMVIEW_COPYVALUE_8: { auto memLock = Memory::Lock(); size_t tempSize = 3 * selectedSize + 1; char *temp = new char[tempSize]; memset(temp, 0, tempSize); // it's admittedly not really useful like this if (asciiSelected_) { for (uint32_t p = selectRangeStart_; p != selectRangeEnd_; ++p) { uint8_t c = Memory::IsValidAddress(p) ? Memory::ReadUnchecked_U8(p) : '.'; if (c < 32 || c >= 128) c = '.'; temp[p - selectRangeStart_] = c; } } else { char *pos = temp; for (uint32_t p = selectRangeStart_; p != selectRangeEnd_; ++p) { uint8_t c = Memory::IsValidAddress(p) ? Memory::ReadUnchecked_U8(p) : 0xFF; pos += snprintf(pos, tempSize - (pos - temp + 1), "%02X ", c); } // Clear the last space. if (pos > temp) *(pos - 1) = '\0'; } W32Util::CopyTextToClipboard(wnd, temp); delete[] temp; } break; case ID_MEMVIEW_COPYVALUE_16: { auto memLock = Memory::Lock(); size_t tempSize = 5 * ((selectedSize + 1) / 2) + 1; char *temp = new char[tempSize]; memset(temp, 0, tempSize); char *pos = temp; for (uint32_t p = selectRangeStart_; p < selectRangeEnd_; p += 2) { uint16_t c = Memory::IsValidRange(p, 2) ? Memory::ReadUnchecked_U16(p) : 0xFFFF; pos += snprintf(pos, tempSize - (pos - temp + 1), "%04X ", c); } // Clear the last space. if (pos > temp) *(pos - 1) = '\0'; W32Util::CopyTextToClipboard(wnd, temp); delete[] temp; } break; case ID_MEMVIEW_COPYVALUE_32: { auto memLock = Memory::Lock(); size_t tempSize = 9 * ((selectedSize + 3) / 4) + 1; char *temp = new char[tempSize]; memset(temp, 0, tempSize); char *pos = temp; for (uint32_t p = selectRangeStart_; p < selectRangeEnd_; p += 4) { uint32_t c = Memory::IsValidRange(p, 4) ? Memory::ReadUnchecked_U32(p) : 0xFFFFFFFF; pos += snprintf(pos, tempSize - (pos - temp + 1), "%08X ", c); } // Clear the last space. if (pos > temp) *(pos - 1) = '\0'; W32Util::CopyTextToClipboard(wnd, temp); delete[] temp; } break; case ID_MEMVIEW_COPYFLOAT_32: { auto memLock = Memory::Lock(); std::ostringstream stream; stream << (Memory::IsValidAddress(curAddress_) ? Memory::Read_Float(curAddress_) : NAN); auto temp_string = stream.str(); W32Util::CopyTextToClipboard(wnd, temp_string.c_str()); } break; case ID_MEMVIEW_EXTENTBEGIN: { std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1); uint32_t addr = curAddress_; for (MemBlockInfo info : memRangeInfo) { addr = info.start; } gotoAddr(addr); break; } case ID_MEMVIEW_EXTENTEND: { std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1); uint32_t addr = curAddress_; for (MemBlockInfo info : memRangeInfo) { addr = info.start + info.size - 1; } gotoAddr(addr); break; } case ID_MEMVIEW_COPYADDRESS: { char temp[24]; snprintf(temp, sizeof(temp), "0x%08X", curAddress_); W32Util::CopyTextToClipboard(wnd, temp); } break; case ID_MEMVIEW_GOTOINDISASM: if (disasmWindow) { disasmWindow->Goto(curAddress_); disasmWindow->Show(true); } break; } return; } int x = LOWORD(lParam); int y = HIWORD(lParam); ReleaseCapture(); GotoPoint(x, y, GotoModeFromModifiers(button == 2)); } void CtrlMemView::onMouseMove(WPARAM wParam, LPARAM lParam, int button) { if (Achievements::HardcoreModeActive()) return; int x = LOWORD(lParam); int y = HIWORD(lParam); if (button & 1) { GotoPoint(x, y, GotoModeFromModifiers(button == 2)); } } void CtrlMemView::updateStatusBarText() { std::vector memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1); char text[512]; snprintf(text, sizeof(text), "%08X", curAddress_); // There should only be one. for (MemBlockInfo info : memRangeInfo) { snprintf(text, sizeof(text), "%08X - %s %08X-%08X (at PC %08X / %lld ticks)", curAddress_, info.tag.c_str(), info.start, info.start + info.size, info.pc, info.ticks); } SendMessage(GetParent(wnd), WM_DEB_SETSTATUSBARTEXT, 0, (LPARAM)text); } void CtrlMemView::UpdateSelectRange(uint32_t target, GotoMode mode) { if (mode == GotoMode::FROM_CUR && lastSelectReset_ == 0) { lastSelectReset_ = curAddress_; } switch (mode) { case GotoMode::RESET: selectRangeStart_ = target; selectRangeEnd_ = target + 1; lastSelectReset_ = target; break; case GotoMode::RESET_IF_OUTSIDE: if (target < selectRangeStart_ || target >= selectRangeEnd_) { selectRangeStart_ = target; selectRangeEnd_ = target + 1; lastSelectReset_ = target; } break; case GotoMode::FROM_CUR: selectRangeStart_ = lastSelectReset_ > target ? target : lastSelectReset_; selectRangeEnd_ = selectRangeStart_ == lastSelectReset_ ? target + 1 : lastSelectReset_ + 1; break; case GotoMode::EXTEND: if (target < selectRangeStart_) selectRangeStart_ = target; if (target > selectRangeEnd_) selectRangeEnd_ = target; break; } curAddress_ = target; } void CtrlMemView::GotoPoint(int x, int y, GotoMode mode) { int line = y / rowHeight_; int lineAddress = windowStart_ + line * rowSize_; if (displayOffsetScale_) { // ignore clicks on the offset space if (line < offsetSpace) { updateStatusBarText(); redraw(); return; } // since each row has been written X rows down from where the window expected it to be written the target of the clicks must be adjusted lineAddress -= rowSize_ * offsetSpace; } uint32_t target = curAddress_; uint32_t targetNibble = selectedNibble_; bool targetAscii = asciiSelected_; if (x >= asciiStartX_) { int col = (x - asciiStartX_) / (charWidth_ + 2); if (col >= rowSize_) return; targetAscii = true; target = lineAddress + col; targetNibble = 0; } else if (x >= hexStartX_) { int col = (x - hexStartX_) / charWidth_; if ((col/3) >= rowSize_) return; switch (col % 3) { case 0: targetNibble = 0; break; case 1: targetNibble = 1; break; case 2: return; // don't change position when clicking on the space } targetAscii = false; target = lineAddress + col / 3; } if (target != curAddress_ || targetNibble != selectedNibble_ || targetAscii != asciiSelected_) { selectedNibble_ = targetNibble; asciiSelected_ = targetAscii; UpdateSelectRange(target, mode); updateStatusBarText(); redraw(); } } void CtrlMemView::gotoAddr(unsigned int addr) { int lines = rect_.bottom / rowHeight_; u32 windowEnd = windowStart_ + lines * rowSize_; curAddress_ = addr; lastSelectReset_ = curAddress_; selectRangeStart_ = curAddress_; selectRangeEnd_ = curAddress_ + 1; selectedNibble_ = 0; if (curAddress_ < windowStart_ || curAddress_ >= windowEnd) { windowStart_ = curAddress_ & ~15; } updateStatusBarText(); redraw(); } void CtrlMemView::ScrollWindow(int lines, GotoMode mode) { windowStart_ += lines * rowSize_; UpdateSelectRange(curAddress_ + lines * rowSize_, mode); updateStatusBarText(); redraw(); } void CtrlMemView::ScrollCursor(int bytes, GotoMode mode) { if (!asciiSelected_ && bytes == 1) { if (selectedNibble_ == 0) { selectedNibble_ = 1; bytes = 0; } else { selectedNibble_ = 0; } } else if (!asciiSelected_ && bytes == -1) { if (selectedNibble_ == 0) { selectedNibble_ = 1; } else { selectedNibble_ = 0; bytes = 0; } } UpdateSelectRange(curAddress_ + bytes, mode); u32 windowEnd = windowStart_ + visibleRows_ * rowSize_; if (curAddress_ < windowStart_) { windowStart_ = curAddress_ & ~15; } else if (curAddress_ >= windowEnd) { windowStart_ = (curAddress_ - (visibleRows_ - 1) * rowSize_) & ~15; } updateStatusBarText(); redraw(); } bool CtrlMemView::ParseSearchString(const std::string &query, bool asHex, std::vector &data) { data.clear(); if (!asHex) { for (size_t i = 0; i < query.length(); i++) { data.push_back(query[i]); } return true; } for (size_t index = 0; index < query.size(); ) { if (isspace(query[index])) { index++; continue; } u8 value = 0; for (int i = 0; i < 2 && index < query.size(); i++) { char c = tolower(query[index++]); if (c >= 'a' && c <= 'f') { value |= (c - 'a' + 10) << (1 - i) * 4; } else if (c >= '0' && c <= '9') { value |= (c - '0') << (1 - i) * 4; } else { return false; } } data.push_back(value); } return true; } std::vector CtrlMemView::searchString(const std::string &searchQuery) { std::vector searchResAddrs; auto memLock = Memory::Lock(); if (!PSP_IsInited()) return searchResAddrs; std::vector searchData; if (!ParseSearchString(searchQuery, false, searchData)) return searchResAddrs; if (searchData.empty()) return searchResAddrs; std::vector> memoryAreas; memoryAreas.emplace_back(PSP_GetScratchpadMemoryBase(), PSP_GetScratchpadMemoryEnd()); // Ignore the video memory mirrors. memoryAreas.emplace_back(PSP_GetVidMemBase(), 0x04200000); memoryAreas.emplace_back(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd()); for (const auto &area : memoryAreas) { const u32 segmentStart = area.first; const u32 segmentEnd = area.second - (u32)searchData.size(); for (u32 pos = segmentStart; pos < segmentEnd; pos++) { if ((pos % 256) == 0 && KeyDownAsync(VK_ESCAPE)) { return searchResAddrs; } const u8 *ptr = Memory::GetPointerUnchecked(pos); if (memcmp(ptr, searchData.data(), searchData.size()) == 0) { searchResAddrs.push_back(pos); } } } return searchResAddrs; }; void CtrlMemView::search(bool continueSearch) { auto memLock = Memory::Lock(); if (!PSP_IsInited()) return; u32 searchAddress = 0; u32 segmentStart = 0; u32 segmentEnd = 0; if (continueSearch == false || searchQuery_.empty()) { if (InputBox_GetString(GetModuleHandle(NULL), wnd, L"Search for", searchQuery_, searchQuery_) == false) { SetFocus(wnd); return; } SetFocus(wnd); searchAddress = curAddress_ + 1; } else { searchAddress = matchAddress_ + 1; } std::vector searchData; if (!ParseSearchString(searchQuery_, !asciiSelected_, searchData)) { MessageBox(wnd, L"Invalid search text.", L"Error", MB_OK); return; } std::vector> memoryAreas; // Ignore the video memory mirrors. memoryAreas.emplace_back(PSP_GetVidMemBase(), 0x04200000); memoryAreas.emplace_back(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd()); memoryAreas.emplace_back(PSP_GetScratchpadMemoryBase(), PSP_GetScratchpadMemoryEnd()); searching_ = true; redraw(); // so the cursor is disabled for (size_t i = 0; i < memoryAreas.size(); i++) { segmentStart = memoryAreas[i].first; segmentEnd = memoryAreas[i].second; // better safe than sorry, I guess if (!Memory::IsValidAddress(segmentStart)) continue; const u8 *dataPointer = Memory::GetPointerUnchecked(segmentStart); if (searchAddress < segmentStart) searchAddress = segmentStart; if (searchAddress >= segmentEnd) continue; int index = searchAddress-segmentStart; int endIndex = segmentEnd-segmentStart - (int)searchData.size(); while (index < endIndex) { // cancel search if ((index % 256) == 0 && KeyDownAsync(VK_ESCAPE)) { searching_ = false; return; } if (memcmp(&dataPointer[index], searchData.data(), searchData.size()) == 0) { matchAddress_ = index + segmentStart; searching_ = false; gotoAddr(matchAddress_); return; } index++; } } MessageBox(wnd, L"Not found", L"Search", MB_OK); searching_ = false; redraw(); } void CtrlMemView::drawOffsetScale(HDC hdc) { int currentX = addressStartX_; SetTextColor(hdc, 0x600000); TextOutA(hdc, currentX, offsetPositionY_, "Offset", 6); // the start offset, the size of the hex addresses and one space currentX = addressStartX_ + ((8 + 1) * charWidth_); char temp[64]; for (int i = 0; i < 16; i++) { snprintf(temp, sizeof(temp), "%02X", i); TextOutA(hdc, currentX, offsetPositionY_, temp, 2); currentX += 3 * charWidth_; // hex and space } } void CtrlMemView::toggleOffsetScale(CommonToggles toggle) { if (toggle == On) displayOffsetScale_ = true; else if (toggle == Off) displayOffsetScale_ = false; updateStatusBarText(); redraw(); } void CtrlMemView::setHighlightType(MemBlockFlags flags) { if (highlightFlags_ != flags) { highlightFlags_ = flags; updateStatusBarText(); redraw(); } } uint32_t CtrlMemView::pickTagColor(const std::string &tag) { int colors[6] = { 0xe0FFFF, 0xFFE0E0, 0xE8E8FF, 0xFFE0FF, 0xE0FFE0, 0xFFFFE0 }; int which = XXH3_64bits(tag.c_str(), tag.length()) % ARRAY_SIZE(colors); return colors[which]; }