ppsspp/UI/ImDebugger/ImDebugger.cpp
2025-01-09 13:35:29 +01:00

1655 lines
50 KiB
C++

#include <algorithm>
#include "ext/imgui/imgui_internal.h"
#include "Common/StringUtils.h"
#include "Common/Data/Format/IniFile.h"
#include "Core/Config.h"
#include "Core/System.h"
#include "Core/RetroAchievements.h"
#include "Core/Core.h"
#include "Core/Debugger/DebugInterface.h"
#include "Core/Debugger/DisassemblyManager.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/HW/SimpleAudioDec.h"
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MemMap.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/SocketManager.h"
#include "Core/HLE/NetInetConstants.h"
#include "Core/HLE/sceNp.h"
#include "Common/System/Request.h"
#include "Core/HLE/sceAtrac.h"
#include "Core/HLE/sceAudio.h"
#include "Core/HLE/sceAudiocodec.h"
#include "Core/HLE/sceMp3.h"
#include "Core/HLE/AtracCtx.h"
#include "Core/CoreTiming.h"
// Threads window
#include "Core/HLE/sceKernelThread.h"
// Callstack window
#include "Core/MIPS/MIPSStackWalk.h"
// GPU things
#include "GPU/Common/GPUDebugInterface.h"
#include "GPU/Debugger/Stepping.h"
#include "UI/ImDebugger/ImDebugger.h"
#include "UI/ImDebugger/ImGe.h"
extern bool g_TakeScreenshot;
void ShowInMemoryViewerMenuItem(uint32_t addr, ImControl &control) {
if (ImGui::BeginMenu("Show in memory viewer")) {
for (int i = 0; i < 4; i++) {
if (ImGui::MenuItem(ImMemWindow::Title(i))) {
control.command = { ImCmd::SHOW_IN_MEMORY_VIEWER, addr, (u32)i };
}
}
ImGui::EndMenu();
}
}
void ShowInMemoryDumperMenuItem(uint32_t addr, uint32_t size, MemDumpMode mode, ImControl &control) {
if (ImGui::MenuItem(mode == MemDumpMode::Raw ? "Dump bytes to file..." : "Disassemble to file...")) {
control.command = { ImCmd::SHOW_IN_MEMORY_DUMPER, addr, size, (u32)mode};
}
}
void ShowInWindowMenuItems(uint32_t addr, ImControl &control) {
// Enable when we implement the memory viewer
ShowInMemoryViewerMenuItem(addr, control);
if (ImGui::MenuItem("Show in CPU debugger")) {
control.command = { ImCmd::SHOW_IN_CPU_DISASM, addr };
}
if (ImGui::MenuItem("Show in GE debugger")) {
control.command = { ImCmd::SHOW_IN_GE_DISASM, addr };
}
}
void StatusBar(std::string_view status) {
if (!status.size()) {
return;
}
ImGui::TextUnformatted(status.data(), status.data() + status.length());
ImGui::SameLine();
if (ImGui::SmallButton("Copy")) {
System_CopyStringToClipboard(status);
}
}
// TODO: Style it.
// Left click performs the preferred action, if any. Right click opens a menu for more.
void ImClickableAddress(uint32_t addr, ImControl &control, ImCmd cmd) {
char temp[32];
snprintf(temp, sizeof(temp), "%08x", addr);
if (ImGui::SmallButton(temp)) {
control.command = { cmd, addr };
}
// Create a right-click popup menu
if (ImGui::BeginPopupContextItem(temp)) {
if (ImGui::MenuItem("Copy address to clipboard")) {
System_CopyStringToClipboard(temp);
}
ImGui::Separator();
ShowInWindowMenuItems(addr, control);
ImGui::EndPopup();
}
}
void DrawSchedulerView(ImConfig &cfg) {
ImGui::SetNextWindowSize(ImVec2(420, 300), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Event Scheduler", &cfg.schedulerOpen)) {
ImGui::End();
return;
}
s64 ticks = CoreTiming::GetTicks();
if (ImGui::BeginChild("event_list", ImVec2(300.0f, 0.0))) {
const CoreTiming::Event *event = CoreTiming::GetFirstEvent();
while (event) {
ImGui::Text("%s (%lld): %d", CoreTiming::GetEventTypes()[event->type].name, event->time - ticks, (int)event->userdata);
event = event->next;
}
ImGui::EndChild();
}
ImGui::SameLine();
if (ImGui::BeginChild("general")) {
ImGui::Text("CoreState: %s", CoreStateToString(coreState));
ImGui::Text("downcount: %d", currentMIPS->downcount);
ImGui::Text("slicelength: %d", CoreTiming::slicelength);
ImGui::Text("Ticks: %lld", ticks);
ImGui::Text("Clock (MHz): %0.1f", (float)CoreTiming::GetClockFrequencyHz() / 1000000.0f);
ImGui::Text("Global time (us): %lld", CoreTiming::GetGlobalTimeUs());
ImGui::EndChild();
}
ImGui::End();
}
static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) {
ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("MIPS GPRs", &config.gprOpen)) {
ImGui::End();
return;
}
bool noDiff = coreState == CORE_RUNNING_CPU || coreState == CORE_STEPPING_GE;
if (ImGui::BeginTable("gpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Reg", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Decimal", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
auto gprLine = [&](int index, const char *regname, int value, int prevValue) {
bool diff = value != prevValue && !noDiff;
bool disabled = value == 0xdeadbeef;
ImGui::TableNextColumn();
ImGui::TextUnformatted(regname);
ImGui::TableNextColumn();
if (diff) {
ImGui::PushStyleColor(ImGuiCol_Text, !disabled ? ImDebuggerColor_Diff : ImDebuggerColor_DiffAlpha);
} else if (disabled) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128));
}
if (Memory::IsValid4AlignedAddress(value)) {
ImGui::PushID(index);
ImClickableAddress(value, control, index == MIPS_REG_RA ? ImCmd::SHOW_IN_CPU_DISASM : ImCmd::SHOW_IN_MEMORY_VIEWER);
ImGui::PopID();
} else {
ImGui::Text("%08x", value);
}
ImGui::TableNextColumn();
if (value >= -1000000 && value <= 1000000) {
ImGui::Text("%d", value);
}
if (diff || disabled) {
ImGui::PopStyleColor();
}
};
for (int i = 0; i < 32; i++) {
ImGui::TableNextRow();
gprLine(i, mipsDebug->GetRegName(0, i).c_str(), mipsDebug->GetGPR32Value(i), prev.gpr[i]);
}
ImGui::TableNextRow();
gprLine(32, "hi", mipsDebug->GetHi(), prev.hi);
ImGui::TableNextRow();
gprLine(33, "lo", mipsDebug->GetLo(), prev.lo);
ImGui::TableNextRow();
gprLine(34, "pc", mipsDebug->GetPC(), prev.pc);
ImGui::TableNextRow();
gprLine(35, "ll", mipsDebug->GetLLBit(), prev.ll);
ImGui::EndTable();
}
ImGui::End();
}
static void DrawFPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) {
ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("MIPS FPRs", &config.fprOpen)) {
ImGui::End();
return;
}
bool noDiff = coreState == CORE_RUNNING_CPU || coreState == CORE_STEPPING_GE;
if (ImGui::BeginTable("fpr", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Reg", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Hex", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
// fpcond
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted("fpcond");
ImGui::TableNextColumn();
ImGui::Text("%08x", mipsDebug->GetFPCond());
for (int i = 0; i < 32; i++) {
float fvalue = mipsDebug->GetFPR32Value(i);
float prevValue = prev.fpr[i];
// NOTE: Using memcmp to avoid NaN problems.
bool diff = memcmp(&fvalue, &prevValue, 4) != 0 && !noDiff;
u32 fivalue;
memcpy(&fivalue, &fvalue, sizeof(fivalue));
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (diff) {
ImGui::PushStyleColor(ImGuiCol_Text, ImDebuggerColor_Diff);
}
ImGui::TextUnformatted(mipsDebug->GetRegName(1, i).c_str());
ImGui::TableNextColumn();
ImGui::Text("%0.7f", fvalue);
ImGui::TableNextColumn();
ImGui::Text("%08x", fivalue);
if (diff) {
ImGui::PopStyleColor();
}
}
ImGui::EndTable();
}
ImGui::End();
}
static void DrawVFPU(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) {
ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("MIPS VFPU regs", &config.vfpuOpen)) {
ImGui::End();
return;
}
ImGui::Text("TODO");
ImGui::End();
}
static const char *ThreadStatusToString(u32 status) {
switch (status) {
case THREADSTATUS_RUNNING: return "Running";
case THREADSTATUS_READY: return "Ready";
case THREADSTATUS_WAIT: return "Wait";
case THREADSTATUS_SUSPEND: return "Suspended";
case THREADSTATUS_DORMANT: return "Dormant";
case THREADSTATUS_DEAD: return "Dead";
case THREADSTATUS_WAITSUSPEND: return "WaitSuspended";
default:
break;
}
return "(unk)";
}
void WaitIDToString(WaitType waitType, SceUID waitID, char *buffer, size_t bufSize) {
switch (waitType) {
case WAITTYPE_AUDIOCHANNEL:
snprintf(buffer, bufSize, "chan %d", (int)waitID);
return;
case WAITTYPE_IO:
// TODO: More detail
snprintf(buffer, bufSize, "fd: %d", (int)waitID);
return;
case WAITTYPE_ASYNCIO:
snprintf(buffer, bufSize, "id: %d", (int)waitID);
return;
case WAITTYPE_THREADEND:
case WAITTYPE_MUTEX:
case WAITTYPE_LWMUTEX:
case WAITTYPE_MODULE:
case WAITTYPE_MSGPIPE:
case WAITTYPE_FPL:
case WAITTYPE_VPL:
case WAITTYPE_MBX:
case WAITTYPE_EVENTFLAG:
case WAITTYPE_SEMA:
// Get the name of the thread
if (kernelObjects.IsValid(waitID)) {
auto obj = kernelObjects.GetFast<KernelObject>(waitID);
if (obj && obj->GetName()) {
truncate_cpy(buffer, bufSize, obj->GetName());
return;
}
}
break;
case WAITTYPE_DELAY:
case WAITTYPE_SLEEP:
case WAITTYPE_HLEDELAY:
case WAITTYPE_UMD:
case WAITTYPE_NONE:
case WAITTYPE_VBLANK:
case WAITTYPE_MICINPUT:
truncate_cpy(buffer, bufSize, "-");
return;
default:
truncate_cpy(buffer, bufSize, "(unimpl)");
return;
}
}
void DrawThreadView(ImConfig &cfg, ImControl &control) {
ImGui::SetNextWindowSize(ImVec2(420, 300), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Threads", &cfg.threadsOpen)) {
ImGui::End();
return;
}
std::vector<DebugThreadInfo> info = GetThreadsInfo();
if (ImGui::BeginTable("threads", 8, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Id", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("PC", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Entry", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Priority", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("State", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Wait Type", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Wait ID", ImGuiTableColumnFlags_WidthStretch);
// .initialStack, .stackSize, etc
ImGui::TableHeadersRow();
for (int i = 0; i < (int)info.size(); i++) {
const DebugThreadInfo &thread = info[i];
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%d", thread.id);
ImGui::TableNextColumn();
ImGui::PushID(i);
if (ImGui::Selectable(thread.name, cfg.selectedThread == i, ImGuiSelectableFlags_AllowDoubleClick | ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedThread = i;
}
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
cfg.selectedThread = i;
ImGui::OpenPopup("threadPopup");
}
ImGui::TableNextColumn();
ImClickableAddress(thread.curPC, control, ImCmd::SHOW_IN_CPU_DISASM);
ImGui::TableNextColumn();
ImClickableAddress(thread.entrypoint, control, ImCmd::SHOW_IN_CPU_DISASM);
ImGui::TableNextColumn();
ImGui::Text("%d", thread.priority);
ImGui::TableNextColumn();
ImGui::TextUnformatted(ThreadStatusToString(thread.status));
ImGui::TableNextColumn();
ImGui::TextUnformatted(getWaitTypeName(thread.waitType));
ImGui::TableNextColumn();
char temp[64];
WaitIDToString(thread.waitType, thread.waitID, temp, sizeof(temp));
ImGui::TextUnformatted(temp);
if (ImGui::BeginPopup("threadPopup")) {
DebugThreadInfo &thread = info[i];
ImGui::Text("Thread: %s", thread.name);
if (ImGui::MenuItem("Copy entry to clipboard")) {
char temp[64];
snprintf(temp, sizeof(temp), "%08x", thread.entrypoint);
System_CopyStringToClipboard(temp);
}
if (ImGui::MenuItem("Copy thread PC to clipboard")) {
char temp[64];
snprintf(temp, sizeof(temp), "%08x", thread.curPC);
System_CopyStringToClipboard(temp);
}
if (ImGui::MenuItem("Kill thread")) {
// Dangerous!
sceKernelTerminateThread(thread.id);
}
if (thread.status == THREADSTATUS_WAIT) {
if (ImGui::MenuItem("Force run now")) {
__KernelResumeThreadFromWait(thread.id, 0);
}
}
ImGui::EndPopup();
}
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::End();
}
// TODO: Add popup menu, export file, export dir, etc...
static void RecurseFileSystem(IFileSystem *fs, std::string path) {
std::vector<PSPFileInfo> fileInfo = fs->GetDirListing(path);
for (auto &file : fileInfo) {
if (file.type == FileType::FILETYPE_DIRECTORY) {
if (file.name != "." && file.name != ".." && ImGui::TreeNode(file.name.c_str())) {
std::string fpath = path + "/" + file.name;
RecurseFileSystem(fs, fpath);
ImGui::TreePop();
}
} else {
ImGui::TextUnformatted(file.name.c_str());
}
}
}
static void DrawFilesystemBrowser(ImConfig &cfg) {
ImGui::SetNextWindowSize(ImVec2(420, 500), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("File System", &cfg.filesystemBrowserOpen)) {
ImGui::End();
return;
}
for (auto &fs : pspFileSystem.GetMounts()) {
std::string path;
char desc[256];
fs.system->Describe(desc, sizeof(desc));
char fsTitle[512];
snprintf(fsTitle, sizeof(fsTitle), "%s - %s", fs.prefix.c_str(), desc);
if (ImGui::TreeNode(fsTitle)) {
auto system = fs.system;
RecurseFileSystem(system.get(), path);
ImGui::TreePop();
}
}
ImGui::End();
}
static void DrawKernelObjects(ImConfig &cfg) {
if (!ImGui::Begin("Kernel Objects", &cfg.kernelObjectsOpen)) {
ImGui::End();
return;
}
if (ImGui::BeginTable("kos", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH | ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Summary", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
for (int i = 0; i < (int)KernelObjectPool::maxCount; i++) {
int id = i + KernelObjectPool::handleOffset;
if (!kernelObjects.IsValid(id)) {
continue;
}
KernelObject *obj = kernelObjects.GetFast<KernelObject>(id);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(i);
if (ImGui::Selectable("", cfg.selectedThread == i, ImGuiSelectableFlags_AllowDoubleClick | ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedThread = i;
}
ImGui::SameLine();
/*
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
cfg.selectedThread = i;
ImGui::OpenPopup("kernelObjectPopup");
}
*/
ImGui::Text("%04x", id);
ImGui::TableNextColumn();
ImGui::TextUnformatted(obj->GetTypeName());
ImGui::TableNextColumn();
ImGui::TextUnformatted(obj->GetName());
ImGui::TableNextColumn();
char qi[128];
obj->GetQuickInfo(qi, sizeof(qi));
ImGui::TextUnformatted(qi);
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::End();
}
static void DrawNp(ImConfig &cfg) {
if (!ImGui::Begin("NP", &cfg.npOpen)) {
ImGui::End();
return;
}
ImGui::Text("Signed in: %d", npSigninState);
ImGui::Text("Title ID: %s", npTitleId.data);
SceNpId id{};
NpGetNpId(&id);
ImGui::Text("User Handle: %s", id.handle.data);
ImGui::End();
}
static void DrawSockets(ImConfig &cfg) {
if (!ImGui::Begin("Sockets", &cfg.socketsOpen)) {
ImGui::End();
return;
}
if (ImGui::BeginTable("sock", 7, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH | ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Host", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Non-blocking", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Created by", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Domain", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Protocol", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
for (int i = SocketManager::MIN_VALID_INET_SOCKET; i < SocketManager::VALID_INET_SOCKET_COUNT; i++) {
InetSocket *inetSocket;
if (!g_socketManager.GetInetSocket(i, &inetSocket)) {
continue;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%d", i);
ImGui::TableNextColumn();
ImGui::Text("%d", (int)inetSocket->sock);
ImGui::TableNextColumn();
ImGui::TextUnformatted(inetSocket->nonblocking ? "Non-blocking" : "Blocking");
ImGui::TableNextColumn();
ImGui::TextUnformatted(SocketStateToString(inetSocket->state));
ImGui::TableNextColumn();
std::string str = inetSocketDomain2str(inetSocket->domain);
ImGui::TextUnformatted(str.c_str());
ImGui::TableNextColumn();
str = inetSocketType2str(inetSocket->type);
ImGui::TextUnformatted(str.c_str());
ImGui::TableNextColumn();
str = inetSocketProto2str(inetSocket->protocol);
ImGui::TextUnformatted(str.c_str());
ImGui::TableNextColumn();
}
ImGui::EndTable();
}
ImGui::End();
}
static const char *MemCheckConditionToString(MemCheckCondition cond) {
switch (cond) {
case MEMCHECK_READ: return "Read";
case MEMCHECK_WRITE: return "Write";
case MEMCHECK_READWRITE: return "Read/Write";
case MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE: return "Write Change";
case MEMCHECK_READWRITE | MEMCHECK_WRITE_ONCHANGE: return "Read/Write Change";
default:
return "(bad!)";
}
}
static void DrawBreakpointsView(MIPSDebugInterface *mipsDebug, ImConfig &cfg) {
if (!ImGui::Begin("Breakpoints", &cfg.breakpointsOpen)) {
ImGui::End();
return;
}
if (ImGui::BeginTable("bp_window", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("right", ImGuiTableColumnFlags_WidthFixed, 150.0f);
ImGui::TableNextRow();
ImGui::TableNextColumn();
auto &bps = g_breakpoints.GetBreakpointRefs();
auto &mcs = g_breakpoints.GetMemCheckRefs();
if (cfg.selectedBreakpoint >= bps.size()) {
cfg.selectedBreakpoint = -1;
}
if (cfg.selectedMemCheck >= mcs.size()) {
cfg.selectedMemCheck = -1;
}
if (ImGui::Button("Add Breakpoint")) {
cfg.selectedBreakpoint = g_breakpoints.AddBreakPoint(0);
cfg.selectedMemCheck = -1;
}
ImGui::SameLine();
if (ImGui::Button("Add MemCheck")) {
cfg.selectedMemCheck = g_breakpoints.AddMemCheck(0, 0, MemCheckCondition::MEMCHECK_WRITE, BreakAction::BREAK_ACTION_PAUSE);
cfg.selectedBreakpoint = -1;
}
if (ImGui::BeginTable("breakpoints", 8, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Enabled", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Size/Label", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("OpCode", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Cond", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Hits", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
for (int i = 0; i < (int)bps.size(); i++) {
auto &bp = bps[i];
bool temp = bp.temporary;
if (temp) {
continue;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(i);
// TODO: This clashes with the checkbox!
if (ImGui::Selectable("", cfg.selectedBreakpoint == i, ImGuiSelectableFlags_SpanAllColumns) && !bp.temporary) {
cfg.selectedBreakpoint = i;
cfg.selectedMemCheck = -1;
}
ImGui::SameLine();
ImGui::CheckboxFlags("##enabled", (int *)&bp.result, (int)BREAK_ACTION_PAUSE);
ImGui::TableNextColumn();
ImGui::TextUnformatted("Exec");
ImGui::TableNextColumn();
ImGui::Text("%08x", bp.addr);
ImGui::TableNextColumn();
const std::string sym = g_symbolMap->GetLabelString(bp.addr);
if (!sym.empty()) {
ImGui::TextUnformatted(sym.c_str()); // size/label
} else {
ImGui::TextUnformatted("-"); // size/label
}
ImGui::TableNextColumn();
// disasm->getOpcodeText(displayedBreakPoints_[index].addr, temp, sizeof(temp));
ImGui::TextUnformatted("-"); // opcode
ImGui::TableNextColumn();
if (bp.hasCond) {
ImGui::TextUnformatted(bp.cond.expressionString.c_str());
} else {
ImGui::TextUnformatted("-"); // condition
}
ImGui::TableNextColumn();
ImGui::Text("-"); // hits not available on exec bps yet
ImGui::PopID();
}
// OK, now list the memchecks.
for (int i = 0; i < (int)mcs.size(); i++) {
auto &mc = mcs[i];
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(i + 10000);
if (ImGui::Selectable("", cfg.selectedMemCheck == i, ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedBreakpoint = -1;
cfg.selectedMemCheck = i;
}
ImGui::SameLine();
ImGui::CheckboxFlags("", (int *)&mc.result, BREAK_ACTION_PAUSE);
ImGui::TableNextColumn();
ImGui::TextUnformatted(MemCheckConditionToString(mc.cond));
ImGui::TableNextColumn();
ImGui::Text("%08x", mc.start);
ImGui::TableNextColumn();
ImGui::Text("%08x", mc.end ? (mc.end - mc.start) : 1);
ImGui::TableNextColumn();
ImGui::TextUnformatted("-"); // opcode
ImGui::TableNextColumn();
ImGui::TextUnformatted("-"); // cond
ImGui::TableNextColumn();
ImGui::Text("%d", mc.numHits);
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::TableNextColumn();
if (cfg.selectedBreakpoint >= 0) {
// Add edit form for breakpoints
if (ImGui::BeginChild("bp_edit")) {
auto &bp = bps[cfg.selectedBreakpoint];
ImGui::TextUnformatted("Edit breakpoint");
ImGui::CheckboxFlags("Enabled", (int *)&bp.result, (int)BREAK_ACTION_PAUSE);
ImGui::InputScalar("Address", ImGuiDataType_U32, &bp.addr, nullptr, nullptr, "%08x", ImGuiInputTextFlags_CharsHexadecimal);
if (ImGui::Button("Delete")) {
g_breakpoints.RemoveBreakPoint(bp.addr);
}
ImGui::EndChild();
}
}
if (cfg.selectedMemCheck >= 0) {
// Add edit form for memchecks
if (ImGui::BeginChild("mc_edit")) {
auto &mc = mcs[cfg.selectedMemCheck];
ImGui::TextUnformatted("Edit memcheck");
ImGui::CheckboxFlags("Enabled", (int *)&mc.result, (int)BREAK_ACTION_PAUSE);
ImGui::InputScalar("Start", ImGuiDataType_U32, &mc.start);
ImGui::InputScalar("End", ImGuiDataType_U32, &mc.end);
if (ImGui::Button("Delete")) {
g_breakpoints.RemoveMemCheck(mcs[cfg.selectedMemCheck].start, mcs[cfg.selectedMemCheck].end);
}
ImGui::EndChild();
}
}
ImGui::EndTable();
}
ImGui::End();
}
void DrawAudioDecodersView(ImConfig &cfg) {
if (!ImGui::Begin("Audio decoding contexts", &cfg.audioDecodersOpen)) {
ImGui::End();
return;
}
if (ImGui::CollapsingHeader("sceAtrac", ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::BeginTable("atracs", 5, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Index", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("OutChans", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("CurrentSample", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("RemainingFrames", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
for (int i = 0; i < PSP_NUM_ATRAC_IDS; i++) {
u32 type = 0;
const AtracBase *atracBase = __AtracGetCtx(i, &type);
if (!atracBase) {
continue;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%d", i);
ImGui::TableNextColumn();
switch (type) {
case PSP_MODE_AT_3_PLUS:
ImGui::TextUnformatted("Atrac3+");
break;
case PSP_MODE_AT_3:
ImGui::TextUnformatted("Atrac3");
break;
default:
ImGui::Text("%04x", type);
break;
}
ImGui::TableNextColumn();
ImGui::Text("%d", atracBase->GetOutputChannels());
ImGui::TableNextColumn();
ImGui::Text("%d", atracBase->CurrentSample());
ImGui::TableNextColumn();
ImGui::Text("%d", atracBase->RemainingFrames());
// TODO: Add more.
}
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("sceAudiocodec", ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::BeginTable("codecs", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("CtxAddr", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
for (auto &iter : g_audioDecoderContexts) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%08x", iter.first);
ImGui::TableNextColumn();
switch (iter.second->GetAudioType()) {
case PSP_CODEC_AAC: ImGui::TextUnformatted("AAC"); break;
case PSP_CODEC_MP3: ImGui::TextUnformatted("MP3"); break;
case PSP_CODEC_AT3PLUS: ImGui::TextUnformatted("Atrac3+"); break;
case PSP_CODEC_AT3: ImGui::TextUnformatted("Atrac3"); break;
default: ImGui::Text("%08x", iter.second->GetAudioType()); break;
}
}
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("sceMp3", ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::BeginTable("mp3", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Handle", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Channels", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("StartPos", ImGuiTableColumnFlags_WidthFixed);
// TODO: more..
for (auto &iter : mp3Map) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%d", iter.first);
if (!iter.second) {
continue;
}
ImGui::TableNextColumn();
ImGui::Text("%d", iter.second->Channels);
ImGui::TableNextColumn();
ImGui::Text("%d", (int)iter.second->startPos);
}
ImGui::EndTable();
}
}
ImGui::End();
}
void DrawAudioChannels(ImConfig &cfg) {
if (!ImGui::Begin("Raw audio channels", &cfg.audioChannelsOpen)) {
ImGui::End();
return;
}
if (ImGui::BeginTable("audios", 6, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Index", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("SampleAddr", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("SampleCount", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Volume", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Format", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Waiting Thread", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
// vaudio / output2 uses channel 8.
for (int i = 0; i < PSP_AUDIO_CHANNEL_MAX + 1; i++) {
if (!chans[i].reserved) {
continue;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (i == 8) {
ImGui::TextUnformatted("audio2");
} else {
ImGui::Text("%d", i);
}
ImGui::TableNextColumn();
ImGui::Text("%08x", chans[i].sampleAddress);
ImGui::TableNextColumn();
ImGui::Text("%08x", chans[i].sampleCount);
ImGui::TableNextColumn();
ImGui::Text("%d | %d", chans[i].leftVolume, chans[i].rightVolume);
ImGui::TableNextColumn();
switch (chans[i].format) {
case PSP_AUDIO_FORMAT_STEREO:
ImGui::TextUnformatted("Stereo");
break;
case PSP_AUDIO_FORMAT_MONO:
ImGui::TextUnformatted("Mono");
break;
default:
ImGui::TextUnformatted("UNK: %04x");
break;
}
ImGui::TableNextColumn();
for (auto t : chans[i].waitingThreads) {
KernelObject *thread = kernelObjects.GetFast<KernelObject>(t.threadID);
if (thread) {
ImGui::Text("%s: %d", thread->GetName(), t.numSamples);
}
}
}
ImGui::EndTable();
}
ImGui::End();
}
static void DrawCallStacks(const MIPSDebugInterface *debug, bool *open) {
if (!ImGui::Begin("Callstacks", open)) {
ImGui::End();
return;
}
std::vector<DebugThreadInfo> info = GetThreadsInfo();
// TODO: Add dropdown for thread choice.
u32 entry = 0;
u32 stackTop = 0;
for (auto &thread : info) {
if (thread.isCurrent) {
entry = thread.entrypoint;
stackTop = thread.initialStack;
break;
}
}
if (entry != 0 && ImGui::BeginTable("frames", 6, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Entry", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("EntryAddr", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("CurPC", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("CurOpCode", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("CurSP", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableHeadersRow();
ImGui::TableNextRow();
std::vector<MIPSStackWalk::StackFrame> frames = MIPSStackWalk::Walk(debug->GetPC(), debug->GetRegValue(0, 31), debug->GetRegValue(0, 29), entry, stackTop);
// TODO: Add context menu and clickability
for (auto &frame : frames) {
const std::string entrySym = g_symbolMap->GetLabelString(frame.entry);
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted(entrySym.c_str());
ImGui::TableSetColumnIndex(1);
ImGui::Text("%08x", frame.entry);
ImGui::TableSetColumnIndex(2);
ImGui::Text("%08x", frame.pc);
ImGui::TableSetColumnIndex(3);
ImGui::TextUnformatted("N/A"); // opcode, see the old debugger
ImGui::TableSetColumnIndex(4);
ImGui::Text("%08x", frame.sp);
ImGui::TableSetColumnIndex(5);
ImGui::Text("%d", frame.stackSize);
ImGui::TableNextRow();
// TODO: More fields?
}
ImGui::EndTable();
}
ImGui::End();
}
static void DrawModules(const MIPSDebugInterface *debug, ImConfig &cfg) {
if (!ImGui::Begin("Modules", &cfg.modulesOpen) || !g_symbolMap) {
ImGui::End();
return;
}
std::vector<LoadedModuleInfo> modules = g_symbolMap->getAllModules();
if (ImGui::BeginTable("modules", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Active", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
// TODO: Add context menu and clickability
for (int i = 0; i < (int)modules.size(); i++) {
auto &module = modules[i];
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(module.name.c_str(), cfg.selectedModule == i, ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedModule = i;
}
ImGui::TableNextColumn();
ImGui::Text("%08x", module.address);
ImGui::TableNextColumn();
ImGui::Text("%08x", module.size);
ImGui::TableNextColumn();
ImGui::TextUnformatted(module.active ? "yes" : "no");
}
ImGui::EndTable();
}
if (cfg.selectedModule >= 0 && cfg.selectedModule < (int)modules.size()) {
auto &module = modules[cfg.selectedModule];
// TODO: Show details
}
ImGui::End();
}
void DrawHLEModules(ImConfig &config) {
if (!ImGui::Begin("HLE Modules", &config.hleModulesOpen)) {
ImGui::End();
return;
}
const int moduleCount = GetNumRegisteredModules();
std::vector<const HLEModule *> modules;
modules.reserve(moduleCount);
for (int i = 0; i < moduleCount; i++) {
modules.push_back(GetModuleByIndex(i));
}
std::sort(modules.begin(), modules.end(), [](const HLEModule* a, const HLEModule* b) {
return std::strcmp(a->name, b->name) < 0;
});
for (auto mod : modules) {
if (ImGui::TreeNode(mod->name)) {
for (int j = 0; j < mod->numFunctions; j++) {
auto &func = mod->funcTable[j];
ImGui::Text("%s(%s)", func.name, func.argmask);
}
ImGui::TreePop();
}
}
ImGui::End();
}
ImDebugger::ImDebugger() {
reqToken_ = g_requestManager.GenerateRequesterToken();
cfg_.LoadConfig(ConfigPath());
}
ImDebugger::~ImDebugger() {
cfg_.SaveConfig(ConfigPath());
}
void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebug, Draw::DrawContext *draw) {
// Snapshot the coreState to avoid inconsistency.
const CoreState coreState = ::coreState;
if (Achievements::HardcoreModeActive()) {
ImGui::Begin("RetroAchievements hardcore mode");
ImGui::Text("The debugger may not be used when the\nRetroAchievements hardcore mode is enabled.");
ImGui::Text("To use the debugger, go into Settings / Tools / RetroAchievements and disable them.");
ImGui::End();
return;
}
// TODO: Pass mipsDebug in where needed instead.
g_disassemblyManager.setCpu(mipsDebug);
disasm_.View().setDebugger(mipsDebug);
for (int i = 0; i < 4; i++) {
mem_[i].View().setDebugger(mipsDebug);
}
// Watch the step counters to figure out when to update things.
if (lastCpuStepCount_ != Core_GetSteppingCounter()) {
lastCpuStepCount_ = Core_GetSteppingCounter();
snapshot_ = newSnapshot_; // Compare against the previous snapshot.
Snapshot(currentMIPS);
disasm_.NotifyStep();
}
if (lastGpuStepCount_ != GPUStepping::GetSteppingCounter()) {
// A GPU step has happened since last time. This means that we should re-center the cursor.
// Snapshot();
lastGpuStepCount_ = GPUStepping::GetSteppingCounter();
SnapshotGPU(gpuDebug);
geDebugger_.NotifyStep();
}
ImControl control{};
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("Debug")) {
switch (coreState) {
case CoreState::CORE_STEPPING_CPU:
if (ImGui::MenuItem("Run")) {
Core_Resume();
}
break;
case CoreState::CORE_RUNNING_CPU:
if (ImGui::MenuItem("Break")) {
Core_Break("Menu:Break");
}
break;
default:
break;
}
ImGui::Separator();
ImGui::MenuItem("Ignore bad memory accesses", nullptr, &g_Config.bIgnoreBadMemAccess);
ImGui::MenuItem("Break on frame timeout", nullptr, &g_Config.bBreakOnFrameTimeout);
ImGui::MenuItem("Don't break on start", nullptr, &g_Config.bAutoRun); // should really invert this bool!
ImGui::MenuItem("Fast memory", nullptr, &g_Config.bFastMemory);
ImGui::Separator();
/*
// Symbol stuff. Move to separate menu?
// Doesn't quite seem to work yet.
if (ImGui::MenuItem("Load symbol map...")) {
System_BrowseForFile(reqToken_, "Load symbol map", BrowseFileType::SYMBOL_MAP, [&](const char *responseString, int) {
Path path(responseString);
if (!g_symbolMap->LoadSymbolMap(path)) {
ERROR_LOG(Log::Common, "Failed to load symbol map");
}
disasm_.DirtySymbolMap();
});
}
if (ImGui::MenuItem("Save symbol map...")) {
System_BrowseForFileSave(reqToken_, "Save symbol map", "symbols.map", BrowseFileType::SYMBOL_MAP, [](const char *responseString, int) {
Path path(responseString);
if (!g_symbolMap->SaveSymbolMap(path)) {
ERROR_LOG(Log::Common, "Failed to save symbol map");
}
});
}
*/
if (ImGui::MenuItem("Reset symbol map")) {
g_symbolMap->Clear();
disasm_.DirtySymbolMap();
// NotifyDebuggerMapLoaded();
}
ImGui::Separator();
if (ImGui::MenuItem("Take screenshot")) {
g_TakeScreenshot = true;
}
ImGui::MenuItem("Save screenshot as .png", nullptr, &g_Config.bScreenshotsAsPNG);
if (ImGui::MenuItem("Restart graphics")) {
System_PostUIMessage(UIMessage::RESTART_GRAPHICS);
}
if (System_GetPropertyBool(SYSPROP_HAS_TEXT_CLIPBOARD)) {
if (ImGui::MenuItem("Copy memory base to clipboard")) {
System_CopyStringToClipboard(StringFromFormat("%016llx", (uint64_t)(uintptr_t)Memory::base));
}
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("CPU")) {
ImGui::MenuItem("CPU debugger", nullptr, &cfg_.disasmOpen);
ImGui::MenuItem("GPR regs", nullptr, &cfg_.gprOpen);
ImGui::MenuItem("FPR regs", nullptr, &cfg_.fprOpen);
ImGui::MenuItem("VFPU regs", nullptr, &cfg_.vfpuOpen);
ImGui::MenuItem("Callstacks", nullptr, &cfg_.callstackOpen);
ImGui::MenuItem("Breakpoints", nullptr, &cfg_.breakpointsOpen);
ImGui::MenuItem("Scheduler", nullptr, &cfg_.schedulerOpen);
ImGui::Separator();
for (int i = 0; i < 4; i++) {
char title[64];
snprintf(title, sizeof(title), "Memory %d", i + 1);
ImGui::MenuItem(title, nullptr, &cfg_.memViewOpen[i]);
}
ImGui::MenuItem("Memory Dumper", nullptr, &cfg_.memDumpOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("HLE")) {
ImGui::MenuItem("File System Browser", nullptr, &cfg_.filesystemBrowserOpen);
ImGui::MenuItem("Kernel Objects", nullptr, &cfg_.kernelObjectsOpen);
ImGui::MenuItem("Threads", nullptr, &cfg_.threadsOpen);
ImGui::MenuItem("Modules",nullptr, &cfg_.modulesOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Graphics")) {
ImGui::MenuItem("GE Debugger", nullptr, &cfg_.geDebuggerOpen);
ImGui::MenuItem("GE State", nullptr, &cfg_.geStateOpen);
ImGui::MenuItem("Display Output", nullptr, &cfg_.displayOpen);
ImGui::MenuItem("Textures", nullptr, &cfg_.texturesOpen);
ImGui::MenuItem("Framebuffers", nullptr, &cfg_.framebuffersOpen);
ImGui::MenuItem("Pixel Viewer", nullptr, &cfg_.pixelViewerOpen);
// More to come here...
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Audio")) {
ImGui::MenuItem("Raw audio channels", nullptr, &cfg_.audioChannelsOpen);
ImGui::MenuItem("Decoder contexts", nullptr, &cfg_.audioDecodersOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Network")) {
ImGui::MenuItem("Sockets", nullptr, &cfg_.socketsOpen);
ImGui::MenuItem("NP", nullptr, &cfg_.npOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Tools")) {
ImGui::MenuItem("Debug stats", nullptr, &cfg_.debugStatsOpen);
ImGui::MenuItem("Struct viewer", nullptr, &cfg_.structViewerOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Misc")) {
if (ImGui::MenuItem("Close Debugger")) {
g_Config.bShowImDebugger = false;
}
ImGui::MenuItem("Dear ImGui Demo", nullptr, &cfg_.demoOpen);
ImGui::MenuItem("Dear ImGui Style editor", nullptr, &cfg_.styleEditorOpen);
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
if (cfg_.demoOpen) {
ImGui::ShowDemoWindow(&cfg_.demoOpen);
}
if (cfg_.styleEditorOpen) {
ImGui::ShowStyleEditor();
}
if (cfg_.disasmOpen) {
disasm_.Draw(mipsDebug, cfg_, control, coreState);
}
if (cfg_.gprOpen) {
DrawGPRs(cfg_, control, mipsDebug, snapshot_);
}
if (cfg_.fprOpen) {
DrawFPRs(cfg_, control, mipsDebug, snapshot_);
}
if (cfg_.vfpuOpen) {
DrawVFPU(cfg_, control, mipsDebug, snapshot_);
}
if (cfg_.breakpointsOpen) {
DrawBreakpointsView(mipsDebug, cfg_);
}
if (cfg_.filesystemBrowserOpen) {
DrawFilesystemBrowser(cfg_);
}
if (cfg_.audioChannelsOpen) {
DrawAudioChannels(cfg_);
}
if (cfg_.kernelObjectsOpen) {
DrawKernelObjects(cfg_);
}
if (cfg_.threadsOpen) {
DrawThreadView(cfg_, control);
}
if (cfg_.callstackOpen) {
DrawCallStacks(mipsDebug, &cfg_.callstackOpen);
}
if (cfg_.modulesOpen) {
DrawModules(mipsDebug, cfg_);
}
if (cfg_.audioDecodersOpen) {
DrawAudioDecodersView(cfg_);
}
if (cfg_.hleModulesOpen) {
DrawHLEModules(cfg_);
}
if (cfg_.framebuffersOpen) {
DrawFramebuffersWindow(cfg_, gpuDebug->GetFramebufferManagerCommon());
}
if (cfg_.texturesOpen) {
DrawTexturesWindow(cfg_, gpuDebug->GetTextureCacheCommon());
}
if (cfg_.displayOpen) {
DrawDisplayWindow(cfg_, gpuDebug->GetFramebufferManagerCommon());
}
if (cfg_.debugStatsOpen) {
DrawDebugStatsWindow(cfg_);
}
if (cfg_.structViewerOpen) {
structViewer_.Draw(mipsDebug, &cfg_.structViewerOpen);
}
if (cfg_.geDebuggerOpen) {
geDebugger_.Draw(cfg_, control, gpuDebug, draw);
}
if (cfg_.geStateOpen) {
geStateWindow_.Draw(cfg_, control, gpuDebug);
}
if (cfg_.schedulerOpen) {
DrawSchedulerView(cfg_);
}
if (cfg_.pixelViewerOpen) {
pixelViewer_.Draw(cfg_, control, gpuDebug, draw);
}
if (cfg_.memDumpOpen) {
memDumpWindow_.Draw(cfg_, mipsDebug);
}
for (int i = 0; i < 4; i++) {
if (cfg_.memViewOpen[i]) {
mem_[i].Draw(mipsDebug, cfg_, control, i);
}
}
if (cfg_.socketsOpen) {
DrawSockets(cfg_);
}
if (cfg_.npOpen) {
DrawNp(cfg_);
}
// Process UI commands
switch (control.command.cmd) {
case ImCmd::SHOW_IN_CPU_DISASM:
disasm_.View().gotoAddr(control.command.param);
cfg_.disasmOpen = true;
ImGui::SetWindowFocus(disasm_.Title());
break;
case ImCmd::SHOW_IN_GE_DISASM:
geDebugger_.View().GotoAddr(control.command.param);
cfg_.geDebuggerOpen = true;
ImGui::SetWindowFocus(geDebugger_.Title());
break;
case ImCmd::SHOW_IN_MEMORY_VIEWER:
{
u32 index = control.command.param2;
_dbg_assert_(index < 4);
mem_[index].GotoAddr(control.command.param);
cfg_.memViewOpen[index] = true;
ImGui::SetWindowFocus(ImMemWindow::Title(index));
break;
}
case ImCmd::SHOW_IN_MEMORY_DUMPER:
{
cfg_.memDumpOpen = true;
memDumpWindow_.SetRange(control.command.param, control.command.param2, (MemDumpMode)control.command.param3);
ImGui::SetWindowFocus(memDumpWindow_.Title());
break;
}
case ImCmd::TRIGGER_FIND_POPUP:
// TODO
break;
case ImCmd::NONE:
break;
}
}
void ImDebugger::Snapshot(MIPSState *mips) {
memcpy(newSnapshot_.gpr, mips->r, sizeof(newSnapshot_.gpr));
memcpy(newSnapshot_.fpr, mips->fs, sizeof(newSnapshot_.fpr));
memcpy(newSnapshot_.vpr, mips->v, sizeof(newSnapshot_.vpr));
newSnapshot_.pc = mips->pc;
newSnapshot_.lo = mips->lo;
newSnapshot_.hi = mips->hi;
newSnapshot_.ll = mips->llBit;
pixelViewer_.Snapshot();
}
void ImDebugger::SnapshotGPU(GPUDebugInterface *gpuDebug) {
pixelViewer_.Snapshot();
}
void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, int index) {
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(Title(index), &cfg.memViewOpen[index])) {
ImGui::End();
return;
}
// Toolbars
ImGui::InputScalar("Go to addr: ", ImGuiDataType_U32, &gotoAddr_, NULL, NULL, "%08X");
if (ImGui::IsItemDeactivatedAfterEdit()) {
memView_.gotoAddr(gotoAddr_);
}
ImGui::SameLine();
if (ImGui::SmallButton("Go")) {
memView_.gotoAddr(gotoAddr_);
}
ImVec2 size(0, -ImGui::GetFrameHeightWithSpacing());
// Main views - list of interesting addresses to the left, memory view to the right.
if (ImGui::BeginChild("addr_list", ImVec2(200.0f, size.y), ImGuiChildFlags_ResizeX)) {
if (ImGui::Selectable("Scratch")) {
GotoAddr(0x00010000);
}
if (ImGui::Selectable("Kernel RAM")) {
GotoAddr(0x08000000);
}
if (ImGui::Selectable("User RAM")) {
GotoAddr(0x08800000);
}
if (ImGui::Selectable("VRAM")) {
GotoAddr(0x04000000);
}
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("memview", size)) {
memView_.Draw(ImGui::GetWindowDrawList());
}
ImGui::EndChild();
StatusBar(memView_.StatusMessage());
ImGui::End();
}
const char *ImMemWindow::Title(int index) {
static const char *const titles[4] = { "Memory 1", "Memory 2", "Memory 3", "Memory 4" };
return titles[index];
}
void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, CoreState coreState) {
disasmView_.setDebugger(mipsDebug);
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(Title(), &cfg.disasmOpen)) {
ImGui::End();
return;
}
if (ImGui::IsWindowFocused()) {
// Process stepping keyboard shortcuts.
if (ImGui::IsKeyPressed(ImGuiKey_F10)) {
u32 stepSize = disasmView_.getInstructionSizeAt(mipsDebug->GetPC());
Core_RequestCPUStep(CPUStepType::Over, stepSize);
}
if (ImGui::IsKeyPressed(ImGuiKey_F11)) {
u32 stepSize = disasmView_.getInstructionSizeAt(mipsDebug->GetPC());
Core_RequestCPUStep(CPUStepType::Into, stepSize);
}
}
if (coreState == CORE_STEPPING_GE || coreState == CORE_RUNNING_GE) {
ImGui::Text("!!! Currently stepping the GE");
ImGui::SameLine();
if (ImGui::SmallButton("Open Ge debugger")) {
cfg.geDebuggerOpen = true;
ImGui::SetWindowFocus("GE Debugger");
}
}
ImGui::BeginDisabled(coreState != CORE_STEPPING_CPU);
if (ImGui::SmallButton("Run")) {
Core_Resume();
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(coreState != CORE_RUNNING_CPU);
if (ImGui::SmallButton("Pause")) {
Core_Break("Pause");
}
ImGui::EndDisabled();
ImGui::BeginDisabled(coreState != CORE_STEPPING_CPU);
ImGui::SameLine();
ImGui::Text("Step: ");
ImGui::SameLine();
if (ImGui::SmallButton("Into")) {
u32 stepSize = disasmView_.getInstructionSizeAt(mipsDebug->GetPC());
Core_RequestCPUStep(CPUStepType::Into, stepSize);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("F11");
}
ImGui::SameLine();
if (ImGui::SmallButton("Over")) {
u32 stepSize = disasmView_.getInstructionSizeAt(mipsDebug->GetPC());
Core_RequestCPUStep(CPUStepType::Over, stepSize);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("F10");
}
ImGui::SameLine();
if (ImGui::SmallButton("Out")) {
Core_RequestCPUStep(CPUStepType::Out, 0);
}
/*
ImGui::SameLine();
if (ImGui::SmallButton("Frame")) {
Core_RequestCPUStep(CPUStepType::Frame, 0);
}*/
ImGui::SameLine();
if (ImGui::SmallButton("Syscall")) {
hleDebugBreak();
Core_Resume();
}
ImGui::SameLine();
ImGui::SmallButton("Skim");
if (ImGui::IsItemActive()) {
u32 stepSize = disasmView_.getInstructionSizeAt(mipsDebug->GetPC());
Core_RequestCPUStep(CPUStepType::Into, stepSize);
}
ImGui::EndDisabled();
ImGui::SameLine();
if (ImGui::SmallButton("Goto PC")) {
disasmView_.GotoPC();
}
ImGui::SameLine();
if (ImGui::SmallButton("Goto RA")) {
disasmView_.GotoRA();
}
if (ImGui::BeginPopup("disSearch")) {
if (ImGui::IsWindowAppearing()) {
ImGui::SetKeyboardFocusHere();
}
if (ImGui::InputText("Search", searchTerm_, sizeof(searchTerm_), ImGuiInputTextFlags_EnterReturnsTrue)) {
disasmView_.Search(searchTerm_);
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::SameLine();
if (ImGui::SmallButton("Search")) {
// Open a small popup
ImGui::OpenPopup("disSearch");
ImGui::Shortcut(ImGuiKey_F | ImGuiMod_Ctrl);
}
ImGui::SameLine();
if (ImGui::SmallButton("Next")) {
disasmView_.SearchNext(true);
}
ImGui::SameLine();
if (ImGui::SmallButton("Settings")) {
ImGui::OpenPopup("disSettings");
}
if (ImGui::BeginPopup("disSettings")) {
ImGui::Checkbox("Follow PC", &disasmView_.followPC_);
ImGui::EndPopup();
}
ImGui::SetNextItemWidth(100);
if (ImGui::InputScalar("Go to addr: ", ImGuiDataType_U32, &gotoAddr_, NULL, NULL, "%08X")) {
disasmView_.setCurAddress(gotoAddr_);
disasmView_.scrollAddressIntoView();
}
ImGui::SameLine();
if (ImGui::Button("Go")) {
disasmView_.setCurAddress(gotoAddr_);
disasmView_.scrollAddressIntoView();
}
ImVec2 avail = ImGui::GetContentRegionAvail();
avail.y -= ImGui::GetTextLineHeightWithSpacing();
if (ImGui::BeginChild("left", ImVec2(150.0f, avail.y), ImGuiChildFlags_ResizeX)) {
if (symCache_.empty() || symsDirty_) {
symCache_ = g_symbolMap->GetAllSymbols(SymbolType::ST_FUNCTION);
symsDirty_ = false;
}
if (selectedSymbol_ >= 0 && selectedSymbol_ < symCache_.size()) {
auto &sym = symCache_[selectedSymbol_];
if (ImGui::TreeNode("Edit Symbol", "Edit %s", sym.name.c_str())) {
if (ImGui::InputText("Name", selectedSymbolName_, sizeof(selectedSymbolName_), ImGuiInputTextFlags_EnterReturnsTrue)) {
g_symbolMap->SetLabelName(selectedSymbolName_, sym.address);
symsDirty_ = true;
}
ImGui::Text("%08x (size: %0d)", sym.address, sym.size);
ImGui::TreePop();
}
}
if (ImGui::BeginListBox("##symbols", ImGui::GetContentRegionAvail())) {
ImGuiListClipper clipper;
clipper.Begin((int)symCache_.size(), -1);
while (clipper.Step()) {
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
if (ImGui::Selectable(symCache_[i].name.c_str(), selectedSymbol_ == i)) {
disasmView_.gotoAddr(symCache_[i].address);
disasmView_.scrollAddressIntoView();
truncate_cpy(selectedSymbolName_, symCache_[i].name.c_str());
selectedSymbol_ = i;
}
}
}
clipper.End();
ImGui::EndListBox();
}
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("right", ImVec2(0.0f, avail.y))) {
disasmView_.Draw(ImGui::GetWindowDrawList(), control);
}
ImGui::EndChild();
StatusBar(disasmView_.StatusBarText());
ImGui::End();
}
Path ImDebugger::ConfigPath() {
return GetSysDirectory(DIRECTORY_SYSTEM) / "imdebugger.ini";
}
// TODO: Move this into the main config at some point.
// But, I don't really want Core to know about the ImDebugger..
void ImConfig::LoadConfig(const Path &iniFile) {
IniFile ini;
ini.Load(iniFile); // Ignore return value, might not exist yet. In that case we'll end up loading defaults.
SyncConfig(&ini, false);
}
void ImConfig::SaveConfig(const Path &iniFile) {
IniFile ini;
ini.Load(iniFile); // ignore return value, might not exist yet
SyncConfig(&ini, true);
ini.Save(iniFile);
}
class Syncer {
public:
explicit Syncer(bool save) : save_(save) {}
void SetSection(Section *section) { section_ = section; }
template<class T>
void Sync(std::string_view key, T *value, T defaultValue) {
if (save_) {
section_->Set(key, *value);
} else {
section_->Get(key, value, defaultValue);
}
}
private:
Section *section_ = nullptr;
bool save_;
};
void ImConfig::SyncConfig(IniFile *ini, bool save) {
Syncer sync(save);
sync.SetSection(ini->GetOrCreateSection("Windows"));
sync.Sync("disasmOpen", &disasmOpen, true);
sync.Sync("demoOpen ", &demoOpen, false);
sync.Sync("gprOpen", &gprOpen, false);
sync.Sync("fprOpen", &fprOpen, false);
sync.Sync("vfpuOpen", &vfpuOpen, false);
sync.Sync("threadsOpen", &threadsOpen, false);
sync.Sync("callstackOpen", &callstackOpen, false);
sync.Sync("breakpointsOpen", &breakpointsOpen, false);
sync.Sync("modulesOpen", &modulesOpen, false);
sync.Sync("hleModulesOpen", &hleModulesOpen, false);
sync.Sync("audioDecodersOpen", &audioDecodersOpen, false);
sync.Sync("structViewerOpen", &structViewerOpen, false);
sync.Sync("framebuffersOpen", &framebuffersOpen, false);
sync.Sync("displayOpen", &displayOpen, true);
sync.Sync("styleEditorOpen", &styleEditorOpen, false);
sync.Sync("filesystemBrowserOpen", &filesystemBrowserOpen, false);
sync.Sync("kernelObjectsOpen", &kernelObjectsOpen, false);
sync.Sync("audioChannelsOpen", &audioChannelsOpen, false);
sync.Sync("texturesOpen", &texturesOpen, false);
sync.Sync("debugStatsOpen", &debugStatsOpen, false);
sync.Sync("geDebuggerOpen", &geDebuggerOpen, false);
sync.Sync("geStateOpen", &geStateOpen, false);
sync.Sync("schedulerOpen", &schedulerOpen, false);
sync.Sync("socketsOpen", &socketsOpen, false);
sync.Sync("npOpen", &npOpen, false);
sync.Sync("pixelViewerOpen", &pixelViewerOpen, false);
for (int i = 0; i < 4; i++) {
char name[64];
snprintf(name, sizeof(name), "memory%dOpen", i + 1);
sync.Sync(name, &memViewOpen[i], false);
}
sync.SetSection(ini->GetOrCreateSection("Settings"));
sync.Sync("displayLatched", &displayLatched, false);
sync.Sync("realtimePixelPreview", &realtimePixelPreview, false);
}