#include "Windows/resource.h" #include "Core/MemMap.h" #include "Windows/W32Util/ContextMenu.h" #include "Windows/W32Util/Misc.h" #include "Windows/W32Util/ShellUtil.h" #include "Windows/MainWindow.h" #include "Windows/InputBox.h" #include "Core/MIPS/MIPSAsm.h" #include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSTables.h" #include "Core/Config.h" #include "Core/Debugger/SymbolMap.h" #include "Core/Reporting.h" #include "Common/StringUtils.h" #include "Windows/Debugger/CtrlDisAsmView.h" #include "Windows/Debugger/DebuggerShared.h" #include "Windows/Debugger/BreakpointWindow.h" #include "Windows/Debugger/EditSymbolsWindow.h" #include "Core/RetroAchievements.h" #include "Windows/main.h" #include "Common/CommonWindows.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/System/Display.h" #include #include TCHAR CtrlDisAsmView::szClassName[] = _T("CtrlDisAsmView"); static constexpr UINT_PTR IDT_REDRAW = 0xC0DE0001; static constexpr UINT REDRAW_DELAY = 1000 / 60; void CtrlDisAsmView::init() { WNDCLASSEX wc; wc.cbSize = sizeof(wc); wc.lpszClassName = szClassName; wc.hInstance = GetModuleHandle(0); wc.lpfnWndProc = CtrlDisAsmView::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( CtrlDisAsmView * ); wc.hIconSm = 0; RegisterClassEx(&wc); } void CtrlDisAsmView::deinit() { //UnregisterClass(szClassName, hInst) } void CtrlDisAsmView::scanVisibleFunctions() { g_disassemblyManager.analyze(windowStart, g_disassemblyManager.getNthNextAddress(windowStart,visibleRows)-windowStart); } LRESULT CALLBACK CtrlDisAsmView::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { CtrlDisAsmView *ccp = CtrlDisAsmView::getFrom(hwnd); static bool lmbDown=false,rmbDown=false; switch(msg) { case WM_NCCREATE: // Allocate a new CustCtrl structure for this window. ccp = new CtrlDisAsmView(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: ccp->dontRedraw = false; if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) { ccp->scrollWindow(-3); } else if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) { ccp->scrollWindow(3); } 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_SYSKEYDOWN: ccp->onKeyDown(wParam,lParam); return 0; // return a value so that windows doesn't execute the standard syskey action case WM_KEYUP: ccp->onKeyUp(wParam,lParam); return 0; case WM_LBUTTONDOWN: lmbDown=true; ccp->onMouseDown(wParam,lParam,1); break; case WM_RBUTTONDOWN: 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; lmbDown = false; rmbDown = false; ccp->redraw(); break; case WM_GETDLGCODE: if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN) { switch (wParam) { case VK_TAB: return DLGC_WANTMESSAGE; default: return DLGC_WANTCHARS|DLGC_WANTARROWS; } } return DLGC_WANTCHARS|DLGC_WANTARROWS; case WM_TIMER: if (wParam == IDT_REDRAW) { InvalidateRect(hwnd, nullptr, FALSE); UpdateWindow(hwnd); ccp->redrawScheduled_ = false; KillTimer(hwnd, wParam); } break; default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); } CtrlDisAsmView *CtrlDisAsmView::getFrom(HWND hwnd) { return (CtrlDisAsmView *)GetWindowLongPtr(hwnd, GWLP_USERDATA); } CtrlDisAsmView::CtrlDisAsmView(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; charWidth = g_Config.iFontWidth * fontScale; rowHeight = (g_Config.iFontHeight + 2) * fontScale; int scaledFontHeight = g_Config.iFontHeight * fontScale; font = CreateFont(scaledFontHeight, charWidth, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, L"Lucida Console"); boldfont = CreateFont(scaledFontHeight, charWidth, 0, 0, FW_DEMIBOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, L"Lucida Console"); curAddress = 0; showHex = false; hasFocus = false; dontRedraw = false; keyTaken = false; matchAddress = -1; searching = false; searchQuery.clear(); windowStart = curAddress; whiteBackground = false; displaySymbols = true; calculatePixelPositions(); } CtrlDisAsmView::~CtrlDisAsmView() { DeleteObject(font); DeleteObject(boldfont); g_disassemblyManager.clear(); } static COLORREF scaleColor(COLORREF color, float factor) { unsigned char r = color & 0xFF; unsigned char g = (color >> 8) & 0xFF; unsigned char b = (color >> 16) & 0xFF; r = std::min(255, std::max((int)(r * factor), 0)); g = std::min(255, std::max((int)(g * factor), 0)); b = std::min(255, std::max((int)(b * factor), 0)); return (color & 0xFF000000) | (b << 16) | (g << 8) | r; } std::string trimString(std::string input) { size_t pos = input.find_first_not_of(" \t"); if (pos != 0 && pos != std::string::npos) { input = input.erase(0,pos); } pos = input.find_last_not_of(" \t"); if (pos != std::string::npos) { size_t size = input.length()-pos-1; input = input.erase(pos+1,size); } return input; } void CtrlDisAsmView::assembleOpcode(u32 address, const std::string &defaultText) { auto memLock = Memory::Lock(); if (!Core_IsStepping()) { MessageBox(wnd,L"Cannot change code while the core is running!",L"Error",MB_OK); return; } std::string op; bool result = InputBox_GetString(MainWindow::GetHInstance(), wnd, L"Assemble opcode", defaultText, op, InputBoxFlags::Default); if (!result) { return; } // check if it changes registers first auto separator = op.find('='); if (separator != std::string::npos) { std::string registerName = trimString(op.substr(0,separator)); std::string expression = trimString(op.substr(separator+1)); u32 value; if (parseExpression(expression.c_str(),debugger,value) == true) { for (int cat = 0; cat < debugger->GetNumCategories(); cat++) { for (int reg = 0; reg < debugger->GetNumRegsInCategory(cat); reg++) { if (strcasecmp(debugger->GetRegName(cat,reg).c_str(), registerName.c_str()) == 0) { debugger->SetRegValue(cat,reg,value); Reporting::NotifyDebugger(); SendMessage(GetParent(wnd),WM_DEB_UPDATE,0,0); return; } } } } // try to assemble the input if it failed } result = MIPSAsm::MipsAssembleOpcode(op.c_str(), debugger, address); Reporting::NotifyDebugger(); if (result == true) { scanVisibleFunctions(); if (address == curAddress) gotoAddr(g_disassemblyManager.getNthNextAddress(curAddress,1)); redraw(); } else { std::wstring error = ConvertUTF8ToWString(MIPSAsm::GetAssembleError()); MessageBox(wnd,error.c_str(),L"Error",MB_OK); } } void CtrlDisAsmView::drawBranchLine(HDC hdc, std::map &addressPositions, const BranchLine &line) { HPEN pen; u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); int topY; int bottomY; if (line.first < windowStart) { topY = -1; } else if (line.first >= windowEnd) { topY = rect.bottom+1; } else { topY = addressPositions[line.first] + rowHeight/2; } if (line.second < windowStart) { bottomY = -1; } else if (line.second >= windowEnd) { bottomY = rect.bottom+1; } else { bottomY = addressPositions[line.second] + rowHeight/2; } if ((topY < 0 && bottomY < 0) || (topY > rect.bottom && bottomY > rect.bottom)) { return; } // highlight line in a different color if it affects the currently selected opcode if (line.first == curAddress || line.second == curAddress) { pen = CreatePen(0,0,0x257AFA); } else { pen = CreatePen(0,0,0xFF3020); } HPEN oldPen = (HPEN) SelectObject(hdc,pen); int x = pixelPositions.arrowsStart+line.laneIndex*8; if (topY < 0) // first is not visible, but second is { MoveToEx(hdc,x-2,bottomY,0); LineTo(hdc,x+2,bottomY); LineTo(hdc,x+2,0); if (line.type == LINE_DOWN) { MoveToEx(hdc,x,bottomY-4,0); LineTo(hdc,x-4,bottomY); LineTo(hdc,x+1,bottomY+5); } } else if (bottomY > rect.bottom) // second is not visible, but first is { MoveToEx(hdc,x-2,topY,0); LineTo(hdc,x+2,topY); LineTo(hdc,x+2,rect.bottom); if (line.type == LINE_UP) { MoveToEx(hdc,x,topY-4,0); LineTo(hdc,x-4,topY); LineTo(hdc,x+1,topY+5); } } else { // both are visible if (line.type == LINE_UP) { MoveToEx(hdc,x-2,bottomY,0); LineTo(hdc,x+2,bottomY); LineTo(hdc,x+2,topY); LineTo(hdc,x-4,topY); MoveToEx(hdc,x,topY-4,0); LineTo(hdc,x-4,topY); LineTo(hdc,x+1,topY+5); } else { MoveToEx(hdc,x-2,topY,0); LineTo(hdc,x+2,topY); LineTo(hdc,x+2,bottomY); LineTo(hdc,x-4,bottomY); MoveToEx(hdc,x,bottomY-4,0); LineTo(hdc,x-4,bottomY); LineTo(hdc,x+1,bottomY+5); } } SelectObject(hdc,oldPen); DeleteObject(pen); } std::set CtrlDisAsmView::getSelectedLineArguments() { std::set args; DisassemblyLineInfo line; for (u32 addr = selectRangeStart; addr < selectRangeEnd; addr += 4) { g_disassemblyManager.getLine(addr, displaySymbols, line, debugger); size_t p = 0, nextp = line.params.find(','); while (nextp != line.params.npos) { args.emplace(line.params.substr(p, nextp - p)); p = nextp + 1; nextp = line.params.find(',', p); } if (p < line.params.size()) { args.emplace(line.params.substr(p)); } } return args; } void CtrlDisAsmView::drawArguments(HDC hdc, const DisassemblyLineInfo &line, int x, int y, int textColor, const std::set ¤tArguments) { if (line.params.empty()) { return; } // Don't highlight the selected lines. if (isInInterval(selectRangeStart, selectRangeEnd - selectRangeStart, line.info.opcodeAddress)) { TextOutA(hdc, x, y, line.params.c_str(), (int)line.params.size()); return; } int highlightedColor = 0xaabb00; if (textColor == 0x0000ff) { highlightedColor = 0xaabb77; } UINT prevAlign = SetTextAlign(hdc, TA_UPDATECP); MoveToEx(hdc, x, y, NULL); size_t p = 0, nextp = line.params.find(','); while (nextp != line.params.npos) { const std::string arg = line.params.substr(p, nextp - p); if (currentArguments.find(arg) != currentArguments.end() && textColor != 0xffffff) { SetTextColor(hdc, highlightedColor); } TextOutA(hdc, 0, 0, arg.c_str(), (int)arg.size()); SetTextColor(hdc,textColor); p = nextp + 1; nextp = line.params.find(',', p); TextOutA(hdc, 0, 0, ",", 1); } if (p < line.params.size()) { const std::string arg = line.params.substr(p); if (currentArguments.find(arg) != currentArguments.end() && textColor != 0xffffff) { SetTextColor(hdc, highlightedColor); } TextOutA(hdc, 0, 0, arg.c_str(), (int)arg.size()); SetTextColor(hdc,textColor); } SetTextAlign(hdc, prevAlign); } void CtrlDisAsmView::onPaint(WPARAM wParam, LPARAM lParam) { auto memLock = Memory::Lock(); if (!debugger->isAlive() || Achievements::HardcoreModeActive()) return; 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, TRANSPARENT); HPEN nullPen=CreatePen(0,0,0xffffff); HBRUSH nullBrush=CreateSolidBrush(0xffffff); HBRUSH currentBrush=CreateSolidBrush(0xffefe8); HPEN oldPen=(HPEN)SelectObject(hdc,nullPen); HBRUSH oldBrush=(HBRUSH)SelectObject(hdc,nullBrush); HFONT oldFont = (HFONT)SelectObject(hdc,(HGDIOBJ)font); HICON breakPoint = (HICON)LoadIcon(GetModuleHandle(0),(LPCWSTR)IDI_STOP); HICON breakPointDisable = (HICON)LoadIcon(GetModuleHandle(0),(LPCWSTR)IDI_STOPDISABLE); unsigned int address = windowStart; std::map addressPositions; const std::set currentArguments = getSelectedLineArguments(); DisassemblyLineInfo line; for (int i = 0; i < visibleRows; i++) { g_disassemblyManager.getLine(address,displaySymbols,line, debugger); int rowY1 = rowHeight*i; int rowY2 = rowHeight*(i+1); addressPositions[address] = rowY1; // draw background COLORREF backgroundColor = whiteBackground ? 0xFFFFFF : (debugger->getColor(address, false) & 0xFFFFFF); COLORREF textColor = 0x000000; if (isInInterval(address, line.totalSize, debugger->GetPC())) { backgroundColor = scaleColor(backgroundColor,1.05f); } if (address >= selectRangeStart && address < selectRangeEnd && searching == false) { if (hasFocus) { backgroundColor = address == curAddress ? 0xFF8822 : 0xFF9933; textColor = 0xFFFFFF; } else { backgroundColor = 0xC0C0C0; } } HBRUSH backgroundBrush = CreateSolidBrush(backgroundColor); HPEN backgroundPen = CreatePen(0,0,backgroundColor); SelectObject(hdc,backgroundBrush); SelectObject(hdc,backgroundPen); Rectangle(hdc,0,rowY1,rect.right,rowY1+rowHeight); SelectObject(hdc,currentBrush); SelectObject(hdc,nullPen); DeleteObject(backgroundBrush); DeleteObject(backgroundPen); // display address/symbol bool enabled; if (g_breakpoints.IsAddressBreakPoint(address,&enabled)) { if (enabled) textColor = 0x0000FF; int yOffset = std::max(-1, (rowHeight - 14 + 1) / 2); if (!enabled) yOffset++; DrawIconEx(hdc,2,rowY1+1+yOffset,enabled ? breakPoint : breakPointDisable,32,32,0,0,DI_NORMAL); } SetTextColor(hdc,textColor); char addressText[64]; GetDisasmAddressText(address,addressText,true,line.type == DISTYPE_OPCODE, displaySymbols); TextOutA(hdc,pixelPositions.addressStart,rowY1+2,addressText,(int)strlen(addressText)); if (isInInterval(address,line.totalSize,debugger->GetPC())) { TextOut(hdc,pixelPositions.opcodeStart-8,rowY1,L"\x25A0",1); } // display whether the condition of a branch is met if (line.info.isConditional && address == debugger->GetPC()) { line.params += line.info.conditionMet ? " ; true" : " ; false"; } drawArguments(hdc, line, pixelPositions.argumentsStart, rowY1 + 2, textColor, currentArguments); SelectObject(hdc,boldfont); TextOutA(hdc,pixelPositions.opcodeStart,rowY1+2,line.name.c_str(),(int)line.name.size()); SelectObject(hdc,font); address += line.totalSize; } std::vector branchLines = g_disassemblyManager.getBranchLines(windowStart,address-windowStart); for (size_t i = 0; i < branchLines.size(); i++) { drawBranchLine(hdc,addressPositions,branchLines[i]); } 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(nullPen); DeleteObject(nullBrush); DeleteObject(currentBrush); DestroyIcon(breakPoint); DestroyIcon(breakPointDisable); EndPaint(wnd, &ps); } void CtrlDisAsmView::onVScroll(WPARAM wParam, LPARAM lParam) { switch (wParam & 0xFFFF) { case SB_LINEDOWN: windowStart = g_disassemblyManager.getNthNextAddress(windowStart,1); break; case SB_LINEUP: windowStart = g_disassemblyManager.getNthPreviousAddress(windowStart,1); break; case SB_PAGEDOWN: windowStart = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); break; case SB_PAGEUP: windowStart = g_disassemblyManager.getNthPreviousAddress(windowStart,visibleRows); break; default: return; } scanVisibleFunctions(); redraw(); } void CtrlDisAsmView::followBranch() { DisassemblyLineInfo line; g_disassemblyManager.getLine(curAddress, true, line, debugger); if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO) { if (line.info.isBranch) { jumpStack.push_back(curAddress); gotoAddr(line.info.branchTarget); } else if (line.info.hasRelevantAddress) { // well, not exactly a branch, but we can do something anyway SendMessage(GetParent(wnd),WM_DEB_GOTOHEXEDIT,line.info.relevantAddress,0); SetFocus(wnd); } } else if (line.type == DISTYPE_DATA) { // jump to the start of the current line SendMessage(GetParent(wnd),WM_DEB_GOTOHEXEDIT,curAddress,0); SetFocus(wnd); } } void CtrlDisAsmView::onChar(WPARAM wParam, LPARAM lParam) { if (Achievements::HardcoreModeActive()) return; if (keyTaken) return; char str[2]; str[0] = wParam; str[1] = 0; assembleOpcode(curAddress,str); } void CtrlDisAsmView::editBreakpoint() { BreakpointWindow win(wnd,debugger); bool exists = false; if (g_breakpoints.IsAddressBreakPoint(curAddress)) { auto breakpoints = g_breakpoints.GetBreakpoints(); for (size_t i = 0; i < breakpoints.size(); i++) { if (breakpoints[i].addr == curAddress) { win.loadFromBreakpoint(breakpoints[i]); exists = true; break; } } } if (!exists) win.initBreakpoint(curAddress); if (win.exec()) { if (exists) g_breakpoints.RemoveBreakPoint(curAddress); win.addBreakpoint(); } } void CtrlDisAsmView::onKeyDown(WPARAM wParam, LPARAM lParam) { if (Achievements::HardcoreModeActive()) return; dontRedraw = false; u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); keyTaken = true; if (KeyDownAsync(VK_CONTROL)) { switch (tolower(wParam & 0xFFFF)) { case 'f': case 's': search(false); break; case 'c': case VK_INSERT: CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::DISASM); break; case 'x': disassembleToFile(); break; case 'a': assembleOpcode(curAddress,""); break; case 'g': { u32 addr; if (executeExpressionWindow(wnd,debugger,addr) == false) return; gotoAddr(addr); } break; case 'e': // edit breakpoint editBreakpoint(); break; case 'd': // toogle breakpoint enabled toggleBreakpoint(true); break; case VK_UP: scrollWindow(-1); scanVisibleFunctions(); break; case VK_DOWN: scrollWindow(1); scanVisibleFunctions(); break; case VK_NEXT: setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowEnd,1),KeyDownAsync(VK_SHIFT)); break; case VK_PRIOR: setCurAddress(windowStart,KeyDownAsync(VK_SHIFT)); break; } } else { switch (wParam & 0xFFFF) { case VK_DOWN: setCurAddress(g_disassemblyManager.getNthNextAddress(curAddress,1), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); break; case VK_UP: setCurAddress(g_disassemblyManager.getNthPreviousAddress(curAddress,1), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); break; case VK_NEXT: if (g_disassemblyManager.getNthNextAddress(curAddress,1) != windowEnd && curAddressIsVisible()) { setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowEnd,1), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } else { setCurAddress(g_disassemblyManager.getNthNextAddress(windowEnd,visibleRows-1), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } break; case VK_PRIOR: if (curAddress != windowStart && curAddressIsVisible()) { setCurAddress(windowStart, KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } else { setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowStart,visibleRows), KeyDownAsync(VK_SHIFT)); scrollAddressIntoView(); } break; case VK_LEFT: if (jumpStack.empty()) { gotoPC(); } else { u32 addr = jumpStack[jumpStack.size()-1]; jumpStack.pop_back(); gotoAddr(addr); } return; case VK_RIGHT: followBranch(); return; case VK_TAB: displaySymbols = !displaySymbols; break; case VK_SPACE: debugger->toggleBreakpoint(curAddress); break; case VK_F3: search(true); break; default: keyTaken = false; return; } } redraw(); } void CtrlDisAsmView::onKeyUp(WPARAM wParam, LPARAM lParam) { } void CtrlDisAsmView::scrollAddressIntoView() { u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); if (curAddress < windowStart) windowStart = curAddress; else if (curAddress >= windowEnd) windowStart = g_disassemblyManager.getNthPreviousAddress(curAddress,visibleRows-1); scanVisibleFunctions(); } bool CtrlDisAsmView::curAddressIsVisible() { u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); return curAddress >= windowStart && curAddress < windowEnd; } void CtrlDisAsmView::redraw() { if (dontRedraw == true) return; GetClientRect(wnd, &rect); visibleRows = rect.bottom/rowHeight; if (!redrawScheduled_) { SetTimer(wnd, IDT_REDRAW, REDRAW_DELAY, nullptr); redrawScheduled_ = true; } } void CtrlDisAsmView::toggleBreakpoint(bool toggleEnabled) { bool enabled; if (g_breakpoints.IsAddressBreakPoint(curAddress, &enabled)) { if (!enabled) { // enable disabled breakpoints g_breakpoints.ChangeBreakPoint(curAddress, true); } else if (!toggleEnabled && g_breakpoints.GetBreakPointCondition(curAddress) != nullptr) { // don't just delete a breakpoint with a custom condition int ret = MessageBox(wnd,L"This breakpoint has a custom condition.\nDo you want to remove it?",L"Confirmation",MB_YESNO); if (ret == IDYES) g_breakpoints.RemoveBreakPoint(curAddress); } else if (toggleEnabled) { // disable breakpoint g_breakpoints.ChangeBreakPoint(curAddress, false); } else { // otherwise just remove breakpoint g_breakpoints.RemoveBreakPoint(curAddress); } } else { g_breakpoints.AddBreakPoint(curAddress); } } void CtrlDisAsmView::onMouseDown(WPARAM wParam, LPARAM lParam, int button) { if (Achievements::HardcoreModeActive()) return; dontRedraw = false; int y = HIWORD(lParam); u32 newAddress = yToAddress(y); bool extend = KeyDownAsync(VK_SHIFT); if (button == 1) { if (newAddress == curAddress && hasFocus) { toggleBreakpoint(); } } else if (button == 2) { // Maintain the current selection if right clicking into it. if (newAddress >= selectRangeStart && newAddress < selectRangeEnd) extend = true; } setCurAddress(newAddress, extend); SetFocus(wnd); redraw(); } void CtrlDisAsmView::CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructionsMode mode) { _assert_msg_((startAddr & 3) == 0, "readMemory() can't handle unaligned reads"); if (mode != CopyInstructionsMode::DISASM) { int instructionSize = debugger->getInstructionSize(0); int count = (endAddr - startAddr) / instructionSize; int space = count * 32; char *temp = new char[space]; char *p = temp, *end = temp + space; for (u32 pos = startAddr; pos < endAddr && p < end; pos += instructionSize) { u32 data = mode == CopyInstructionsMode::OPCODES ? debugger->readMemory(pos) : pos; p += snprintf(p, end - p, "%08X", data); // Don't leave a trailing newline. if (pos + instructionSize < endAddr && p < end) p += snprintf(p, end - p, "\r\n"); } W32Util::CopyTextToClipboard(wnd, temp); delete [] temp; } else { std::string disassembly = DisassembleRange(startAddr,endAddr-startAddr, displaySymbols, debugger); W32Util::CopyTextToClipboard(wnd, disassembly.c_str()); } } void CtrlDisAsmView::NopInstructions(u32 selectRangeStart, u32 selectRangeEnd) { for (u32 addr = selectRangeStart; addr < selectRangeEnd; addr += 4) { Memory::Write_U32(0, addr); } if (currentMIPS) { currentMIPS->InvalidateICache(selectRangeStart, selectRangeEnd - selectRangeStart); } } void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) { if (Achievements::HardcoreModeActive()) return; if (button == 1) { int y = HIWORD(lParam); setCurAddress(yToAddress(y), KeyDownAsync(VK_SHIFT)); redraw(); } else if (button == 2) { // We don't want to let the users play with deallocated or uninitialized debugging objects GlobalUIState state = GetUIState(); if (state != UISTATE_INGAME && state != UISTATE_PAUSEMENU) { return; } switch (TriggerContextMenu(ContextMenuID::DISASM, wnd, ContextPoint::FromEvent(lParam))) { case ID_DISASM_GOTOINMEMORYVIEW: SendMessage(GetParent(wnd),WM_DEB_GOTOHEXEDIT,curAddress,0); break; case ID_DISASM_TOGGLEBREAKPOINT: toggleBreakpoint(); redraw(); break; case ID_DISASM_ASSEMBLE: assembleOpcode(curAddress,""); break; case ID_DISASM_COPYINSTRUCTIONDISASM: CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::DISASM); break; case ID_DISASM_COPYADDRESS: CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::ADDRESSES); break; case ID_DISASM_COPYINSTRUCTIONHEX: CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::OPCODES); break; case ID_DISASM_NOPINSTRUCTION: NopInstructions(selectRangeStart, selectRangeEnd); redraw(); break; case ID_DISASM_EDITSYMBOLS: { EditSymbolsWindow esw(wnd, debugger); if (esw.exec()) { esw.eval(); SendMessage(GetParent(wnd), WM_DEB_MAPLOADED, 0, 0); redraw(); } } break; case ID_DISASM_SETPCTOHERE: debugger->SetPC(curAddress); redraw(); break; case ID_DISASM_FOLLOWBRANCH: followBranch(); break; case ID_DISASM_RUNTOHERE: { SendMessage(GetParent(wnd), WM_COMMAND, ID_DEBUG_RUNTOLINE, 0); redraw(); } break; case ID_DISASM_RENAMEFUNCTION: { u32 funcBegin = g_symbolMap->GetFunctionStart(curAddress); if (funcBegin != -1) { char name[256]; std::string newname; truncate_cpy(name, g_symbolMap->GetLabelString(funcBegin).c_str()); if (InputBox_GetString(MainWindow::GetHInstance(), MainWindow::GetHWND(), L"New function name", name, newname)) { g_symbolMap->SetLabelName(newname.c_str(), funcBegin); u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin); MIPSAnalyst::RegisterFunction(funcBegin, funcSize, newname.c_str()); MIPSAnalyst::UpdateHashMap(); MIPSAnalyst::ApplyHashMap(); SendMessage(GetParent(wnd),WM_DEB_MAPLOADED,0,0); redraw(); } } else { MessageBox(MainWindow::GetHWND(), L"No symbol selected",0,0); } } break; case ID_DISASM_REMOVEFUNCTION: { char statusBarTextBuff[256]; u32 funcBegin = g_symbolMap->GetFunctionStart(curAddress); if (funcBegin != -1) { u32 prevBegin = g_symbolMap->GetFunctionStart(funcBegin-1); if (prevBegin != -1) { u32 expandedSize = g_symbolMap->GetFunctionSize(prevBegin) + g_symbolMap->GetFunctionSize(funcBegin); g_symbolMap->SetFunctionSize(prevBegin,expandedSize); } g_symbolMap->RemoveFunction(funcBegin,true); g_symbolMap->SortSymbols(); g_disassemblyManager.clear(); SendMessage(GetParent(wnd), WM_DEB_MAPLOADED, 0, 0); } else { snprintf(statusBarTextBuff,256, "WARNING: unable to find function symbol here"); SendMessage(GetParent(wnd), WM_DEB_SETSTATUSBARTEXT, 0, (LPARAM) statusBarTextBuff); } redraw(); } break; case ID_DISASM_ADDFUNCTION: { char statusBarTextBuff[256]; u32 prevBegin = g_symbolMap->GetFunctionStart(curAddress); if (prevBegin != -1) { if (prevBegin == curAddress) { snprintf(statusBarTextBuff,256, "WARNING: There's already a function entry point at this adress"); SendMessage(GetParent(wnd), WM_DEB_SETSTATUSBARTEXT, 0, (LPARAM) statusBarTextBuff); } else { char symname[128]; u32 prevSize = g_symbolMap->GetFunctionSize(prevBegin); u32 newSize = curAddress-prevBegin; g_symbolMap->SetFunctionSize(prevBegin,newSize); newSize = prevSize-newSize; snprintf(symname,128,"u_un_%08X",curAddress); g_symbolMap->AddFunction(symname,curAddress,newSize); g_symbolMap->SortSymbols(); g_disassemblyManager.clear(); SendMessage(GetParent(wnd), WM_DEB_MAPLOADED, 0, 0); } } else { char symname[128]; int newSize = selectRangeEnd - selectRangeStart; snprintf(symname, 128, "u_un_%08X", selectRangeStart); g_symbolMap->AddFunction(symname, selectRangeStart, newSize); g_symbolMap->SortSymbols(); SendMessage(GetParent(wnd), WM_DEB_MAPLOADED, 0, 0); } redraw(); } break; case ID_DISASM_DISASSEMBLETOFILE: disassembleToFile(); break; } return; } redraw(); } void CtrlDisAsmView::onMouseMove(WPARAM wParam, LPARAM lParam, int button) { if (Achievements::HardcoreModeActive()) return; if ((button & 1) != 0) { int y = HIWORD(lParam); setCurAddress(yToAddress(y), KeyDownAsync(VK_SHIFT)); redraw(); } } void CtrlDisAsmView::updateStatusBarText() { auto memLock = Memory::Lock(); if (!PSP_IsInited()) return; char text[512]; DisassemblyLineInfo line; g_disassemblyManager.getLine(curAddress,true,line, debugger); text[0] = 0; if (line.type == DISTYPE_OPCODE || line.type == DISTYPE_MACRO) { if (line.info.hasRelevantAddress && IsLikelyStringAt(line.info.relevantAddress)) { snprintf(text, sizeof(text), "[%08X] = \"%s\"", line.info.relevantAddress, Memory::GetCharPointer(line.info.relevantAddress)); } if (line.info.isDataAccess) { if (!Memory::IsValidAddress(line.info.dataAddress)) { snprintf(text, sizeof(text), "Invalid address %08X",line.info.dataAddress); } else { bool isFloat = MIPSGetInfo(line.info.encodedOpcode) & (IS_FPU | IS_VFPU); switch (line.info.dataSize) { case 1: snprintf(text, sizeof(text), "[%08X] = %02X",line.info.dataAddress,Memory::Read_U8(line.info.dataAddress)); break; case 2: snprintf(text, sizeof(text), "[%08X] = %04X",line.info.dataAddress,Memory::Read_U16(line.info.dataAddress)); break; case 4: { u32 dataInt = Memory::Read_U32(line.info.dataAddress); u32 dataFloat = Memory::Read_Float(line.info.dataAddress); std::string dataString; if (isFloat) dataString = StringFromFormat("%08X / %f", dataInt, dataFloat); else dataString = StringFromFormat("%08X", dataInt); const std::string addressSymbol = g_symbolMap->GetLabelString(dataInt); if (!addressSymbol.empty()) { snprintf(text, sizeof(text), "[%08X] = %s (%s)", line.info.dataAddress, addressSymbol.c_str(), dataString.c_str()); } else { snprintf(text, sizeof(text), "[%08X] = %s", line.info.dataAddress, dataString.c_str()); } break; } case 16: { uint32_t dataInt[4]; float dataFloat[4]; for (int i = 0; i < 4; ++i) { dataInt[i] = Memory::Read_U32(line.info.dataAddress + i * 4); dataFloat[i] = Memory::Read_Float(line.info.dataAddress + i * 4); } std::string dataIntString = StringFromFormat("%08X,%08X,%08X,%08X", dataInt[0], dataInt[1], dataInt[2], dataInt[3]); std::string dataFloatString = StringFromFormat("%f,%f,%f,%f", dataFloat[0], dataFloat[1], dataFloat[2], dataFloat[3]); snprintf(text, sizeof(text), "[%08X] = %s / %s", line.info.dataAddress, dataIntString.c_str(), dataFloatString.c_str()); break; } } } } if (line.info.isBranch) { const std::string addressSymbol = g_symbolMap->GetLabelString(line.info.branchTarget); if (addressSymbol.empty()) { snprintf(text, sizeof(text), "%08X", line.info.branchTarget); } else { snprintf(text, sizeof(text), "%08X = %s",line.info.branchTarget,addressSymbol.c_str()); } } } else if (line.type == DISTYPE_DATA) { u32 start = g_symbolMap->GetDataStart(curAddress); if (start == -1) start = curAddress; u32 diff = curAddress-start; const std::string label = g_symbolMap->GetLabelString(start); if (!label.empty()) { if (diff != 0) snprintf(text, sizeof(text), "%08X (%s) + %08X",start,label.c_str(),diff); else snprintf(text, sizeof(text), "%08X (%s)",start,label.c_str()); } else { if (diff != 0) snprintf(text, sizeof(text), "%08X + %08X",start,diff); else snprintf(text, sizeof(text), "%08X",start); } } SendMessage(GetParent(wnd),WM_DEB_SETSTATUSBARTEXT,0,(LPARAM)text); const std::string label = g_symbolMap->GetLabelString(line.info.opcodeAddress); if (!label.empty()) { SendMessage(GetParent(wnd),WM_DEB_SETSTATUSBARTEXT,1,(LPARAM)label.c_str()); } } u32 CtrlDisAsmView::yToAddress(int y) { int line = y/rowHeight; return g_disassemblyManager.getNthNextAddress(windowStart,line); } void CtrlDisAsmView::calculatePixelPositions() { pixelPositions.addressStart = 16; pixelPositions.opcodeStart = pixelPositions.addressStart + 18*charWidth; pixelPositions.argumentsStart = pixelPositions.opcodeStart + 9*charWidth; pixelPositions.arrowsStart = pixelPositions.argumentsStart + 30*charWidth; } void CtrlDisAsmView::search(bool continueSearch) { auto memLock = Memory::Lock(); u32 searchAddress; if (continueSearch == false || searchQuery[0] == 0) { if (InputBox_GetString(MainWindow::GetHInstance(), MainWindow::GetHWND(), L"Search for:", searchQuery, searchQuery) == false || searchQuery[0] == 0) { SetFocus(wnd); return; } for (size_t i = 0; i < searchQuery.size(); i++) { searchQuery[i] = tolower(searchQuery[i]); } SetFocus(wnd); searchAddress = g_disassemblyManager.getNthNextAddress(curAddress,1); } else { searchAddress = g_disassemblyManager.getNthNextAddress(matchAddress,1); } // limit address to sensible ranges if (searchAddress < 0x04000000) searchAddress = 0x04000000; if (searchAddress >= 0x04200000 && searchAddress < 0x08000000) searchAddress = 0x08000000; if (searchAddress >= 0x0A000000) { MessageBox(wnd,L"Not found",L"Search",MB_OK); return; } searching = true; redraw(); // so the cursor is disabled DisassemblyLineInfo lineInfo; while (searchAddress < 0x0A000000) { g_disassemblyManager.getLine(searchAddress,displaySymbols,lineInfo, debugger); char addressText[64]; GetDisasmAddressText(searchAddress,addressText,true,lineInfo.type == DISTYPE_OPCODE, displaySymbols); const char* opcode = lineInfo.name.c_str(); const char* arguments = lineInfo.params.c_str(); char merged[512]; int mergePos = 0; // I'm doing it manually to convert everything to lowercase at the same time for (int i = 0; addressText[i] != 0; i++) merged[mergePos++] = tolower(addressText[i]); merged[mergePos++] = ' '; for (int i = 0; opcode[i] != 0; i++) merged[mergePos++] = tolower(opcode[i]); merged[mergePos++] = ' '; for (int i = 0; arguments[i] != 0; i++) merged[mergePos++] = tolower(arguments[i]); merged[mergePos] = 0; // match! if (strstr(merged, searchQuery.c_str()) != NULL) { matchAddress = searchAddress; searching = false; gotoAddr(searchAddress); return; } // cancel search if ((searchAddress % 256) == 0 && KeyDownAsync(VK_ESCAPE)) { searching = false; return; } searchAddress = g_disassemblyManager.getNthNextAddress(searchAddress,1); if (searchAddress >= 0x04200000 && searchAddress < 0x08000000) searchAddress = 0x08000000; } MessageBox(wnd,L"Not found",L"Search",MB_OK); searching = false; } void CtrlDisAsmView::disassembleToFile() { // get size u32 size; if (executeExpressionWindow(wnd,debugger,size) == false) return; if (size == 0 || size > 10*1024*1024) { MessageBox(wnd,L"Invalid size!",L"Error",MB_OK); return; } std::string filename; if (W32Util::BrowseForFileName(false, nullptr, L"Save Disassembly As...", nullptr, L"All Files\0*.*\0\0", nullptr, filename)) { std::wstring fileName = ConvertUTF8ToWString(filename); FILE *output = _wfopen(fileName.c_str(), L"wb"); if (output == nullptr) { MessageBox(wnd, L"Could not open file!", L"Error", MB_OK); return; } std::string disassembly = DisassembleRange(curAddress, size, displaySymbols, debugger); fprintf(output, "%s", disassembly.c_str()); fclose(output); MessageBox(wnd, L"Finished!", L"Done", MB_OK); } } void CtrlDisAsmView::getOpcodeText(u32 address, char* dest, int bufsize) { DisassemblyLineInfo line; address = g_disassemblyManager.getStartAddress(address); g_disassemblyManager.getLine(address,displaySymbols,line, debugger); snprintf(dest, bufsize, "%s %s",line.name.c_str(),line.params.c_str()); } void CtrlDisAsmView::scrollStepping(u32 newPc) { u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows); newPc = g_disassemblyManager.getStartAddress(newPc); if (newPc >= windowEnd || newPc >= g_disassemblyManager.getNthPreviousAddress(windowEnd,1)) { windowStart = g_disassemblyManager.getNthPreviousAddress(newPc,visibleRows-2); } } u32 CtrlDisAsmView::getInstructionSizeAt(u32 address) { u32 start = g_disassemblyManager.getStartAddress(address); u32 next = g_disassemblyManager.getNthNextAddress(start,1); return next - address; }