CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Windows/Debugger/Debugger_Lists.cpp
Views: 1401
#include "Windows/Debugger/Debugger_Lists.h"1#include "Common/CommonWindows.h"2#include <windowsx.h>3#include <commctrl.h>4#include "Windows/Debugger/BreakpointWindow.h"5#include "Windows/Debugger/CtrlDisAsmView.h"6#include "Windows/Debugger/DebuggerShared.h"7#include "Windows/Debugger/WatchItemWindow.h"8#include "Windows/W32Util/ContextMenu.h"9#include "Windows/MainWindow.h"10#include "Windows/resource.h"11#include "Windows/main.h"12#include "Common/Data/Encoding/Utf8.h"13#include "Core/HLE/sceKernelThread.h"1415enum { TL_NAME, TL_PROGRAMCOUNTER, TL_ENTRYPOINT, TL_PRIORITY, TL_STATE, TL_WAITTYPE, TL_COLUMNCOUNT };16enum { BPL_ENABLED, BPL_TYPE, BPL_OFFSET, BPL_SIZELABEL, BPL_OPCODE, BPL_CONDITION, BPL_HITS, BPL_COLUMNCOUNT };17enum { SF_ENTRY, SF_ENTRYNAME, SF_CURPC, SF_CUROPCODE, SF_CURSP, SF_FRAMESIZE, SF_COLUMNCOUNT };18enum { ML_NAME, ML_ADDRESS, ML_SIZE, ML_ACTIVE, ML_COLUMNCOUNT };19enum { WL_NAME, WL_EXPRESSION, WL_VALUE, WL_COLUMNCOUNT };2021GenericListViewColumn threadColumns[TL_COLUMNCOUNT] = {22{ L"Name", 0.20f },23{ L"PC", 0.15f },24{ L"Entry Point", 0.15f },25{ L"Priority", 0.15f },26{ L"State", 0.15f },27{ L"Wait type", 0.20f }28};2930GenericListViewDef threadListDef = {31threadColumns, ARRAY_SIZE(threadColumns), NULL, false32};3334GenericListViewColumn breakpointColumns[BPL_COLUMNCOUNT] = {35{ L"", 0.03f }, // enabled36{ L"Type", 0.15f },37{ L"Offset", 0.12f },38{ L"Size/Label", 0.20f },39{ L"Opcode", 0.28f },40{ L"Condition", 0.17f },41{ L"Hits", 0.05f },42};4344GenericListViewDef breakpointListDef = {45breakpointColumns, ARRAY_SIZE(breakpointColumns), NULL, true46};4748GenericListViewColumn stackTraceColumns[SF_COLUMNCOUNT] = {49{ L"Entry", 0.12f },50{ L"Name", 0.24f },51{ L"PC", 0.12f },52{ L"Opcode", 0.28f },53{ L"SP", 0.12f },54{ L"Frame Size", 0.12f }55};5657GenericListViewDef stackTraceListDef = {58stackTraceColumns, ARRAY_SIZE(stackTraceColumns), NULL, false59};6061GenericListViewColumn moduleListColumns[ML_COLUMNCOUNT] = {62{ L"Name", 0.25f },63{ L"Address", 0.25f },64{ L"Size", 0.25f },65{ L"Active", 0.25f },66};6768GenericListViewDef moduleListDef = {69moduleListColumns, ARRAY_SIZE(moduleListColumns), NULL, false70};7172GenericListViewColumn watchListColumns[WL_COLUMNCOUNT] = {73{ L"Name", 0.25f },74{ L"Expression", 0.5f },75{ L"Value", 0.25f },76};7778GenericListViewDef watchListDef = {79watchListColumns, ARRAY_SIZE(watchListColumns), nullptr, false,80};8182//83// CtrlThreadList84//8586CtrlThreadList::CtrlThreadList(HWND hwnd): GenericListControl(hwnd,threadListDef)87{88Update();89}9091bool CtrlThreadList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue)92{93switch (msg)94{95case WM_KEYDOWN:96if (wParam == VK_TAB)97{98SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0);99returnValue = 0;100return true;101}102break;103case WM_GETDLGCODE:104if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN)105{106if (wParam == VK_TAB)107{108returnValue = DLGC_WANTMESSAGE;109return true;110}111}112break;113}114115return false;116}117118void CtrlThreadList::showMenu(int itemIndex, const POINT &pt)119{120auto threadInfo = threads[itemIndex];121122// Can't do it, sorry. Needs to not be running.123if (Core_IsActive())124return;125126HMENU subMenu = GetContextMenu(ContextMenuID::THREADLIST);127switch (threadInfo.status) {128case THREADSTATUS_DEAD:129case THREADSTATUS_DORMANT:130case THREADSTATUS_RUNNING:131EnableMenuItem(subMenu, ID_DISASM_THREAD_FORCERUN, MF_BYCOMMAND | MF_DISABLED);132EnableMenuItem(subMenu, ID_DISASM_THREAD_KILL, MF_BYCOMMAND | MF_DISABLED);133break;134case THREADSTATUS_READY:135EnableMenuItem(subMenu, ID_DISASM_THREAD_FORCERUN, MF_BYCOMMAND | MF_DISABLED);136EnableMenuItem(subMenu, ID_DISASM_THREAD_KILL, MF_BYCOMMAND | MF_ENABLED);137break;138case THREADSTATUS_SUSPEND:139case THREADSTATUS_WAIT:140case THREADSTATUS_WAITSUSPEND:141default:142EnableMenuItem(subMenu, ID_DISASM_THREAD_FORCERUN, MF_BYCOMMAND | MF_ENABLED);143EnableMenuItem(subMenu, ID_DISASM_THREAD_KILL, MF_BYCOMMAND | MF_ENABLED);144break;145}146147switch (TriggerContextMenu(ContextMenuID::THREADLIST, GetHandle(), ContextPoint::FromClient(pt)))148{149case ID_DISASM_THREAD_FORCERUN:150__KernelResumeThreadFromWait(threadInfo.id, 0);151reloadThreads();152break;153case ID_DISASM_THREAD_KILL:154sceKernelTerminateThread(threadInfo.id);155reloadThreads();156break;157}158}159160void CtrlThreadList::GetColumnText(wchar_t* dest, int row, int col)161{162if (row < 0 || row >= (int)threads.size()) {163return;164}165166switch (col)167{168case TL_NAME:169wcscpy(dest, ConvertUTF8ToWString(threads[row].name).c_str());170break;171case TL_PROGRAMCOUNTER:172switch (threads[row].status)173{174case THREADSTATUS_DORMANT:175case THREADSTATUS_DEAD:176wcscpy(dest, L"N/A");177break;178default:179wsprintf(dest, L"0x%08X",threads[row].curPC);180break;181};182break;183case TL_ENTRYPOINT:184wsprintf(dest,L"0x%08X",threads[row].entrypoint);185break;186case TL_PRIORITY:187wsprintf(dest,L"%d",threads[row].priority);188break;189case TL_STATE:190switch (threads[row].status)191{192case THREADSTATUS_RUNNING:193wcscpy(dest,L"Running");194break;195case THREADSTATUS_READY:196wcscpy(dest,L"Ready");197break;198case THREADSTATUS_WAIT:199wcscpy(dest,L"Waiting");200break;201case THREADSTATUS_SUSPEND:202wcscpy(dest,L"Suspended");203break;204case THREADSTATUS_DORMANT:205wcscpy(dest,L"Dormant");206break;207case THREADSTATUS_DEAD:208wcscpy(dest,L"Dead");209break;210case THREADSTATUS_WAITSUSPEND:211wcscpy(dest,L"Waiting/Suspended");212break;213default:214wcscpy(dest,L"Invalid");215break;216}217break;218case TL_WAITTYPE:219wcscpy(dest, ConvertUTF8ToWString(getWaitTypeName(threads[row].waitType)).c_str());220break;221}222}223224void CtrlThreadList::OnDoubleClick(int itemIndex, int column)225{226u32 address;227switch (threads[itemIndex].status)228{229case THREADSTATUS_DORMANT:230case THREADSTATUS_DEAD:231address = threads[itemIndex].entrypoint;232break;233default:234address = threads[itemIndex].curPC;235break;236}237238SendMessage(GetParent(GetHandle()),WM_DEB_GOTOWPARAM,address,0);239}240241void CtrlThreadList::OnRightClick(int itemIndex, int column, const POINT& point)242{243showMenu(itemIndex,point);244}245246void CtrlThreadList::reloadThreads()247{248threads = GetThreadsInfo();249Update();250}251252const char* CtrlThreadList::getCurrentThreadName()253{254for (size_t i = 0; i < threads.size(); i++)255{256if (threads[i].isCurrent) return threads[i].name;257}258259return "N/A";260}261262263//264// CtrlBreakpointList265//266267CtrlBreakpointList::CtrlBreakpointList(HWND hwnd, DebugInterface* cpu, CtrlDisAsmView* disasm)268: GenericListControl(hwnd,breakpointListDef),cpu(cpu),disasm(disasm)269{270SetSendInvalidRows(true);271Update();272}273274bool CtrlBreakpointList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue)275{276switch(msg)277{278case WM_KEYDOWN:279returnValue = 0;280if(wParam == VK_RETURN)281{282int index = GetSelectedIndex();283editBreakpoint(index);284return true;285} else if (wParam == VK_DELETE)286{287int index = GetSelectedIndex();288removeBreakpoint(index);289return true;290} else if (wParam == VK_TAB)291{292SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0);293return true;294} else if (wParam == VK_SPACE)295{296int index = GetSelectedIndex();297toggleEnabled(index);298return true;299}300break;301case WM_GETDLGCODE:302if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN)303{304if (wParam == VK_TAB || wParam == VK_RETURN)305{306returnValue = DLGC_WANTMESSAGE;307return true;308}309}310break;311}312313return false;314}315316void CtrlBreakpointList::reloadBreakpoints()317{318// Update the items we're displaying from the debugger.319displayedBreakPoints_ = CBreakPoints::GetBreakpoints();320displayedMemChecks_= CBreakPoints::GetMemChecks();321322for (int i = 0; i < GetRowCount(); i++)323{324bool isMemory;325int index = getBreakpointIndex(i, isMemory);326if (index < 0)327continue;328329if (isMemory)330SetCheckState(i, displayedMemChecks_[index].IsEnabled());331else332SetCheckState(i, displayedBreakPoints_[index].IsEnabled());333}334335Update();336}337338void CtrlBreakpointList::editBreakpoint(int itemIndex)339{340bool isMemory;341int index = getBreakpointIndex(itemIndex, isMemory);342if (index == -1) return;343344BreakpointWindow win(GetHandle(),cpu);345if (isMemory)346{347auto mem = displayedMemChecks_[index];348win.loadFromMemcheck(mem);349if (win.exec())350{351CBreakPoints::RemoveMemCheck(mem.start,mem.end);352win.addBreakpoint();353}354} else {355auto bp = displayedBreakPoints_[index];356win.loadFromBreakpoint(bp);357if (win.exec())358{359CBreakPoints::RemoveBreakPoint(bp.addr);360win.addBreakpoint();361}362}363}364365void CtrlBreakpointList::toggleEnabled(int itemIndex)366{367bool isMemory;368int index = getBreakpointIndex(itemIndex, isMemory);369if (index == -1) return;370371if (isMemory) {372MemCheck mcPrev = displayedMemChecks_[index];373CBreakPoints::ChangeMemCheck(mcPrev.start, mcPrev.end, mcPrev.cond, BreakAction(mcPrev.result ^ BREAK_ACTION_PAUSE));374} else {375BreakPoint bpPrev = displayedBreakPoints_[index];376CBreakPoints::ChangeBreakPoint(bpPrev.addr, BreakAction(bpPrev.result ^ BREAK_ACTION_PAUSE));377}378}379380void CtrlBreakpointList::gotoBreakpointAddress(int itemIndex)381{382bool isMemory;383int index = getBreakpointIndex(itemIndex, isMemory);384if (index == -1)385return;386387if (isMemory) {388u32 address = displayedMemChecks_[index].start;389MainWindow::CreateMemoryWindow();390if (memoryWindow)391memoryWindow->Goto(address);392} else {393u32 address = displayedBreakPoints_[index].addr;394MainWindow::CreateDisasmWindow();395if (disasmWindow)396disasmWindow->Goto(address);397}398}399400void CtrlBreakpointList::removeBreakpoint(int itemIndex)401{402bool isMemory;403int index = getBreakpointIndex(itemIndex,isMemory);404if (index == -1) return;405406if (isMemory)407{408auto mc = displayedMemChecks_[index];409CBreakPoints::RemoveMemCheck(mc.start, mc.end);410} else {411u32 address = displayedBreakPoints_[index].addr;412CBreakPoints::RemoveBreakPoint(address);413}414}415416int CtrlBreakpointList::getTotalBreakpointCount() {417int count = (int)displayedMemChecks_.size();418for (auto bp : displayedBreakPoints_) {419if (!bp.temporary)420++count;421}422423return count;424}425426int CtrlBreakpointList::getBreakpointIndex(int itemIndex, bool& isMemory)427{428// memory breakpoints first429if (itemIndex < (int)displayedMemChecks_.size())430{431isMemory = true;432return itemIndex;433}434435itemIndex -= (int)displayedMemChecks_.size();436437size_t i = 0;438while (i < displayedBreakPoints_.size())439{440if (displayedBreakPoints_[i].temporary)441{442i++;443continue;444}445446// the index is 0 when there are no more breakpoints to skip447if (itemIndex == 0)448{449isMemory = false;450return (int)i;451}452453i++;454itemIndex--;455}456457return -1;458}459460void CtrlBreakpointList::GetColumnText(wchar_t* dest, int row, int col)461{462if (!PSP_IsInited()) {463return;464}465bool isMemory;466int index = getBreakpointIndex(row,isMemory);467if (index == -1) return;468469switch (col)470{471case BPL_TYPE:472{473if (isMemory) {474switch ((int)displayedMemChecks_[index].cond) {475case MEMCHECK_READ:476wcscpy(dest,L"Read");477break;478case MEMCHECK_WRITE:479wcscpy(dest,L"Write");480break;481case MEMCHECK_READWRITE:482wcscpy(dest,L"Read/Write");483break;484case MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE:485wcscpy(dest,L"Write Change");486break;487case MEMCHECK_READWRITE | MEMCHECK_WRITE_ONCHANGE:488wcscpy(dest,L"Read/Write Change");489break;490}491} else {492wcscpy(dest,L"Execute");493}494}495break;496case BPL_OFFSET:497{498if (isMemory) {499wsprintf(dest,L"0x%08X",displayedMemChecks_[index].start);500} else {501wsprintf(dest,L"0x%08X",displayedBreakPoints_[index].addr);502}503}504break;505case BPL_SIZELABEL:506{507if (isMemory) {508auto mc = displayedMemChecks_[index];509if (mc.end == 0)510wsprintf(dest,L"0x%08X",1);511else512wsprintf(dest,L"0x%08X",mc.end-mc.start);513} else {514const std::string sym = g_symbolMap->GetLabelString(displayedBreakPoints_[index].addr);515if (!sym.empty())516{517std::wstring s = ConvertUTF8ToWString(sym);518wcscpy(dest,s.c_str());519} else {520wcscpy(dest,L"-");521}522}523}524break;525case BPL_OPCODE:526{527if (isMemory) {528wcscpy(dest,L"-");529} else {530char temp[256];531disasm->getOpcodeText(displayedBreakPoints_[index].addr, temp, sizeof(temp));532std::wstring s = ConvertUTF8ToWString(temp);533wcscpy(dest,s.c_str());534}535}536break;537case BPL_CONDITION:538{539if (isMemory || displayedBreakPoints_[index].hasCond == false) {540wcscpy(dest,L"-");541} else {542std::wstring s = ConvertUTF8ToWString(displayedBreakPoints_[index].cond.expressionString);543wcscpy(dest,s.c_str());544}545}546break;547case BPL_HITS:548{549if (isMemory) {550wsprintf(dest,L"%d",displayedMemChecks_[index].numHits);551} else {552wsprintf(dest,L"-");553}554}555break;556case BPL_ENABLED:557{558wsprintf(dest,L"\xFFFE");559}560break;561}562}563564void CtrlBreakpointList::OnDoubleClick(int itemIndex, int column)565{566gotoBreakpointAddress(itemIndex);567}568569void CtrlBreakpointList::OnRightClick(int itemIndex, int column, const POINT& point)570{571showBreakpointMenu(itemIndex,point);572}573574void CtrlBreakpointList::OnToggle(int item, bool newValue)575{576toggleEnabled(item);577}578579void CtrlBreakpointList::showBreakpointMenu(int itemIndex, const POINT &pt)580{581bool isMemory;582int index = getBreakpointIndex(itemIndex, isMemory);583if (index == -1)584{585switch (TriggerContextMenu(ContextMenuID::NEWBREAKPOINT, GetHandle(), ContextPoint::FromClient(pt)))586{587case ID_DISASM_ADDNEWBREAKPOINT:588{589BreakpointWindow bpw(GetHandle(),cpu);590if (bpw.exec()) bpw.addBreakpoint();591}592break;593}594} else {595MemCheck mcPrev;596BreakPoint bpPrev;597if (isMemory) {598mcPrev = displayedMemChecks_[index];599} else {600bpPrev = displayedBreakPoints_[index];601}602603HMENU subMenu = GetContextMenu(ContextMenuID::BREAKPOINTLIST);604if (isMemory) {605CheckMenuItem(subMenu, ID_DISASM_DISABLEBREAKPOINT, MF_BYCOMMAND | (mcPrev.IsEnabled() ? MF_CHECKED : MF_UNCHECKED));606} else {607CheckMenuItem(subMenu, ID_DISASM_DISABLEBREAKPOINT, MF_BYCOMMAND | (bpPrev.IsEnabled() ? MF_CHECKED : MF_UNCHECKED));608}609610switch (TriggerContextMenu(ContextMenuID::BREAKPOINTLIST, GetHandle(), ContextPoint::FromClient(pt)))611{612case ID_DISASM_DISABLEBREAKPOINT:613if (isMemory) {614CBreakPoints::ChangeMemCheck(mcPrev.start, mcPrev.end, mcPrev.cond, BreakAction(mcPrev.result ^ BREAK_ACTION_PAUSE));615} else {616CBreakPoints::ChangeBreakPoint(bpPrev.addr, BreakAction(bpPrev.result ^ BREAK_ACTION_PAUSE));617}618break;619case ID_DISASM_EDITBREAKPOINT:620editBreakpoint(itemIndex);621break;622case ID_DISASM_ADDNEWBREAKPOINT:623{624BreakpointWindow bpw(GetHandle(),cpu);625if (bpw.exec()) bpw.addBreakpoint();626}627break;628case ID_DISASM_DELETEBREAKPOINT:629removeBreakpoint(itemIndex);630break;631}632}633}634635//636// CtrlStackTraceView637//638639CtrlStackTraceView::CtrlStackTraceView(HWND hwnd, DebugInterface* cpu, CtrlDisAsmView* disasm)640: GenericListControl(hwnd,stackTraceListDef),cpu(cpu),disasm(disasm)641{642Update();643}644645bool CtrlStackTraceView::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue)646{647switch(msg)648{649case WM_KEYDOWN:650if (wParam == VK_TAB)651{652returnValue = 0;653SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0);654return true;655}656break;657case WM_GETDLGCODE:658if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN)659{660if (wParam == VK_TAB || wParam == VK_RETURN)661{662returnValue = DLGC_WANTMESSAGE;663return true;664}665}666break;667}668669return false;670}671672void CtrlStackTraceView::GetColumnText(wchar_t* dest, int row, int col)673{674// We should have emptied the list if g_symbolMap is nullptr, but apparently we don't,675// so let's have a sanity check here.676if (row < 0 || row >= (int)frames.size() || !g_symbolMap) {677return;678}679680switch (col)681{682case SF_ENTRY:683wsprintf(dest,L"%08X",frames[row].entry);684break;685case SF_ENTRYNAME:686{687const std::string sym = g_symbolMap->GetLabelString(frames[row].entry);688if (!sym.empty()) {689wcscpy(dest, ConvertUTF8ToWString(sym).c_str());690} else {691wcscpy(dest,L"-");692}693}694break;695case SF_CURPC:696wsprintf(dest,L"%08X",frames[row].pc);697break;698case SF_CUROPCODE:699{700char temp[512];701disasm->getOpcodeText(frames[row].pc, temp, sizeof(temp));702wcscpy(dest, ConvertUTF8ToWString(temp).c_str());703}704break;705case SF_CURSP:706wsprintf(dest,L"%08X",frames[row].sp);707break;708case SF_FRAMESIZE:709wsprintf(dest,L"%08X",frames[row].stackSize);710break;711}712}713714void CtrlStackTraceView::OnDoubleClick(int itemIndex, int column)715{716SendMessage(GetParent(GetHandle()),WM_DEB_GOTOWPARAM,frames[itemIndex].pc,0);717}718719void CtrlStackTraceView::loadStackTrace() {720auto memLock = Memory::Lock();721if (!PSP_IsInited())722return;723724auto threads = GetThreadsInfo();725726u32 entry = 0, stackTop = 0;727for (size_t i = 0; i < threads.size(); i++)728{729if (threads[i].isCurrent)730{731entry = threads[i].entrypoint;732stackTop = threads[i].initialStack;733break;734}735}736737if (entry != 0) {738frames = MIPSStackWalk::Walk(cpu->GetPC(),cpu->GetRegValue(0,31),cpu->GetRegValue(0,29),entry,stackTop);739} else {740frames.clear();741}742Update();743}744745//746// CtrlModuleList747//748749CtrlModuleList::CtrlModuleList(HWND hwnd, DebugInterface* cpu)750: GenericListControl(hwnd,moduleListDef),cpu(cpu)751{752Update();753}754755bool CtrlModuleList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue)756{757switch(msg)758{759case WM_KEYDOWN:760if (wParam == VK_TAB)761{762returnValue = 0;763SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0);764return true;765}766break;767case WM_GETDLGCODE:768if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN)769{770if (wParam == VK_TAB || wParam == VK_RETURN)771{772returnValue = DLGC_WANTMESSAGE;773return true;774}775}776break;777}778779return false;780}781782void CtrlModuleList::GetColumnText(wchar_t* dest, int row, int col)783{784if (row < 0 || row >= (int)modules.size()) {785return;786}787788switch (col)789{790case ML_NAME:791wcscpy(dest,ConvertUTF8ToWString(modules[row].name).c_str());792break;793case ML_ADDRESS:794wsprintf(dest,L"%08X",modules[row].address);795break;796case ML_SIZE:797wsprintf(dest,L"%08X",modules[row].size);798break;799case ML_ACTIVE:800wcscpy(dest,modules[row].active ? L"true" : L"false");801break;802}803}804805void CtrlModuleList::OnDoubleClick(int itemIndex, int column)806{807SendMessage(GetParent(GetHandle()),WM_DEB_GOTOWPARAM,modules[itemIndex].address,0);808}809810void CtrlModuleList::loadModules()811{812if (g_symbolMap) {813modules = g_symbolMap->getAllModules();814} else {815modules.clear();816}817Update();818}819820// In case you modify things in the memory view.821static constexpr UINT_PTR IDT_CHECK_REFRESH = 0xC0DE0044;822823CtrlWatchList::CtrlWatchList(HWND hwnd, DebugInterface *cpu)824: GenericListControl(hwnd, watchListDef), cpu_(cpu) {825SetSendInvalidRows(true);826Update();827828SetTimer(GetHandle(), IDT_CHECK_REFRESH, 1000U, nullptr);829}830831void CtrlWatchList::RefreshValues() {832int steppingCounter = Core_GetSteppingCounter();833int changes = false;834for (auto &watch : watches_) {835if (watch.steppingCounter != steppingCounter) {836watch.lastValue = watch.currentValue;837watch.steppingCounter = steppingCounter;838changes = true;839}840841uint32_t prevValue = watch.currentValue;842watch.evaluateFailed = !cpu_->parseExpression(watch.expression, watch.currentValue);843if (prevValue != watch.currentValue)844changes = true;845}846847if (changes)848Update();849}850851bool CtrlWatchList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT &returnValue) {852switch (msg) {853case WM_KEYDOWN:854switch (wParam) {855case VK_TAB:856returnValue = 0;857SendMessage(GetParent(GetHandle()), WM_DEB_TABPRESSED, 0, 0);858return true;859case VK_RETURN:860returnValue = 0;861EditWatch(GetSelectedIndex());862return true;863case VK_DELETE:864returnValue = 0;865DeleteWatch(GetSelectedIndex());866return true;867default:868break;869}870break;871case WM_GETDLGCODE:872if (lParam && ((MSG *)lParam)->message == WM_KEYDOWN) {873if (wParam == VK_TAB || wParam == VK_RETURN || wParam == VK_DELETE) {874returnValue = DLGC_WANTMESSAGE;875return true;876}877}878break;879case WM_TIMER:880if (wParam == IDT_CHECK_REFRESH) {881RefreshValues();882return true;883}884break;885}886887return false;888}889890void CtrlWatchList::GetColumnText(wchar_t *dest, int row, int col) {891const auto &watch = watches_[row];892switch (col) {893case WL_NAME:894wcsncpy(dest, ConvertUTF8ToWString(watch.name).c_str(), 255);895dest[255] = 0;896break;897case WL_EXPRESSION:898wcsncpy(dest, ConvertUTF8ToWString(watch.originalExpression).c_str(), 255);899dest[255] = 0;900break;901case WL_VALUE:902if (watch.evaluateFailed) {903wcscpy(dest, L"(failed to evaluate)");904} else {905const uint32_t &value = watch.currentValue;906float valuef = 0.0f;907switch (watch.format) {908case WatchFormat::HEX:909wsprintf(dest, L"0x%08X", value);910break;911case WatchFormat::INT:912wsprintf(dest, L"%d", (int32_t)value);913break;914case WatchFormat::FLOAT:915memcpy(&valuef, &value, sizeof(valuef));916swprintf_s(dest, 255, L"%f", valuef);917break;918case WatchFormat::STR:919if (Memory::IsValidAddress(value)) {920uint32_t len = Memory::ValidSize(value, 255);921swprintf_s(dest, 255, L"%.*S", len, Memory::GetCharPointer(value));922} else {923wsprintf(dest, L"(0x%08X)", value);924}925break;926}927}928break;929}930}931932void CtrlWatchList::OnRightClick(int itemIndex, int column, const POINT &pt) {933if (itemIndex == -1) {934switch (TriggerContextMenu(ContextMenuID::CPUADDWATCH, GetHandle(), ContextPoint::FromClient(pt))) {935case ID_DISASM_ADDNEWBREAKPOINT:936AddWatch();937break;938}939} else {940switch (TriggerContextMenu(ContextMenuID::CPUWATCHLIST, GetHandle(), ContextPoint::FromClient(pt))) {941case ID_DISASM_EDITBREAKPOINT:942EditWatch(itemIndex);943break;944case ID_DISASM_DELETEBREAKPOINT:945DeleteWatch(itemIndex);946break;947case ID_DISASM_ADDNEWBREAKPOINT:948AddWatch();949break;950}951}952}953954bool CtrlWatchList::OnRowPrePaint(int row, LPNMLVCUSTOMDRAW msg) {955if (row >= 0 && HasWatchChanged(row)) {956msg->clrText = RGB(255, 0, 0);957return true;958}959return false;960}961962void CtrlWatchList::AddWatch() {963WatchItemWindow win(nullptr, GetHandle(), cpu_);964if (win.Exec()) {965WatchInfo info;966if (cpu_->initExpression(win.GetExpression().c_str(), info.expression)) {967info.name = win.GetName();968info.originalExpression = win.GetExpression();969info.format = win.GetFormat();970watches_.push_back(info);971RefreshValues();972} else {973char errorMessage[512];974snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", win.GetExpression().c_str(), getExpressionError());975MessageBoxA(GetHandle(), errorMessage, "Error", MB_OK);976}977}978}979980void CtrlWatchList::EditWatch(int pos) {981auto &watch = watches_[pos];982WatchItemWindow win(nullptr, GetHandle(), cpu_);983win.Init(watch.name, watch.originalExpression, watch.format);984if (win.Exec()) {985if (cpu_->initExpression(win.GetExpression().c_str(), watch.expression)) {986watch.name = win.GetName();987watch.originalExpression = win.GetExpression();988watch.format = win.GetFormat();989RefreshValues();990} else {991char errorMessage[512];992snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", win.GetExpression().c_str(), getExpressionError());993MessageBoxA(GetHandle(), errorMessage, "Error", MB_OK);994}995}996}997998void CtrlWatchList::DeleteWatch(int pos) {999watches_.erase(watches_.begin() + pos);1000Update();1001}10021003bool CtrlWatchList::HasWatchChanged(int pos) {1004return watches_[pos].lastValue != watches_[pos].currentValue;1005}100610071008