mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-12-04 12:05:23 +00:00
1002 lines
27 KiB
C++
1002 lines
27 KiB
C++
#include <cctype>
|
|
#include <tchar.h>
|
|
#include <cmath>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
|
|
#include "ext/xxhash.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Core/Config.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<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::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<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(const std::string &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(const std::string &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(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];
|
|
}
|