mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-08-31 09:45:24 +02:00
Reported hang: the CPU thread held g_frameMutex (NativeFrame) and blocked on g_shutdownLock inside a queued memory.read, while the GUI thread held g_shutdownLock (CtrlMemView::onPaint) and blocked on g_frameMutex. Textbook ABBA. The CPU thread's order is structural - NativeFrame wraps everything below it in g_frameMutex, and both Core_ProcessCPUQueue() and runImDebugger() -> DisassembleRange() lock memory from under there - so the GUI side is the one that has to match. Swaps the three handlers that had it backwards (CtrlMemView::onPaint, CtrlDisAsmView::onPaint, CtrlStackTraceView:: loadStackTrace) to take g_frameMutex first. They already took both locks, so this is ordering only, and g_shutdownLock is recursive so nesting is fine. Also drops the Memory::MemoryInitedLock from the WebSocket LockMemory(), which is what made the CPU thread want that lock in the first place. It was guarding against another thread tearing down the memory system, but that doesn't happen: Memory::Shutdown() is only reached via CPU_Shutdown() <- PSP_Shutdown(), whose callers all run on the CPU thread, and Memory::Reinit() runs from Memory::DoState() on savestate load, likewise. WebSocket.cpp additionally holds lifecycleLock across the whole handler and takes it on STOPPING. Note this second part isn't sufficient on its own - ImMemView's copy-disassembly path also locks memory from inside the frame span - which is why the ordering fix is the real one. Not removing Memory::Lock() from the Win32 paint handlers: teardown isn't fully inside the g_frameMutex span yet. EmuScreen::render()'s PSP_Shutdown() is, but the ones in EmuScreen::sendMessage() (game reset, loading a new game) run from g_screenManager->sendMessage(), above where NativeFrame takes the guard. Closing that is the prerequisite, and is left for later. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
1007 lines
28 KiB
C++
1007 lines
28 KiB
C++
#include <cctype>
|
|
#include <cmath>
|
|
#include <sstream>
|
|
|
|
#include "ext/xxhash.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/Core.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;
|
|
|
|
// Reading live memory/tracking state here on the GUI thread would otherwise race with the CPU
|
|
// thread - hold g_frameMutex for the duration of the read, which NativeFrame() also holds
|
|
// while it's actually touching that state. See g_frameMutex in Core.h.
|
|
//
|
|
// g_frameMutex first, then Memory::Lock() - never the other way around. The CPU thread has no
|
|
// choice but that order (NativeFrame wraps everything below it), so this side has to match.
|
|
std::lock_guard<std::mutex> frameGuard(g_frameMutex);
|
|
Memory::MemoryInitedLock 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<MemBlockInfo> 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::ClampValidSizeAt(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) {
|
|
Memory::MemoryInitedLock memLock = Memory::Lock();
|
|
if (!PSP_IsInited())
|
|
return;
|
|
|
|
if (KeyDownAsync(VK_CONTROL) || wParam == VK_TAB)
|
|
return;
|
|
|
|
if (!Memory::IsValidAddress(curAddress_)) {
|
|
ScrollCursor(1, GotoMode::RESET);
|
|
return;
|
|
}
|
|
|
|
// Route the actual memory write to the CPU thread instead of poking at it directly from this
|
|
// GUI thread - see Core_RunOnCPUThread() in Core.h. No need to force the core to pause first.
|
|
if (asciiSelected_) {
|
|
Core_RunOnCPUThread([&] { 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;
|
|
|
|
Core_RunOnCPUThread([&] {
|
|
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();
|
|
}
|
|
|
|
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::IsValid4AlignedAddress(curAddress_) ? Memory::ReadUnchecked_Float(curAddress_) : NAN);
|
|
auto temp_string = stream.str();
|
|
W32Util::CopyTextToClipboard(wnd, temp_string);
|
|
}
|
|
break;
|
|
|
|
case ID_MEMVIEW_EXTENTBEGIN:
|
|
{
|
|
std::vector<MemBlockInfo> 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<MemBlockInfo> 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<MemBlockInfo> 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(std::string_view query, bool asHex, std::vector<uint8_t> &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<u32> CtrlMemView::searchString(std::string_view searchQuery) {
|
|
std::vector<u32> searchResAddrs;
|
|
|
|
auto memLock = Memory::Lock();
|
|
if (!PSP_IsInited())
|
|
return searchResAddrs;
|
|
|
|
std::vector<u8> searchData;
|
|
if (!ParseSearchString(searchQuery, false, searchData))
|
|
return searchResAddrs;
|
|
|
|
if (searchData.empty())
|
|
return searchResAddrs;
|
|
|
|
std::vector<std::pair<u32, u32>> 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<u8> searchData;
|
|
if (!ParseSearchString(searchQuery_, !asciiSelected_, searchData)) {
|
|
MessageBox(wnd, L"Invalid search text.", L"Error", MB_OK);
|
|
return;
|
|
}
|
|
|
|
std::vector<std::pair<u32, u32>> 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(std::string_view tag) {
|
|
int colors[6] = { 0xe0FFFF, 0xFFE0E0, 0xE8E8FF, 0xFFE0FF, 0xE0FFE0, 0xFFFFE0 };
|
|
int which = XXH3_64bits(tag.data(), tag.length()) % ARRAY_SIZE(colors);
|
|
return colors[which];
|
|
}
|