diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.cpp b/pcsx2-qt/Debugger/DisassemblyWidget.cpp index cc95e32391..22fb035335 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.cpp +++ b/pcsx2-qt/Debugger/DisassemblyWidget.cpp @@ -37,56 +37,11 @@ DisassemblyWidget::DisassemblyWidget(QWidget* parent) { ui.setupUi(this); - CreateCustomContextMenu(); connect(this, &DisassemblyWidget::customContextMenuRequested, this, &DisassemblyWidget::customMenuRequested); } DisassemblyWidget::~DisassemblyWidget() = default; -void DisassemblyWidget::CreateCustomContextMenu() -{ - if (m_contextMenu) - return; // ??? - m_contextMenu = new QMenu(this); - - QAction* action = 0; - m_contextMenu->addAction(action = new QAction(tr("Copy Address"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyAddress); - m_contextMenu->addAction(action = new QAction(tr("Copy Instruction Hex"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionHex); - m_contextMenu->addAction(action = new QAction(tr("Copy Instruction Text"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionText); - // TODO: Disassemble to file. Do people use that? - m_contextMenu->addSeparator(); - m_contextMenu->addAction(action = new QAction(tr("Assemble new Instruction(s)"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAssembleInstruction); - m_contextMenu->addAction(action = new QAction(tr("NOP Instruction(s)"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextNoopInstruction); - m_contextMenu->addSeparator(); - m_contextMenu->addAction(action = new QAction(tr("Run to Cursor"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRunToCursor); - m_contextMenu->addAction(action = new QAction(tr("Jump to Cursor"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextJumpToCursor); - m_contextMenu->addAction(action = new QAction(tr("Toggle Breakpoint"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextToggleBreakpoint); - m_contextMenu->addAction(action = new QAction(tr("Follow Branch"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextFollowBranch); - m_contextMenu->addSeparator(); - m_contextMenu->addAction(action = new QAction(tr("Go to Address"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextGoToAddress); - m_contextMenu->addAction(action = new QAction(tr("Go to in Memory View"), this)); - connect(action, &QAction::triggered, this, [this]() { gotoInMemory(m_selectedAddressStart); }); - m_contextMenu->addSeparator(); - m_contextMenu->addAction(action = new QAction(tr("Add Function"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAddFunction); - m_contextMenu->addAction(action = new QAction(tr("Rename Function"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRenameFunction); - m_contextMenu->addAction(action = new QAction(tr("Remove Function"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRemoveFunction); - m_contextMenu->addAction(action = new QAction(tr("Stub (NOP) Function"), this)); - connect(action, &QAction::triggered, this, &DisassemblyWidget::contextStubFunction); -} - void DisassemblyWidget::contextCopyAddress() { QGuiApplication::clipboard()->setText(FetchSelectionInfo(SelectionInfo::ADDRESS)); @@ -133,6 +88,7 @@ void DisassemblyWidget::contextAssembleInstruction() Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = m_cpu, val = encodedInstruction] { for (u32 i = start; i <= end; i += 4) { + this->m_nopedInstructions.insert({i, cpu->read32(i)}); cpu->write32(i, val); } QtHost::RunOnUIThread([this] { VMUpdate(); }); @@ -145,12 +101,28 @@ void DisassemblyWidget::contextNoopInstruction() Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = m_cpu] { for (u32 i = start; i <= end; i += 4) { + this->m_nopedInstructions.insert({i, cpu->read32(i)}); cpu->write32(i, 0x00); } QtHost::RunOnUIThread([this] { VMUpdate(); }); }); } +void DisassemblyWidget::contextRestoreInstruction() +{ + Host::RunOnCPUThread([this, start = m_selectedAddressStart, end = m_selectedAddressEnd, cpu = m_cpu] { + for (u32 i = start; i <= end; i += 4) + { + if (this->m_nopedInstructions.find(i) != this->m_nopedInstructions.end()) + { + cpu->write32(i, this->m_nopedInstructions[i]); + this->m_nopedInstructions.erase(i); + } + } + QtHost::RunOnUIThread([this] { VMUpdate(); }); + }); +} + void DisassemblyWidget::contextRunToCursor() { Host::RunOnCPUThread([&] { @@ -317,6 +289,7 @@ void DisassemblyWidget::contextStubFunction() if (curFuncAddress != SymbolMap::INVALID_ADDRESS) { Host::RunOnCPUThread([this, curFuncAddress, cpu = m_cpu] { + this->m_stubbedFunctions.insert({curFuncAddress, {cpu->read32(curFuncAddress), cpu->read32(curFuncAddress + 4)}}); cpu->write32(curFuncAddress, 0x03E00008); // jr $ra cpu->write32(curFuncAddress + 4, 0x00000000); // nop QtHost::RunOnUIThread([this] { VMUpdate(); }); @@ -328,6 +301,23 @@ void DisassemblyWidget::contextStubFunction() } } +void DisassemblyWidget::contextRestoreFunction() +{ + const u32 curFuncAddress = m_cpu->GetSymbolMap().GetFunctionStart(m_selectedAddressStart); + if (curFuncAddress != SymbolMap::INVALID_ADDRESS && m_stubbedFunctions.find(curFuncAddress) != m_stubbedFunctions.end()) + { + Host::RunOnCPUThread([this, curFuncAddress, cpu = m_cpu] { + cpu->write32(curFuncAddress, std::get<0>(this->m_stubbedFunctions[curFuncAddress])); + cpu->write32(curFuncAddress + 4, std::get<1>(this->m_stubbedFunctions[curFuncAddress])); + this->m_stubbedFunctions.erase(curFuncAddress); + QtHost::RunOnUIThread([this] { VMUpdate(); }); + }); + } + else + { + QMessageBox::warning(this, tr("Restore Function Error"), tr("No function / symbol is currently selected.")); + } +} void DisassemblyWidget::SetCpu(DebugInterface* cpu) { m_cpu = cpu; @@ -639,7 +629,58 @@ void DisassemblyWidget::customMenuRequested(QPoint pos) if (!m_cpu->isAlive()) return; - m_contextMenu->popup(this->mapToGlobal(pos)); + QMenu* contextMenu = new QMenu(this); + + QAction* action = 0; + contextMenu->addAction(action = new QAction(tr("Copy Address"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyAddress); + contextMenu->addAction(action = new QAction(tr("Copy Instruction Hex"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionHex); + contextMenu->addAction(action = new QAction(tr("Copy Instruction Text"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextCopyInstructionText); + contextMenu->addSeparator(); + if (AddressCanRestore(m_selectedAddressStart, m_selectedAddressEnd)) + { + contextMenu->addAction(action = new QAction(tr("Restore Instruction(s)"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreInstruction); + } + contextMenu->addAction(action = new QAction(tr("Assemble new Instruction(s)"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAssembleInstruction); + contextMenu->addAction(action = new QAction(tr("NOP Instruction(s)"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextNoopInstruction); + contextMenu->addSeparator(); + contextMenu->addAction(action = new QAction(tr("Run to Cursor"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRunToCursor); + contextMenu->addAction(action = new QAction(tr("Jump to Cursor"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextJumpToCursor); + contextMenu->addAction(action = new QAction(tr("Toggle Breakpoint"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextToggleBreakpoint); + contextMenu->addAction(action = new QAction(tr("Follow Branch"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextFollowBranch); + contextMenu->addSeparator(); + contextMenu->addAction(action = new QAction(tr("Go to Address"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextGoToAddress); + contextMenu->addAction(action = new QAction(tr("Go to in Memory View"), this)); + connect(action, &QAction::triggered, this, [this]() { gotoInMemory(m_selectedAddressStart); }); + contextMenu->addSeparator(); + contextMenu->addAction(action = new QAction(tr("Add Function"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextAddFunction); + contextMenu->addAction(action = new QAction(tr("Rename Function"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRenameFunction); + contextMenu->addAction(action = new QAction(tr("Remove Function"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRemoveFunction); + if (FunctionCanRestore(m_selectedAddressStart)) + { + contextMenu->addAction(action = new QAction(tr("Restore Function"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextRestoreFunction); + } + else + { + contextMenu->addAction(action = new QAction(tr("Stub (NOP) Function"), this)); + connect(action, &QAction::triggered, this, &DisassemblyWidget::contextStubFunction); + } + contextMenu->setAttribute(Qt::WA_DeleteOnClose); + contextMenu->popup(this->mapToGlobal(pos)); } inline QString DisassemblyWidget::DisassemblyStringFromAddress(u32 address, QFont font, u32 pc) @@ -756,3 +797,29 @@ void DisassemblyWidget::gotoAddress(u32 address) this->repaint(); this->setFocus(); } + +bool DisassemblyWidget::AddressCanRestore(u32 start, u32 end) +{ + for (u32 i = start; i <= end; i += 4) + { + if (this->m_nopedInstructions.find(i) != this->m_nopedInstructions.end()) + { + return true; + } + } + return false; +} + +bool DisassemblyWidget::FunctionCanRestore(u32 address) +{ + u32 funcStartAddress = m_cpu->GetSymbolMap().GetFunctionStart(address); + + if (funcStartAddress != SymbolMap::INVALID_ADDRESS) + { + if (m_stubbedFunctions.find(funcStartAddress) != this->m_stubbedFunctions.end()) + { + return true; + } + } + return false; +} diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.h b/pcsx2-qt/Debugger/DisassemblyWidget.h index 89714847fa..91efb1ecfb 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.h +++ b/pcsx2-qt/Debugger/DisassemblyWidget.h @@ -56,6 +56,7 @@ public slots: void contextCopyInstructionText(); void contextAssembleInstruction(); void contextNoopInstruction(); + void contextRestoreInstruction(); void contextRunToCursor(); void contextJumpToCursor(); void contextToggleBreakpoint(); @@ -65,6 +66,7 @@ public slots: void contextRenameFunction(); void contextRemoveFunction(); void contextStubFunction(); + void contextRestoreFunction(); void gotoAddress(u32 address); @@ -76,9 +78,6 @@ signals: private: Ui::DisassemblyWidget ui; - QMenu* m_contextMenu = nullptr; - void CreateCustomContextMenu(); - DebugInterface* m_cpu; u32 m_visibleStart = 0x00336318; // The address of the first opcode shown(row 0) u32 m_visibleRows; @@ -86,6 +85,9 @@ private: u32 m_selectedAddressEnd = 0; u32 m_rowHeight = 0; + std::map m_nopedInstructions; + std::map> m_stubbedFunctions; + DisassemblyManager m_disassemblyManager; inline QString DisassemblyStringFromAddress(u32 address, QFont font, u32 pc); @@ -97,4 +99,7 @@ private: INSTRUCTIONTEXT, }; QString FetchSelectionInfo(SelectionInfo selInfo); + + bool AddressCanRestore(u32 start, u32 end); + bool FunctionCanRestore(u32 address); };