diff --git a/Source/Core/Common/SymbolDB.cpp b/Source/Core/Common/SymbolDB.cpp index f9d352b415..bfdd9f57db 100644 --- a/Source/Core/Common/SymbolDB.cpp +++ b/Source/Core/Common/SymbolDB.cpp @@ -51,6 +51,7 @@ void SymbolDB::Clear(const char* prefix) { // TODO: honor prefix m_functions.clear(); + m_notes.clear(); m_checksum_to_function.clear(); } diff --git a/Source/Core/Common/SymbolDB.h b/Source/Core/Common/SymbolDB.h index 255994b3c2..8930b95996 100644 --- a/Source/Core/Common/SymbolDB.h +++ b/Source/Core/Common/SymbolDB.h @@ -29,6 +29,16 @@ struct SCall u32 call_address; }; +struct Note +{ + std::string name; + u32 address = 0; + u32 size = 0; + int layer = 0; + + Note() = default; +}; + struct Symbol { enum class Type @@ -71,6 +81,7 @@ class SymbolDB { public: using XFuncMap = std::map; + using XNoteMap = std::map; using XFuncPtrMap = std::map>; SymbolDB(); @@ -86,6 +97,7 @@ public: std::vector GetSymbolsFromHash(u32 hash); const XFuncMap& Symbols() const { return m_functions; } + const XNoteMap& Notes() const { return m_notes; } XFuncMap& AccessSymbols() { return m_functions; } bool IsEmpty() const; void Clear(const char* prefix = ""); @@ -94,6 +106,7 @@ public: protected: XFuncMap m_functions; + XNoteMap m_notes; XFuncPtrMap m_checksum_to_function; }; } // namespace Common diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp index 5351d1ec28..c837f1c041 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp @@ -91,6 +91,46 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd } } +void PPCSymbolDB::AddKnownNote(u32 start_addr, u32 size, const std::string& name) +{ + auto iter = m_notes.find(start_addr); + + if (iter != m_notes.end()) + { + // Already got it, just update the name and size. + Common::Note* tempfunc = &iter->second; + tempfunc->name = name; + tempfunc->size = size; + } + else + { + Common::Note tf; + tf.name = name; + tf.address = start_addr; + tf.size = size; + + m_notes[start_addr] = tf; + } +} + +void PPCSymbolDB::DetermineNoteLayers() +{ + if (m_notes.empty()) + return; + + for (auto& note : m_notes) + note.second.layer = 0; + + for (auto iter = m_notes.begin(); iter != m_notes.end(); ++iter) + { + const u32 range = iter->second.address + iter->second.size; + auto search = m_notes.lower_bound(range); + + while (--search != iter) + search->second.layer += 1; + } +} + Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) { auto it = m_functions.lower_bound(addr); @@ -112,6 +152,46 @@ Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) return nullptr; } +Common::Note* PPCSymbolDB::GetNoteFromAddr(u32 addr) +{ + if (m_notes.empty()) + return nullptr; + + auto itn = m_notes.lower_bound(addr); + + // If the address is exactly the start address of a symbol, we're done. + if (itn != m_notes.end() && itn->second.address == addr) + return &itn->second; + + // Otherwise, check whether the address is within the bounds of a symbol. + if (itn == m_notes.begin()) + return nullptr; + + do + { + --itn; + + // If itn's range reaches the address. + if (addr < itn->second.address + itn->second.size) + return &itn->second; + + // If layer is 0, it's the last note that could possibly reach the address, as there are no more + // underlying notes. + } while (itn != m_notes.begin() && itn->second.layer != 0); + + return nullptr; +} + +void PPCSymbolDB::DeleteFunction(u32 start_address) +{ + m_functions.erase(start_address); +} + +void PPCSymbolDB::DeleteNote(u32 start_address) +{ + m_notes.erase(start_address); +} + std::string_view PPCSymbolDB::GetDescription(u32 addr) { if (const Common::Symbol* const symbol = GetSymbolFromAddr(addr)) @@ -406,6 +486,7 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string& if (strlen(name) > 0) { bool good; + // Notes will be treated the same as Data. const Common::Symbol::Type type = section_name == ".text" || section_name == ".init" ? Common::Symbol::Type::Function : Common::Symbol::Type::Data; @@ -438,7 +519,11 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string& if (good) { ++good_count; - AddKnownSymbol(guard, vaddress, size, name_string, object_filename_string, type); + + if (section_name == ".note") + AddKnownNote(vaddress, size, name); + else + AddKnownSymbol(guard, vaddress, size, name_string, object_filename_string, type); } else { @@ -448,6 +533,7 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string& } Index(); + DetermineNoteLayers(); NOTICE_LOG_FMT(SYMBOLS, "{} symbols loaded, {} symbols ignored.", good_count, bad_count); return true; } @@ -495,6 +581,17 @@ bool PPCSymbolDB::SaveSymbolMap(const std::string& filename) const file.WriteString(line); } + // Write .note section + auto note_symbols = m_notes | std::views::transform([](auto f) { return f.second; }); + file.WriteString("\n.note section layout\n"); + for (const auto& symbol : note_symbols) + { + // Write symbol address, size, virtual address, alignment, name + const std::string line = fmt::format("{:08x} {:06x} {:08x} {} {}\n", symbol.address, + symbol.size, symbol.address, 0, symbol.name); + file.WriteString(line); + } + return true; } diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.h b/Source/Core/Core/PowerPC/PPCSymbolDB.h index de3e210327..b96ad2f42f 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.h +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.h @@ -25,13 +25,18 @@ public: void AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAddr, u32 size, const std::string& name, const std::string& object_name, Common::Symbol::Type type = Common::Symbol::Type::Function); + void AddKnownNote(u32 start_addr, u32 size, const std::string& name); Common::Symbol* GetSymbolFromAddr(u32 addr) override; + bool NoteExists() const { return !m_notes.empty(); } + Common::Note* GetNoteFromAddr(u32 addr); + void DetermineNoteLayers(); + void DeleteFunction(u32 start_address); + void DeleteNote(u32 start_address); std::string_view GetDescription(u32 addr); void FillInCallers(); - bool LoadMap(const Core::CPUThreadGuard& guard, const std::string& filename, bool bad = false); bool SaveSymbolMap(const std::string& filename) const; bool SaveCodeMap(const Core::CPUThreadGuard& guard, const std::string& filename) const; diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index a86b41cd68..f42b84925d 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -221,6 +221,8 @@ add_executable(dolphin-emu Debugger/CodeViewWidget.h Debugger/CodeWidget.cpp Debugger/CodeWidget.h + Debugger/EditSymbolDialog.cpp + Debugger/EditSymbolDialog.h Debugger/GekkoSyntaxHighlight.cpp Debugger/GekkoSyntaxHighlight.h Debugger/JitBlockTableModel.cpp diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp index a88a80a945..e555054938 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp @@ -37,6 +37,7 @@ #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" #include "DolphinQt/Debugger/AssembleInstructionDialog.h" +#include "DolphinQt/Debugger/EditSymbolDialog.h" #include "DolphinQt/Debugger/PatchInstructionDialog.h" #include "DolphinQt/Host.h" #include "DolphinQt/QtUtils/FromStdString.h" @@ -136,8 +137,9 @@ constexpr int CODE_VIEW_COLUMN_ADDRESS = 1; constexpr int CODE_VIEW_COLUMN_INSTRUCTION = 2; constexpr int CODE_VIEW_COLUMN_PARAMETERS = 3; constexpr int CODE_VIEW_COLUMN_DESCRIPTION = 4; -constexpr int CODE_VIEW_COLUMN_BRANCH_ARROWS = 5; -constexpr int CODE_VIEW_COLUMNCOUNT = 6; +constexpr int CODE_VIEW_COLUMN_NOTE = 5; +constexpr int CODE_VIEW_COLUMN_BRANCH_ARROWS = 6; +constexpr int CODE_VIEW_COLUMNCOUNT = 7; CodeViewWidget::CodeViewWidget() : m_system(Core::System::GetInstance()), m_ppc_symbol_db(m_system.GetPPCSymbolDB()) @@ -160,6 +162,7 @@ CodeViewWidget::CodeViewWidget() setHorizontalHeaderItem(CODE_VIEW_COLUMN_INSTRUCTION, new QTableWidgetItem(tr("Instr."))); setHorizontalHeaderItem(CODE_VIEW_COLUMN_PARAMETERS, new QTableWidgetItem(tr("Parameters"))); setHorizontalHeaderItem(CODE_VIEW_COLUMN_DESCRIPTION, new QTableWidgetItem(tr("Symbols"))); + setHorizontalHeaderItem(CODE_VIEW_COLUMN_NOTE, new QTableWidgetItem(tr("Notes"))); setHorizontalHeaderItem(CODE_VIEW_COLUMN_BRANCH_ARROWS, new QTableWidgetItem(tr("Branches"))); setFont(Settings::Instance().GetDebugFont()); @@ -332,6 +335,11 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) std::string param = (split == std::string::npos ? "" : disas.substr(split + 1)); const std::string_view desc = debug_interface.GetDescription(addr); + const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(addr); + std::string note_string; + if (note != nullptr) + note_string = note->name; + // Adds whitespace and a minimum size to ins and param. Helps to prevent frequent resizing while // scrolling. const QString ins_formatted = @@ -343,9 +351,11 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) auto* ins_item = new QTableWidgetItem(ins_formatted); auto* param_item = new QTableWidgetItem(param_formatted); auto* description_item = new QTableWidgetItem(desc_formatted); + auto* note_item = new QTableWidgetItem(QString::fromStdString(note_string)); auto* branch_item = new QTableWidgetItem(); - for (auto* item : {bp_item, addr_item, ins_item, param_item, description_item, branch_item}) + for (auto* item : + {bp_item, addr_item, ins_item, param_item, description_item, note_item, branch_item}) { item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); item->setData(Qt::UserRole, addr); @@ -407,6 +417,7 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) setItem(i, CODE_VIEW_COLUMN_INSTRUCTION, ins_item); setItem(i, CODE_VIEW_COLUMN_PARAMETERS, param_item); setItem(i, CODE_VIEW_COLUMN_DESCRIPTION, description_item); + setItem(i, CODE_VIEW_COLUMN_NOTE, note_item); setItem(i, CODE_VIEW_COLUMN_BRANCH_ARROWS, branch_item); if (addr == GetAddress()) @@ -415,6 +426,9 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) } } + m_ppc_symbol_db.NoteExists() ? showColumn(CODE_VIEW_COLUMN_NOTE) : + hideColumn(CODE_VIEW_COLUMN_NOTE); + CalculateBranchIndentation(); m_ppc_symbol_db.FillInCallers(); @@ -565,8 +579,6 @@ void CodeViewWidget::OnContextMenu() const u32 addr = GetContextAddress(); - const bool has_symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); - auto* follow_branch_action = menu->addAction(tr("Follow &Branch"), this, &CodeViewWidget::OnFollowBranch); @@ -586,17 +598,17 @@ void CodeViewWidget::OnContextMenu() menu->addAction(tr("Copy Tar&get Address"), this, &CodeViewWidget::OnCopyTargetAddress); menu->addSeparator(); - auto* symbol_rename_action = - menu->addAction(tr("&Rename Symbol"), this, &CodeViewWidget::OnRenameSymbol); - auto* symbol_size_action = - menu->addAction(tr("Set Symbol &Size"), this, &CodeViewWidget::OnSetSymbolSize); - auto* symbol_end_action = - menu->addAction(tr("Set Symbol &End Address"), this, &CodeViewWidget::OnSetSymbolEndAddress); + auto* symbol_add_action = + menu->addAction(tr("&Add function symbol"), this, &CodeViewWidget::OnAddFunction); + auto* symbol_edit_action = + menu->addAction(tr("&Edit function symbol"), this, &CodeViewWidget::OnEditSymbol); + + auto* note_add_action = menu->addAction(tr("Add Note"), this, &CodeViewWidget::OnAddNote); + auto* note_edit_action = menu->addAction(tr("Edit Note"), this, &CodeViewWidget::OnEditNote); + menu->addSeparator(); auto* run_to_action = menu->addAction(tr("Run &to Here"), this, &CodeViewWidget::OnRunToHere); - auto* function_action = - menu->addAction(tr("&Add Function"), this, &CodeViewWidget::OnAddFunction); auto* ppc_action = menu->addAction(tr("PPC vs Host"), this, &CodeViewWidget::OnPPCComparison); auto* insert_blr_action = menu->addAction(tr("&Insert BLR"), this, &CodeViewWidget::OnInsertBLR); auto* insert_nop_action = menu->addAction(tr("Insert &NOP"), this, &CodeViewWidget::OnInsertNOP); @@ -645,20 +657,24 @@ void CodeViewWidget::OnContextMenu() follow_branch_action->setEnabled(follow_branch_enabled); for (auto* action : - {copy_address_action, copy_line_action, copy_hex_action, function_action, run_to_action, - ppc_action, insert_blr_action, insert_nop_action, replace_action, assemble_action}) + {copy_address_action, copy_line_action, copy_hex_action, symbol_add_action, + symbol_edit_action, note_add_action, note_edit_action, run_to_action, ppc_action, + insert_blr_action, insert_nop_action, replace_action, assemble_action}) { action->setEnabled(running); } - for (auto* action : {symbol_rename_action, symbol_size_action, symbol_end_action}) - action->setEnabled(has_symbol); - for (auto* action : {copy_target_memory, show_target_memory}) { action->setEnabled(valid_load_store); } + auto* note = m_ppc_symbol_db.GetNoteFromAddr(addr); + note_edit_action->setEnabled(note != nullptr); + // A note cannot be added ontop of the starting address of another note. + if (note != nullptr && note->address == addr) + note_add_action->setEnabled(false); + restore_action->setEnabled(running && m_system.GetPowerPC().GetDebugInterface().HasEnabledPatch(addr)); @@ -881,6 +897,13 @@ void CodeViewWidget::OnPPCComparison() void CodeViewWidget::OnAddFunction() { const u32 addr = GetContextAddress(); + const int confirm = + QMessageBox::warning(this, tr("Add Function Symbol"), + tr("Force new function symbol to be made at %1?").arg(addr, 0, 16), + QMessageBox::Ok | QMessageBox::Cancel); + + if (confirm != QMessageBox::Ok) + return; Core::CPUThreadGuard guard(m_system); @@ -917,25 +940,80 @@ void CodeViewWidget::OnFollowBranch() SetAddress(branch_addr, SetAddressUpdate::WithDetailedUpdate); } -void CodeViewWidget::OnRenameSymbol() +void CodeViewWidget::OnEditSymbol() { const u32 addr = GetContextAddress(); - Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); - if (!symbol) + if (symbol == nullptr) + { + OnAddFunction(); + return; + } + + std::string name = symbol->name; + u32 size = symbol->size; + const u32 symbol_address = symbol->address; + + EditSymbolDialog dialog(this, symbol_address, &size, &name); + + if (dialog.exec() != QDialog::Accepted) return; - bool good; - const QString name = - QInputDialog::getText(this, tr("Rename Symbol"), tr("Symbol Name:"), QLineEdit::Normal, - QString::fromStdString(symbol->name), &good, Qt::WindowCloseButtonHint); - - if (good && !name.isEmpty()) + if (dialog.DeleteRequested()) { - symbol->Rename(name.toStdString()); - emit Host::GetInstance()->PPCSymbolsChanged(); + OnDeleteSymbol(); + return; } + + if (symbol->name != name) + symbol->Rename(name); + + if (symbol->size != size) + { + Core::CPUThreadGuard guard(m_system); + PPCAnalyst::ReanalyzeFunction(guard, symbol->address, *symbol, size); + } + + emit Host::GetInstance()->PPCSymbolsChanged(); +} + +void CodeViewWidget::OnDeleteSymbol() +{ + const u32 addr = GetContextAddress(); + Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); + + if (symbol == nullptr) + return; + + const int confirm = QMessageBox::warning(this, tr("Delete Function Symbol"), + tr("Delete function symbol: %1\nat %2?") + .arg(QString::fromStdString(symbol->name)) + .arg(addr, 0, 16), + QMessageBox::Ok | QMessageBox::Cancel); + + if (confirm != QMessageBox::Ok) + return; + + m_ppc_symbol_db.DeleteFunction(symbol->address); + + emit Host::GetInstance()->PPCSymbolsChanged(); +} + +void CodeViewWidget::OnAddNote() +{ + const u32 note_address = GetContextAddress(); + std::string name = ""; + u32 size = 4; + + EditSymbolDialog dialog(this, note_address, &size, &name, EditSymbolDialog::Type::Note); + + if (dialog.exec() != QDialog::Accepted || dialog.DeleteRequested()) + return; + + m_ppc_symbol_db.AddKnownNote(note_address, size, name); + m_ppc_symbol_db.DetermineNoteLayers(); + emit Host::GetInstance()->PPCSymbolsChanged(); } void CodeViewWidget::OnSelectionChanged() @@ -951,53 +1029,57 @@ void CodeViewWidget::OnSelectionChanged() } } -void CodeViewWidget::OnSetSymbolSize() +void CodeViewWidget::OnEditNote() { - const u32 addr = GetContextAddress(); + const u32 context_address = GetContextAddress(); + Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address); - Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); - - if (!symbol) + if (note == nullptr) return; - bool good; - const int size = QInputDialog::getInt( - this, tr("Rename Symbol"), tr("Symbol Size (%1):").arg(QString::fromStdString(symbol->name)), - symbol->size, 1, 0xFFFF, 1, &good, Qt::WindowCloseButtonHint); + std::string name = note->name; + u32 size = note->size; + const u32 note_address = note->address; - if (!good) + EditSymbolDialog dialog(this, note_address, &size, &name, EditSymbolDialog::Type::Note); + + if (dialog.exec() != QDialog::Accepted) return; - Core::CPUThreadGuard guard(m_system); + if (dialog.DeleteRequested()) + { + OnDeleteNote(); + return; + } + + if (note->name != name || note->size != size) + { + m_ppc_symbol_db.AddKnownNote(note_address, size, name); + m_ppc_symbol_db.DetermineNoteLayers(); + } - PPCAnalyst::ReanalyzeFunction(guard, symbol->address, *symbol, size); emit Host::GetInstance()->PPCSymbolsChanged(); } -void CodeViewWidget::OnSetSymbolEndAddress() +void CodeViewWidget::OnDeleteNote() { - const u32 addr = GetContextAddress(); + const u32 context_address = GetContextAddress(); + Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address); - Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); - - if (!symbol) + if (note == nullptr) return; - bool good; - const QString name = QInputDialog::getText( - this, tr("Set Symbol End Address"), - tr("Symbol End Address (%1):").arg(QString::fromStdString(symbol->name)), QLineEdit::Normal, - QStringLiteral("%1").arg(addr + symbol->size, 8, 16, QLatin1Char('0')), &good, - Qt::WindowCloseButtonHint); + const int confirm = QMessageBox::warning(this, tr("Delete Note"), + tr("Delete Note: %1\nat %2?") + .arg(QString::fromStdString(note->name)) + .arg(context_address, 0, 16), + QMessageBox::Ok | QMessageBox::Cancel); - const u32 address = name.toUInt(&good, 16); - - if (!good) + if (confirm != QMessageBox::Ok) return; - Core::CPUThreadGuard guard(m_system); + m_ppc_symbol_db.DeleteNote(note->address); - PPCAnalyst::ReanalyzeFunction(guard, symbol->address, *symbol, address - symbol->address); emit Host::GetInstance()->PPCSymbolsChanged(); } diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h index b59bd27277..6760c082eb 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h @@ -87,13 +87,15 @@ private: void OnCopyFunction(); void OnCopyCode(); void OnCopyHex(); - void OnRenameSymbol(); void OnSelectionChanged(); - void OnSetSymbolSize(); - void OnSetSymbolEndAddress(); void OnRunToHere(); void OnAddFunction(); + void OnEditSymbol(); + void OnDeleteSymbol(); + void OnAddNote(); void OnPPCComparison(); + void OnEditNote(); + void OnDeleteNote(); void OnInsertBLR(); void OnInsertNOP(); void OnReplaceInstruction(); diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp index 50f7172a15..cac6770b67 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp @@ -16,7 +16,9 @@ #include #include #include +#include #include +#include #include #include "Common/Event.h" @@ -119,7 +121,7 @@ void CodeWidget::CreateWidgets() m_box_splitter = new QSplitter(Qt::Vertical); m_box_splitter->setStyleSheet(BOX_SPLITTER_STYLESHEET); - auto add_search_line_edit = [this](const QString& name, QListWidget* list_widget) { + auto add_search_line_edit = [this](const QString& name, QWidget* list_widget) { auto* widget = new QWidget; auto* line_layout = new QGridLayout; auto* label = new QLabel(name); @@ -138,8 +140,12 @@ void CodeWidget::CreateWidgets() m_search_callstack = add_search_line_edit(tr("Callstack"), m_callstack_list); // Symbols + auto* symbols_tab = new QTabWidget; m_symbols_list = new QListWidget; - m_search_symbols = add_search_line_edit(tr("Symbols"), m_symbols_list); + m_note_list = new QListWidget; + symbols_tab->addTab(m_symbols_list, tr("Symbols")); + symbols_tab->addTab(m_note_list, tr("Notes")); + m_search_symbols = add_search_line_edit(tr("Symbols"), symbols_tab); // Function calls m_function_calls_list = new QListWidget; @@ -197,7 +203,7 @@ void CodeWidget::ConnectWidgets() connect(m_search_callstack, &QLineEdit::textChanged, this, &CodeWidget::UpdateCallstack); connect(m_branch_watch, &QPushButton::clicked, this, &CodeWidget::OnBranchWatchDialog); - + connect(m_note_list, &QListWidget::itemPressed, this, &CodeWidget::OnSelectNote); connect(m_symbols_list, &QListWidget::itemPressed, this, &CodeWidget::OnSelectSymbol); connect(m_callstack_list, &QListWidget::itemPressed, this, &CodeWidget::OnSelectCallstack); connect(m_function_calls_list, &QListWidget::itemPressed, this, @@ -234,6 +240,7 @@ void CodeWidget::OnSetCodeAddress(u32 address) void CodeWidget::OnPPCSymbolsChanged() { UpdateSymbols(); + UpdateNotes(); UpdateCallstack(); if (const Common::Symbol* symbol = m_ppc_symbol_db.GetSymbolFromAddr(m_code_view->GetAddress())) { @@ -277,6 +284,7 @@ void CodeWidget::OnSearchSymbols() { m_symbol_filter = m_search_symbols->text(); UpdateSymbols(); + UpdateNotes(); } void CodeWidget::OnSelectSymbol() @@ -296,6 +304,17 @@ void CodeWidget::OnSelectSymbol() m_code_view->setFocus(); } +void CodeWidget::OnSelectNote() +{ + const auto items = m_note_list->selectedItems(); + if (items.isEmpty()) + return; + + const u32 address = items[0]->data(Qt::UserRole).toUInt(); + + m_code_view->SetAddress(address, CodeViewWidget::SetAddressUpdate::WithUpdate); +} + void CodeWidget::OnSelectCallstack() { const auto items = m_callstack_list->selectedItems(); @@ -424,6 +443,30 @@ void CodeWidget::UpdateSymbols() m_symbols_list->sortItems(); } +void CodeWidget::UpdateNotes() +{ + const QString selection = m_note_list->selectedItems().isEmpty() ? + QStringLiteral("") : + m_note_list->selectedItems()[0]->text(); + m_note_list->clear(); + + for (const auto& note : m_ppc_symbol_db.Notes()) + { + const QString name = QString::fromStdString(note.second.name); + + auto* item = new QListWidgetItem(name); + if (name == selection) + item->setSelected(true); + + item->setData(Qt::UserRole, note.second.address); + + if (name.toUpper().indexOf(m_symbol_filter.toUpper()) != -1) + m_note_list->addItem(item); + } + + m_note_list->sortItems(); +} + void CodeWidget::UpdateFunctionCalls(const Common::Symbol* symbol) { m_function_calls_list->clear(); diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.h b/Source/Core/DolphinQt/Debugger/CodeWidget.h index 10f56e1546..a7ca9f4a0c 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.h +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.h @@ -61,11 +61,13 @@ private: void UpdateCallstack(); void UpdateFunctionCalls(const Common::Symbol* symbol); void UpdateFunctionCallers(const Common::Symbol* symbol); + void UpdateNotes(); void OnPPCSymbolsChanged(); void OnSearchAddress(); void OnSearchSymbols(); void OnSelectSymbol(); + void OnSelectNote(); void OnSelectCallstack(); void OnSelectFunctionCallers(); void OnSelectFunctionCalls(); @@ -84,6 +86,7 @@ private: QListWidget* m_callstack_list; QLineEdit* m_search_symbols; QListWidget* m_symbols_list; + QListWidget* m_note_list; QLineEdit* m_search_calls; QListWidget* m_function_calls_list; QLineEdit* m_search_callers; diff --git a/Source/Core/DolphinQt/Debugger/EditSymbolDialog.cpp b/Source/Core/DolphinQt/Debugger/EditSymbolDialog.cpp new file mode 100644 index 0000000000..b0ecb32797 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/EditSymbolDialog.cpp @@ -0,0 +1,153 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Debugger/EditSymbolDialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +EditSymbolDialog::EditSymbolDialog(QWidget* parent, const u32 symbol_address, u32* symbol_size, + std::string* symbol_name, Type type) + : QDialog(parent), m_symbol_name(symbol_name), m_symbol_size(symbol_size), + m_symbol_address(symbol_address) +{ + m_type = type == Type::Symbol ? tr("Symbol") : tr("Note"); + setWindowTitle(tr("Edit %1").arg(m_type)); + CreateWidgets(); + ConnectWidgets(); +} + +void EditSymbolDialog::CreateWidgets() +{ + m_buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + m_buttons->addButton(tr("Reset"), QDialogButtonBox::ResetRole); + m_buttons->addButton(tr("Delete"), QDialogButtonBox::DestructiveRole); + + QLabel* info_label = new QLabel(tr("Editing %1 starting at: %2\nWarning: Must save the symbol " + "map for changes to be kept.") + .arg(m_type) + .arg(QString::number(m_symbol_address, 16))); + m_name_edit = new QLineEdit(); + m_name_edit->setPlaceholderText(tr("%1 name").arg(m_type)); + + auto* size_layout = new QHBoxLayout; + QLabel* address_end_label = new QLabel(tr("End Address")); + QLabel* size_lines_label = new QLabel(tr("Lines")); + QLabel* size_hex_label = new QLabel(tr("Size: 0x")); + m_address_end_edit = new QLineEdit(); + m_size_lines_spin = new QSpinBox(); + m_size_hex_edit = new QLineEdit(); + + size_hex_label->setAlignment(Qt::AlignCenter | Qt::AlignRight); + size_lines_label->setAlignment(Qt::AlignCenter | Qt::AlignRight); + + // Get system font and use to size boxes. + QFont font; + QFontMetrics fm(font); + const int width = fm.horizontalAdvance(QLatin1Char('0')) * 2; + m_address_end_edit->setFixedWidth(width * 6); + m_size_hex_edit->setFixedWidth(width * 5); + m_size_lines_spin->setFixedWidth(width * 5); + m_size_hex_edit->setMaxLength(7); + m_size_lines_spin->setRange(0, 99999); + + // Accept hex input only + QRegularExpression rx(QStringLiteral("[0-9a-fA-F]{0,8}")); + QValidator* validator = new QRegularExpressionValidator(rx, this); + m_address_end_edit->setValidator(validator); + m_size_hex_edit->setValidator(validator); + + size_layout->addWidget(address_end_label); + size_layout->addWidget(m_address_end_edit); + size_layout->addWidget(size_hex_label); + size_layout->addWidget(m_size_hex_edit); + size_layout->addWidget(size_lines_label); + size_layout->addWidget(m_size_lines_spin); + + auto* layout = new QVBoxLayout(); + layout->addWidget(info_label); + layout->addWidget(m_name_edit); + layout->addLayout(size_layout); + layout->addWidget(m_buttons); + + setLayout(layout); + + FillFunctionData(); +} + +void EditSymbolDialog::FillFunctionData() +{ + m_name_edit->setText(QString::fromStdString(*m_symbol_name)); + m_size_lines_spin->setValue(*m_symbol_size / 4); + m_size_hex_edit->setText(QString::number(*m_symbol_size, 16)); + m_address_end_edit->setText( + QStringLiteral("%1").arg(m_symbol_address + *m_symbol_size, 8, 16, QLatin1Char('0'))); +} + +void EditSymbolDialog::UpdateAddressData(u32 size) +{ + // Not sure what the max size should be. Definitely not a full 8, so set to 7. + size = size & 0xFFFFFFF; + + m_size_lines_spin->setValue(size / 4); + m_size_hex_edit->setText(QString::number(size, 16)); + m_address_end_edit->setText( + QStringLiteral("%1").arg(m_symbol_address + size, 8, 16, QLatin1Char('0'))); +} + +void EditSymbolDialog::ConnectWidgets() +{ + connect(m_size_lines_spin, QOverload::of(&QSpinBox::valueChanged), this, + [this](int value) { UpdateAddressData(value * 4); }); + + connect(m_size_hex_edit, &QLineEdit::editingFinished, this, [this] { + bool good; + const u32 size = m_size_hex_edit->text().toUInt(&good, 16); + if (good) + UpdateAddressData(size); + }); + + connect(m_address_end_edit, &QLineEdit::textEdited, this, [this] { + bool good; + const u32 end = m_address_end_edit->text().toUInt(&good, 16); + if (good && end > m_symbol_address) + UpdateAddressData(end - m_symbol_address); + }); + + connect(m_buttons, &QDialogButtonBox::accepted, this, &EditSymbolDialog::Accepted); + connect(m_buttons, &QDialogButtonBox::rejected, this, &EditSymbolDialog::reject); + connect(m_buttons, &QDialogButtonBox::clicked, this, [this](QAbstractButton* btn) { + const auto role = m_buttons->buttonRole(btn); + if (role == QDialogButtonBox::ButtonRole::ResetRole) + { + FillFunctionData(); + } + else if (role == QDialogButtonBox::ButtonRole::DestructiveRole) + { + m_delete_chosen = true; + QDialog::accept(); + } + }); +} + +void EditSymbolDialog::Accepted() +{ + const std::string name = m_name_edit->text().toStdString(); + + if (*m_symbol_name != name) + *m_symbol_name = name; + + bool good; + const u32 size = m_size_hex_edit->text().toUInt(&good, 16); + + if (good && *m_symbol_size != size) + *m_symbol_size = size; + + QDialog::accept(); +} diff --git a/Source/Core/DolphinQt/Debugger/EditSymbolDialog.h b/Source/Core/DolphinQt/Debugger/EditSymbolDialog.h new file mode 100644 index 0000000000..c3e3f034e7 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/EditSymbolDialog.h @@ -0,0 +1,51 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include + +#include +#include +#include "Common/CommonTypes.h" + +class QLineEdit; +class QDialogButtonBox; +class QSpinBox; + +class EditSymbolDialog : public QDialog +{ + Q_OBJECT + +public: + enum class Type + { + Symbol, + Note, + }; + + explicit EditSymbolDialog(QWidget* parent, const u32 symbol_address, u32* symbol_size, + std::string* symbol_name, Type type = Type::Symbol); + + bool DeleteRequested() const { return m_delete_chosen; } + +private: + void CreateWidgets(); + void ConnectWidgets(); + void FillFunctionData(); + void UpdateAddressData(u32 size); + void Accepted(); + + QLineEdit* m_name_edit; + QSpinBox* m_size_lines_spin; + QLineEdit* m_size_hex_edit; + QLineEdit* m_address_end_edit; + + QDialogButtonBox* m_buttons; + + QString m_type; + std::string* m_symbol_name; + u32* m_symbol_size; + const u32 m_symbol_address; + + bool m_delete_chosen = false; +}; diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index e73271a230..206b3203bb 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -144,6 +144,7 @@ + @@ -365,6 +366,7 @@ +