#include "Windows/Debugger/Debugger_Lists.h" #include "Common/CommonWindows.h" #include <windowsx.h> #include <commctrl.h> #include "Windows/Debugger/BreakpointWindow.h" #include "Windows/Debugger/CtrlDisAsmView.h" #include "Windows/Debugger/DebuggerShared.h" #include "Windows/Debugger/WatchItemWindow.h" #include "Windows/W32Util/ContextMenu.h" #include "Windows/MainWindow.h" #include "Windows/resource.h" #include "Windows/main.h" #include "Common/Data/Encoding/Utf8.h" #include "Core/HLE/sceKernelThread.h" enum { TL_NAME, TL_PROGRAMCOUNTER, TL_ENTRYPOINT, TL_PRIORITY, TL_STATE, TL_WAITTYPE, TL_COLUMNCOUNT }; enum { BPL_ENABLED, BPL_TYPE, BPL_OFFSET, BPL_SIZELABEL, BPL_OPCODE, BPL_CONDITION, BPL_HITS, BPL_COLUMNCOUNT }; enum { SF_ENTRY, SF_ENTRYNAME, SF_CURPC, SF_CUROPCODE, SF_CURSP, SF_FRAMESIZE, SF_COLUMNCOUNT }; enum { ML_NAME, ML_ADDRESS, ML_SIZE, ML_ACTIVE, ML_COLUMNCOUNT }; enum { WL_NAME, WL_EXPRESSION, WL_VALUE, WL_COLUMNCOUNT }; GenericListViewColumn threadColumns[TL_COLUMNCOUNT] = { { L"Name", 0.20f }, { L"PC", 0.15f }, { L"Entry Point", 0.15f }, { L"Priority", 0.15f }, { L"State", 0.15f }, { L"Wait type", 0.20f } }; GenericListViewDef threadListDef = { threadColumns, ARRAY_SIZE(threadColumns), NULL, false }; GenericListViewColumn breakpointColumns[BPL_COLUMNCOUNT] = { { L"", 0.03f }, // enabled { L"Type", 0.15f }, { L"Offset", 0.12f }, { L"Size/Label", 0.20f }, { L"Opcode", 0.28f }, { L"Condition", 0.17f }, { L"Hits", 0.05f }, }; GenericListViewDef breakpointListDef = { breakpointColumns, ARRAY_SIZE(breakpointColumns), NULL, true }; GenericListViewColumn stackTraceColumns[SF_COLUMNCOUNT] = { { L"Entry", 0.12f }, { L"Name", 0.24f }, { L"PC", 0.12f }, { L"Opcode", 0.28f }, { L"SP", 0.12f }, { L"Frame Size", 0.12f } }; GenericListViewDef stackTraceListDef = { stackTraceColumns, ARRAY_SIZE(stackTraceColumns), NULL, false }; GenericListViewColumn moduleListColumns[ML_COLUMNCOUNT] = { { L"Name", 0.25f }, { L"Address", 0.25f }, { L"Size", 0.25f }, { L"Active", 0.25f }, }; GenericListViewDef moduleListDef = { moduleListColumns, ARRAY_SIZE(moduleListColumns), NULL, false }; GenericListViewColumn watchListColumns[WL_COLUMNCOUNT] = { { L"Name", 0.25f }, { L"Expression", 0.5f }, { L"Value", 0.25f }, }; GenericListViewDef watchListDef = { watchListColumns, ARRAY_SIZE(watchListColumns), nullptr, false, }; // // CtrlThreadList // CtrlThreadList::CtrlThreadList(HWND hwnd): GenericListControl(hwnd,threadListDef) { Update(); } bool CtrlThreadList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue) { switch (msg) { case WM_KEYDOWN: if (wParam == VK_TAB) { SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0); returnValue = 0; return true; } break; case WM_GETDLGCODE: if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN) { if (wParam == VK_TAB) { returnValue = DLGC_WANTMESSAGE; return true; } } break; } return false; } void CtrlThreadList::showMenu(int itemIndex, const POINT &pt) { auto threadInfo = threads[itemIndex]; // Can't do it, sorry. Needs to not be running. if (Core_IsActive()) return; HMENU subMenu = GetContextMenu(ContextMenuID::THREADLIST); switch (threadInfo.status) { case THREADSTATUS_DEAD: case THREADSTATUS_DORMANT: case THREADSTATUS_RUNNING: EnableMenuItem(subMenu, ID_DISASM_THREAD_FORCERUN, MF_BYCOMMAND | MF_DISABLED); EnableMenuItem(subMenu, ID_DISASM_THREAD_KILL, MF_BYCOMMAND | MF_DISABLED); break; case THREADSTATUS_READY: EnableMenuItem(subMenu, ID_DISASM_THREAD_FORCERUN, MF_BYCOMMAND | MF_DISABLED); EnableMenuItem(subMenu, ID_DISASM_THREAD_KILL, MF_BYCOMMAND | MF_ENABLED); break; case THREADSTATUS_SUSPEND: case THREADSTATUS_WAIT: case THREADSTATUS_WAITSUSPEND: default: EnableMenuItem(subMenu, ID_DISASM_THREAD_FORCERUN, MF_BYCOMMAND | MF_ENABLED); EnableMenuItem(subMenu, ID_DISASM_THREAD_KILL, MF_BYCOMMAND | MF_ENABLED); break; } switch (TriggerContextMenu(ContextMenuID::THREADLIST, GetHandle(), ContextPoint::FromClient(pt))) { case ID_DISASM_THREAD_FORCERUN: __KernelResumeThreadFromWait(threadInfo.id, 0); reloadThreads(); break; case ID_DISASM_THREAD_KILL: sceKernelTerminateThread(threadInfo.id); reloadThreads(); break; } } void CtrlThreadList::GetColumnText(wchar_t* dest, size_t destSize, int row, int col) { if (row < 0 || row >= (int)threads.size()) { return; } switch (col) { case TL_NAME: wcscpy(dest, ConvertUTF8ToWString(threads[row].name).c_str()); break; case TL_PROGRAMCOUNTER: switch (threads[row].status) { case THREADSTATUS_DORMANT: case THREADSTATUS_DEAD: wcscpy(dest, L"N/A"); break; default: wsprintf(dest, L"0x%08X",threads[row].curPC); break; }; break; case TL_ENTRYPOINT: wsprintf(dest,L"0x%08X",threads[row].entrypoint); break; case TL_PRIORITY: wsprintf(dest,L"%d",threads[row].priority); break; case TL_STATE: switch (threads[row].status) { case THREADSTATUS_RUNNING: wcscpy(dest,L"Running"); break; case THREADSTATUS_READY: wcscpy(dest,L"Ready"); break; case THREADSTATUS_WAIT: wcscpy(dest,L"Waiting"); break; case THREADSTATUS_SUSPEND: wcscpy(dest,L"Suspended"); break; case THREADSTATUS_DORMANT: wcscpy(dest,L"Dormant"); break; case THREADSTATUS_DEAD: wcscpy(dest,L"Dead"); break; case THREADSTATUS_WAITSUSPEND: wcscpy(dest,L"Waiting/Suspended"); break; default: wcscpy(dest,L"Invalid"); break; } break; case TL_WAITTYPE: wcscpy(dest, ConvertUTF8ToWString(getWaitTypeName(threads[row].waitType)).c_str()); break; } } void CtrlThreadList::OnDoubleClick(int itemIndex, int column) { u32 address; switch (threads[itemIndex].status) { case THREADSTATUS_DORMANT: case THREADSTATUS_DEAD: address = threads[itemIndex].entrypoint; break; default: address = threads[itemIndex].curPC; break; } SendMessage(GetParent(GetHandle()),WM_DEB_GOTOWPARAM,address,0); } void CtrlThreadList::OnRightClick(int itemIndex, int column, const POINT& point) { showMenu(itemIndex,point); } void CtrlThreadList::reloadThreads() { threads = GetThreadsInfo(); Update(); } const char* CtrlThreadList::getCurrentThreadName() { for (size_t i = 0; i < threads.size(); i++) { if (threads[i].isCurrent) return threads[i].name; } return "N/A"; } // // CtrlBreakpointList // CtrlBreakpointList::CtrlBreakpointList(HWND hwnd, MIPSDebugInterface* cpu, CtrlDisAsmView* disasm) : GenericListControl(hwnd,breakpointListDef),cpu(cpu),disasm(disasm) { SetSendInvalidRows(true); Update(); } bool CtrlBreakpointList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue) { switch(msg) { case WM_KEYDOWN: returnValue = 0; if(wParam == VK_RETURN) { int index = GetSelectedIndex(); editBreakpoint(index); return true; } else if (wParam == VK_DELETE) { int index = GetSelectedIndex(); removeBreakpoint(index); return true; } else if (wParam == VK_TAB) { SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0); return true; } else if (wParam == VK_SPACE) { int index = GetSelectedIndex(); toggleEnabled(index); return true; } break; case WM_GETDLGCODE: if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN) { if (wParam == VK_TAB || wParam == VK_RETURN) { returnValue = DLGC_WANTMESSAGE; return true; } } break; } return false; } void CtrlBreakpointList::reloadBreakpoints() { // Update the items we're displaying from the debugger. displayedBreakPoints_ = g_breakpoints.GetBreakpoints(); displayedMemChecks_= g_breakpoints.GetMemChecks(); for (int i = 0; i < GetRowCount(); i++) { bool isMemory; int index = getBreakpointIndex(i, isMemory); if (index < 0) continue; if (isMemory) SetCheckState(i, displayedMemChecks_[index].IsEnabled()); else SetCheckState(i, displayedBreakPoints_[index].IsEnabled()); } Update(); } void CtrlBreakpointList::editBreakpoint(int itemIndex) { bool isMemory; int index = getBreakpointIndex(itemIndex, isMemory); if (index == -1) return; BreakpointWindow win(GetHandle(),cpu); if (isMemory) { auto mem = displayedMemChecks_[index]; win.loadFromMemcheck(mem); if (win.exec()) { g_breakpoints.RemoveMemCheck(mem.start,mem.end); win.addBreakpoint(); } } else { auto bp = displayedBreakPoints_[index]; win.loadFromBreakpoint(bp); if (win.exec()) { g_breakpoints.RemoveBreakPoint(bp.addr); win.addBreakpoint(); } } } void CtrlBreakpointList::toggleEnabled(int itemIndex) { bool isMemory; int index = getBreakpointIndex(itemIndex, isMemory); if (index == -1) return; if (isMemory) { MemCheck mcPrev = displayedMemChecks_[index]; g_breakpoints.ChangeMemCheck(mcPrev.start, mcPrev.end, mcPrev.cond, BreakAction(mcPrev.result ^ BREAK_ACTION_PAUSE)); } else { BreakPoint bpPrev = displayedBreakPoints_[index]; g_breakpoints.ChangeBreakPoint(bpPrev.addr, BreakAction(bpPrev.result ^ BREAK_ACTION_PAUSE)); } } void CtrlBreakpointList::gotoBreakpointAddress(int itemIndex) { bool isMemory; int index = getBreakpointIndex(itemIndex, isMemory); if (index == -1) return; if (isMemory) { u32 address = displayedMemChecks_[index].start; MainWindow::CreateMemoryWindow(); if (memoryWindow) memoryWindow->Goto(address); } else { u32 address = displayedBreakPoints_[index].addr; MainWindow::CreateDisasmWindow(); if (disasmWindow) disasmWindow->Goto(address); } } void CtrlBreakpointList::removeBreakpoint(int itemIndex) { bool isMemory; int index = getBreakpointIndex(itemIndex,isMemory); if (index == -1) return; if (isMemory) { auto mc = displayedMemChecks_[index]; g_breakpoints.RemoveMemCheck(mc.start, mc.end); } else { u32 address = displayedBreakPoints_[index].addr; g_breakpoints.RemoveBreakPoint(address); } } int CtrlBreakpointList::getTotalBreakpointCount() { int count = (int)displayedMemChecks_.size(); for (auto bp : displayedBreakPoints_) { if (!bp.temporary) ++count; } return count; } int CtrlBreakpointList::getBreakpointIndex(int itemIndex, bool& isMemory) { // memory breakpoints first if (itemIndex < (int)displayedMemChecks_.size()) { isMemory = true; return itemIndex; } itemIndex -= (int)displayedMemChecks_.size(); size_t i = 0; while (i < displayedBreakPoints_.size()) { if (displayedBreakPoints_[i].temporary) { i++; continue; } // the index is 0 when there are no more breakpoints to skip if (itemIndex == 0) { isMemory = false; return (int)i; } i++; itemIndex--; } return -1; } void CtrlBreakpointList::GetColumnText(wchar_t* dest, size_t destSize, int row, int col) { if (!PSP_IsInited()) { return; } bool isMemory; int index = getBreakpointIndex(row,isMemory); if (index == -1) return; switch (col) { case BPL_TYPE: { if (isMemory) { switch ((int)displayedMemChecks_[index].cond) { case MEMCHECK_READ: wcscpy(dest,L"Read"); break; case MEMCHECK_WRITE: wcscpy(dest,L"Write"); break; case MEMCHECK_READWRITE: wcscpy(dest,L"Read/Write"); break; case MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE: wcscpy(dest,L"Write Change"); break; case MEMCHECK_READWRITE | MEMCHECK_WRITE_ONCHANGE: wcscpy(dest,L"Read/Write Change"); break; } } else { wcscpy(dest, L"Execute"); } } break; case BPL_OFFSET: { if (isMemory) { wsprintf(dest,L"0x%08X",displayedMemChecks_[index].start); } else { wsprintf(dest,L"0x%08X",displayedBreakPoints_[index].addr); } } break; case BPL_SIZELABEL: { if (isMemory) { auto mc = displayedMemChecks_[index]; if (mc.end == 0) wsprintf(dest,L"0x%08X",1); else wsprintf(dest,L"0x%08X",mc.end-mc.start); } else { const std::string sym = g_symbolMap->GetLabelString(displayedBreakPoints_[index].addr); if (!sym.empty()) { ConvertUTF8ToWString(dest, destSize, sym); } else { wcscpy(dest,L"-"); } } } break; case BPL_OPCODE: { if (isMemory) { wcscpy(dest,L"-"); } else { char temp[256]; disasm->getOpcodeText(displayedBreakPoints_[index].addr, temp, sizeof(temp)); ConvertUTF8ToWString(dest, destSize, temp); } } break; case BPL_CONDITION: { if (isMemory || displayedBreakPoints_[index].hasCond == false) { wcscpy(dest,L"-"); } else { std::wstring s = ConvertUTF8ToWString(displayedBreakPoints_[index].cond.expressionString); wcscpy(dest,s.c_str()); } } break; case BPL_HITS: { if (isMemory) { wsprintf(dest,L"%d",displayedMemChecks_[index].numHits); } else { wsprintf(dest,L"-"); } } break; case BPL_ENABLED: { wsprintf(dest,L"\xFFFE"); } break; } } void CtrlBreakpointList::OnDoubleClick(int itemIndex, int column) { gotoBreakpointAddress(itemIndex); } void CtrlBreakpointList::OnRightClick(int itemIndex, int column, const POINT& point) { showBreakpointMenu(itemIndex,point); } void CtrlBreakpointList::OnToggle(int item, bool newValue) { toggleEnabled(item); } void CtrlBreakpointList::showBreakpointMenu(int itemIndex, const POINT &pt) { bool isMemory; int index = getBreakpointIndex(itemIndex, isMemory); if (index == -1) { switch (TriggerContextMenu(ContextMenuID::NEWBREAKPOINT, GetHandle(), ContextPoint::FromClient(pt))) { case ID_DISASM_ADDNEWBREAKPOINT: { BreakpointWindow bpw(GetHandle(),cpu); if (bpw.exec()) bpw.addBreakpoint(); } break; } } else { MemCheck mcPrev; BreakPoint bpPrev; if (isMemory) { mcPrev = displayedMemChecks_[index]; } else { bpPrev = displayedBreakPoints_[index]; } HMENU subMenu = GetContextMenu(ContextMenuID::BREAKPOINTLIST); if (isMemory) { CheckMenuItem(subMenu, ID_DISASM_DISABLEBREAKPOINT, MF_BYCOMMAND | (mcPrev.IsEnabled() ? MF_CHECKED : MF_UNCHECKED)); } else { CheckMenuItem(subMenu, ID_DISASM_DISABLEBREAKPOINT, MF_BYCOMMAND | (bpPrev.IsEnabled() ? MF_CHECKED : MF_UNCHECKED)); } switch (TriggerContextMenu(ContextMenuID::BREAKPOINTLIST, GetHandle(), ContextPoint::FromClient(pt))) { case ID_DISASM_DISABLEBREAKPOINT: if (isMemory) { g_breakpoints.ChangeMemCheck(mcPrev.start, mcPrev.end, mcPrev.cond, BreakAction(mcPrev.result ^ BREAK_ACTION_PAUSE)); } else { g_breakpoints.ChangeBreakPoint(bpPrev.addr, BreakAction(bpPrev.result ^ BREAK_ACTION_PAUSE)); } break; case ID_DISASM_EDITBREAKPOINT: editBreakpoint(itemIndex); break; case ID_DISASM_ADDNEWBREAKPOINT: { BreakpointWindow bpw(GetHandle(),cpu); if (bpw.exec()) bpw.addBreakpoint(); } break; case ID_DISASM_DELETEBREAKPOINT: removeBreakpoint(itemIndex); break; } } } // // CtrlStackTraceView // CtrlStackTraceView::CtrlStackTraceView(HWND hwnd, DebugInterface* cpu, CtrlDisAsmView* disasm) : GenericListControl(hwnd,stackTraceListDef),cpu(cpu),disasm(disasm) { Update(); } bool CtrlStackTraceView::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue) { switch(msg) { case WM_KEYDOWN: if (wParam == VK_TAB) { returnValue = 0; SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0); return true; } break; case WM_GETDLGCODE: if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN) { if (wParam == VK_TAB || wParam == VK_RETURN) { returnValue = DLGC_WANTMESSAGE; return true; } } break; } return false; } void CtrlStackTraceView::GetColumnText(wchar_t* dest, size_t destSize, int row, int col) { // We should have emptied the list if g_symbolMap is nullptr, but apparently we don't, // so let's have a sanity check here. if (row < 0 || row >= (int)frames.size() || !g_symbolMap) { return; } switch (col) { case SF_ENTRY: wsprintf(dest,L"%08X",frames[row].entry); break; case SF_ENTRYNAME: { const std::string sym = g_symbolMap->GetLabelString(frames[row].entry); if (!sym.empty()) { wcscpy(dest, ConvertUTF8ToWString(sym).c_str()); } else { wcscpy(dest,L"-"); } } break; case SF_CURPC: wsprintf(dest,L"%08X",frames[row].pc); break; case SF_CUROPCODE: { char temp[512]; disasm->getOpcodeText(frames[row].pc, temp, sizeof(temp)); wcscpy(dest, ConvertUTF8ToWString(temp).c_str()); } break; case SF_CURSP: wsprintf(dest,L"%08X",frames[row].sp); break; case SF_FRAMESIZE: wsprintf(dest,L"%08X",frames[row].stackSize); break; } } void CtrlStackTraceView::OnDoubleClick(int itemIndex, int column) { SendMessage(GetParent(GetHandle()),WM_DEB_GOTOWPARAM,frames[itemIndex].pc,0); } void CtrlStackTraceView::loadStackTrace() { auto memLock = Memory::Lock(); if (!PSP_IsInited()) return; auto threads = GetThreadsInfo(); u32 entry = 0, stackTop = 0; for (size_t i = 0; i < threads.size(); i++) { if (threads[i].isCurrent) { entry = threads[i].entrypoint; stackTop = threads[i].initialStack; break; } } if (entry != 0) { frames = MIPSStackWalk::Walk(cpu->GetPC(),cpu->GetRegValue(0,31),cpu->GetRegValue(0,29),entry,stackTop); } else { frames.clear(); } Update(); } // // CtrlModuleList // CtrlModuleList::CtrlModuleList(HWND hwnd, DebugInterface* cpu) : GenericListControl(hwnd,moduleListDef),cpu(cpu) { Update(); } bool CtrlModuleList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue) { switch(msg) { case WM_KEYDOWN: if (wParam == VK_TAB) { returnValue = 0; SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0); return true; } break; case WM_GETDLGCODE: if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN) { if (wParam == VK_TAB || wParam == VK_RETURN) { returnValue = DLGC_WANTMESSAGE; return true; } } break; } return false; } void CtrlModuleList::GetColumnText(wchar_t* dest, size_t destSize, int row, int col) { if (row < 0 || row >= (int)modules.size()) { return; } switch (col) { case ML_NAME: ConvertUTF8ToWString(dest, destSize, modules[row].name); break; case ML_ADDRESS: wsprintf(dest,L"%08X",modules[row].address); break; case ML_SIZE: wsprintf(dest,L"%08X",modules[row].size); break; case ML_ACTIVE: wcscpy(dest,modules[row].active ? L"true" : L"false"); break; } } void CtrlModuleList::OnDoubleClick(int itemIndex, int column) { SendMessage(GetParent(GetHandle()),WM_DEB_GOTOWPARAM,modules[itemIndex].address,0); } void CtrlModuleList::loadModules() { if (g_symbolMap) { modules = g_symbolMap->getAllModules(); } else { modules.clear(); } Update(); } // In case you modify things in the memory view. static constexpr UINT_PTR IDT_CHECK_REFRESH = 0xC0DE0044; CtrlWatchList::CtrlWatchList(HWND hwnd, DebugInterface *cpu) : GenericListControl(hwnd, watchListDef), cpu_(cpu) { SetSendInvalidRows(true); Update(); SetTimer(GetHandle(), IDT_CHECK_REFRESH, 1000U, nullptr); } void CtrlWatchList::RefreshValues() { int steppingCounter = Core_GetSteppingCounter(); int changes = false; for (auto &watch : watches_) { if (watch.steppingCounter != steppingCounter) { watch.lastValue = watch.currentValue; watch.steppingCounter = steppingCounter; changes = true; } uint32_t prevValue = watch.currentValue; watch.evaluateFailed = !parseExpression(cpu_, watch.expression, watch.currentValue); if (prevValue != watch.currentValue) changes = true; } if (changes) Update(); } bool CtrlWatchList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT &returnValue) { switch (msg) { case WM_KEYDOWN: switch (wParam) { case VK_TAB: returnValue = 0; SendMessage(GetParent(GetHandle()), WM_DEB_TABPRESSED, 0, 0); return true; case VK_RETURN: returnValue = 0; EditWatch(GetSelectedIndex()); return true; case VK_DELETE: returnValue = 0; DeleteWatch(GetSelectedIndex()); return true; default: break; } break; case WM_GETDLGCODE: if (lParam && ((MSG *)lParam)->message == WM_KEYDOWN) { if (wParam == VK_TAB || wParam == VK_RETURN || wParam == VK_DELETE) { returnValue = DLGC_WANTMESSAGE; return true; } } break; case WM_TIMER: if (wParam == IDT_CHECK_REFRESH) { RefreshValues(); return true; } break; } return false; } void CtrlWatchList::GetColumnText(wchar_t *dest, size_t destSize, int row, int col) { const auto &watch = watches_[row]; switch (col) { case WL_NAME: wcsncpy(dest, ConvertUTF8ToWString(watch.name).c_str(), 255); dest[255] = 0; break; case WL_EXPRESSION: wcsncpy(dest, ConvertUTF8ToWString(watch.originalExpression).c_str(), 255); dest[255] = 0; break; case WL_VALUE: if (watch.evaluateFailed) { wcscpy(dest, L"(failed to evaluate)"); } else { const uint32_t &value = watch.currentValue; float valuef = 0.0f; switch (watch.format) { case WatchFormat::HEX: wsprintf(dest, L"0x%08X", value); break; case WatchFormat::INT: wsprintf(dest, L"%d", (int32_t)value); break; case WatchFormat::FLOAT: memcpy(&valuef, &value, sizeof(valuef)); swprintf_s(dest, destSize, L"%f", valuef); break; case WatchFormat::STR: if (Memory::IsValidAddress(value)) { uint32_t len = Memory::ValidSize(value, 255); swprintf_s(dest, destSize, L"%.*S", len, Memory::GetCharPointer(value)); } else { wsprintf(dest, L"(0x%08X)", value); } break; } } break; } } void CtrlWatchList::OnRightClick(int itemIndex, int column, const POINT &pt) { if (itemIndex == -1) { switch (TriggerContextMenu(ContextMenuID::CPUADDWATCH, GetHandle(), ContextPoint::FromClient(pt))) { case ID_DISASM_ADDNEWBREAKPOINT: AddWatch(); break; } } else { switch (TriggerContextMenu(ContextMenuID::CPUWATCHLIST, GetHandle(), ContextPoint::FromClient(pt))) { case ID_DISASM_EDITBREAKPOINT: EditWatch(itemIndex); break; case ID_DISASM_DELETEBREAKPOINT: DeleteWatch(itemIndex); break; case ID_DISASM_ADDNEWBREAKPOINT: AddWatch(); break; } } } bool CtrlWatchList::OnRowPrePaint(int row, LPNMLVCUSTOMDRAW msg) { if (row >= 0 && HasWatchChanged(row)) { msg->clrText = RGB(255, 0, 0); return true; } return false; } void CtrlWatchList::AddWatch() { WatchItemWindow win(nullptr, GetHandle(), cpu_); if (win.Exec()) { WatchInfo info; if (initExpression(cpu_, win.GetExpression().c_str(), info.expression)) { info.name = win.GetName(); info.originalExpression = win.GetExpression(); info.format = win.GetFormat(); watches_.push_back(info); RefreshValues(); } else { char errorMessage[512]; snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", win.GetExpression().c_str(), getExpressionError()); MessageBoxA(GetHandle(), errorMessage, "Error", MB_OK); } } } void CtrlWatchList::EditWatch(int pos) { auto &watch = watches_[pos]; WatchItemWindow win(nullptr, GetHandle(), cpu_); win.Init(watch.name, watch.originalExpression, watch.format); if (win.Exec()) { if (initExpression(cpu_, win.GetExpression().c_str(), watch.expression)) { watch.name = win.GetName(); watch.originalExpression = win.GetExpression(); watch.format = win.GetFormat(); RefreshValues(); } else { char errorMessage[512]; snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", win.GetExpression().c_str(), getExpressionError()); MessageBoxA(GetHandle(), errorMessage, "Error", MB_OK); } } } void CtrlWatchList::DeleteWatch(int pos) { watches_.erase(watches_.begin() + pos); Update(); } bool CtrlWatchList::HasWatchChanged(int pos) { return watches_[pos].lastValue != watches_[pos].currentValue; }