Files
ppsspp/UI/ImDebugger/ImDebugger.cpp
T
Henrik RydgårdandClaude Opus 5 0c510ca62e Add BreakpointManager::ChangeBreakPointAddress, use it from the ImDebugger
ChangeBreakPointAddress() moves the breakpoint keeping its action, condition and
log format, invalidates both ends, refuses to land on an existing breakpoint,
and resets the hit count since it belonged to the old address. The edit form now
works on a copy of the address and commits on deactivation rather than per
keystroke, so typing one address doesn't churn through every prefix of it.

The breakpoint edit form assigned straight to bp.addr and then invalidated the
icache at "bp.addr - 4, 8" - which by then is the *new* address - need both.

Also clear the selection after Delete in both edit forms - the reference into
the vector is dangling from that point on. Harmless today, but only because
nothing happens to touch it below.

Covered by a new Breakpoints unit test (verified to fail without the duplicate
check and the hit reset).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 00:28:39 +02:00

3078 lines
102 KiB
C++

#include <algorithm>
#include "ext/imgui/imgui_internal.h"
#include "ext/imgui/imgui_extras.h"
#include "Common/StringUtils.h"
#include "Common/File/FileUtil.h"
#include "Common/Data/Format/IniFile.h"
#include "Common/Data/Text/StringWriter.h"
#include "Common/Log/LogManager.h"
#include "Common/TimeUtil.h"
#include "Common/Data/Text/I18n.h"
#include "Core/RetroAchievements.h"
#include "Core/Config.h"
#include "Core/System.h"
#include "Core/SaveState.h"
#include "Core/WebServer.h"
#include "Core/Debugger/MemBlockInfo.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/sceKernelModule.h"
#include "Core/HLE/sceAac.h"
#include "Core/HLE/sceMpeg.h"
#include "Core/HLE/sceNp.h"
#include "Core/HLE/sceNet.h"
#include "Core/HLE/sceNetApctl.h"
#include "Core/HLE/sceNetAdhoc.h"
#include "Core/HLE/proAdhoc.h"
#include "Core/HLE/sceNetAdhocMatching.h"
#include "Core/HLE/NetAdhocCommon.h"
#include "Common/System/Request.h"
#include "Core/Util/AtracTrack.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/HLE/sceSas.h"
#include "Core/HW/SasAudio.h"
#include "Core/HW/Display.h"
#include "Core/Dialog/PSPSaveDialog.h"
#include "Core/CoreTiming.h"
// Threads window
#include "Core/HLE/sceKernelThread.h"
// Callstack window
#include "Core/MIPS/MIPSStackWalk.h"
// GPU things
#include "GPU/GPUCommon.h"
#include "GPU/Debugger/Stepping.h"
#include "UI/ImDebugger/ImDebugger.h"
#include "UI/ImDebugger/ImGe.h"
#include "UI/AudioCommon.h"
#include "UI/GameInfoCache.h"
#include "UI/PauseScreen.h"
extern bool g_TakeScreenshot;
static ImVec4 g_normalTextColor;
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 more
// Left click performs the preferred action, if any. Right click opens a menu for more.
void ImClickableValue(const char *id, uint32_t value, ImControl &control, ImCmd cmd) {
ImGui::PushID(id);
bool validAddr = Memory::IsValidAddress(value);
char temp[32];
snprintf(temp, sizeof(temp), "%08x", value);
if (ImGui::Selectable(temp) && validAddr) {
control.command = { cmd, value };
}
// Create a right-click popup menu. Restore the color while it's up. NOTE: can't query the theme, pushcolor modifies it!
ImGui::PushStyleColor(ImGuiCol_Text, g_normalTextColor);
if (ImGui::BeginPopupContextItem(temp)) {
if (ImGui::MenuItem(validAddr ? "Copy address to clipboard" : "Copy value to clipboard")) {
System_CopyStringToClipboard(temp);
}
if (validAddr) {
ImGui::Separator();
ShowInWindowMenuItems(value, control);
}
ImGui::EndPopup();
}
ImGui::PopStyleColor();
ImGui::PopID();
}
// Left click performs the preferred action, if any. Right click opens a menu for more.
void ImClickableValueFloat(const char *id, float value) {
ImGui::PushID(id);
char temp[32];
snprintf(temp, sizeof(temp), "%0.7f", value);
if (ImGui::Selectable(temp)) {}
// Create a right-click popup menu. Restore the color while it's up. NOTE: can't query the theme, pushcolor modifies it!
ImGui::PushStyleColor(ImGuiCol_Text, g_normalTextColor);
if (ImGui::BeginPopupContextItem(temp)) {
if (ImGui::MenuItem("Copy value to clipboard")) {
System_CopyStringToClipboard(temp);
}
ImGui::EndPopup();
}
ImGui::PopStyleColor();
ImGui::PopID();
}
void DrawTimeView(ImConfig &cfg) {
ImGui::SetNextWindowSize(ImVec2(420, 300), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Time", &cfg.timeOpen)) {
ImGui::End();
return;
}
// Display timing
if (ImGui::CollapsingHeader("Display Timing", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Text("Num VBlanks: %d", __DisplayGetNumVblanks());
ImGui::Text("FlipCount: %d", __DisplayGetFlipCount());
ImGui::Text("VCount: %d", __DisplayGetVCount());
ImGui::Text("HCount cur: %d accum: %d", __DisplayGetCurrentHcount(), __DisplayGetAccumulatedHcount());
ImGui::Text("IsVblank: %d", DisplayIsVblank());
}
// RTC
if (ImGui::CollapsingHeader("RTC", ImGuiTreeNodeFlags_DefaultOpen)) {
PSPTimeval tv;
__RtcTimeOfDay(&tv);
ImGui::Text("RtcTimeOfDay: %d.%06d", tv.tv_sec, tv.tv_usec);
ImGui::Text("RtcGetCurrentTick: %lld", (long long)__RtcGetCurrentTick());
}
ImGui::End();
}
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(currentMIPS);
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 DescribeGPRValue(const MIPSDebugInterface *mipsDebug, u32 value, char *buffer, size_t bufferSize) {
if (Memory::IsValidAddress(value)) {
DescribeAddress(mipsDebug, value, buffer, bufferSize);
} else {
snprintf(buffer, bufferSize, "(value)");
}
}
static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) {
ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("GPRs", &config.gprOpen)) {
ImGui::End();
return;
}
bool noDiff = coreState == CORE_RUNNING_CPU || coreState == CORE_STEPPING_GE;
if (ImGui::Button("Copy all to clipboard")) {
char *buffer = new char[20000];
StringWriter w(buffer, 20000);
for (int i = 0; i < 32; i++) {
u32 value = mipsDebug->GetGPR32Value(i);
w.F("%s: %08x (%d)", mipsDebug->GetRegName(0, i).c_str(), value, value).endl();
}
w.F("hi: %08x", mipsDebug->GetHi()).endl();
w.F("lo: %08x", mipsDebug->GetLo()).endl();
w.F("pc: %08x", mipsDebug->GetPC()).endl();
System_CopyStringToClipboard(buffer);
delete[] buffer;
}
if (ImGui::BeginTable("gpr", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Reg", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Dec", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Desc", 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));
}
// TODO: Check if the address is in the code segment to decide default action.
ImClickableValue(regname, value, control, index == MIPS_REG_RA ? ImCmd::SHOW_IN_CPU_DISASM : ImCmd::SHOW_IN_MEMORY_VIEWER);
ImGui::TableNextColumn();
if (value >= -1000000 && value <= 1000000) {
ImGui::Text("%d", value);
}
ImGui::TableNextColumn();
char temp[256];
DescribeGPRValue(mipsDebug, value, temp, sizeof(temp));
ImGui::TextUnformatted(temp);
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("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();
ImClickableValueFloat(mipsDebug->GetRegName(1, i).c_str(), 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("VFPU", &config.vfpuOpen)) {
ImGui::End();
return;
}
ImGui::Text("TODO");
ImGui::End();
}
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;
case WAITTYPE_CTRL:
snprintf(buffer, bufSize, "ctrl: %d", waitID);
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::PushID(i);
ImGui::TableNextColumn();
ImGui::Text("%d", thread.id);
ImGui::TableNextColumn();
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();
ImClickableValue("curpc", thread.curPC, control, ImCmd::SHOW_IN_CPU_DISASM);
ImGui::TableNextColumn();
ImClickableValue("entry", 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(WaitTypeToString(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();
}
void DrawParamSFO(ImConfig &cfg, ImControl &control) {
ImGui::SetNextWindowSize(ImVec2(420, 300), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("PARAMS.SFO", &cfg.paramSFOOpen)) {
ImGui::End();
return;
}
auto renderParamSFOTable = [](ParamSFOData &data) {
const auto &map = data.Values();
if (System_GetPropertyBool(SYSPROP_HAS_TEXT_CLIPBOARD) && ImGui::Button("Copy to clipboard")) {
char buffer[4096];
StringWriter w(buffer);
for (auto &[key, value] : map) {
switch (value.type) {
case ParamSFOData::VT_UTF8:
w.F("%s: %s\n", key.c_str(), value.s_value.c_str());
break;
case ParamSFOData::VT_UTF8_SPE:
w.F("%s: %d raw bytes\n", key.c_str(), (int)value.s_value.size());
break;
case ParamSFOData::VT_INT:
w.F("%s: %d\n", key.c_str(), value.i_value);
break;
default:
break;
}
}
System_CopyStringToClipboard(w.as_view());
}
if (ImGui::BeginTable("paramsfo", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH | ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthFixed, 120.0f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 120.f);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed, 50.f);
ImGui::TableHeadersRow();
for (auto &[key, value] : map) {
ImGui::TableNextRow();
ImGui::PushID(key.c_str());
ImGui::TableNextColumn();
ImGui::Text("%s", key.c_str());
ImGui::TableNextColumn();
switch (value.type) {
case ParamSFOData::VT_UTF8:
ImGui::Text("%s", value.s_value.c_str());
break;
case ParamSFOData::VT_UTF8_SPE:
ImGui::Text("%d raw bytes", (int)value.s_value.size());
break;
case ParamSFOData::VT_INT:
ImGui::Text("%d", value.i_value);
break;
default:
ImGui::TextUnformatted("N/A");
break;
}
ImGui::TableNextColumn();
ImGui::Text("%s", ParamSFOData::ValueTypeToString(value.type));
ImGui::PopID();
}
ImGui::EndTable();
}
};
if (ImGui::BeginTabBar("ParamSFOTabs")) {
if (ImGui::BeginTabItem("Active")) {
renderParamSFOTable(g_paramSFO);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Original")) {
renderParamSFOTable(g_paramSFORaw);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("GameInfo")) {
Path path = PSP_CoreParameter().fileToStart;
std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, path, GameInfoFlags::PARAM_SFO);
if (info && ImGui::BeginTable("paramsfo", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH | ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("Key", ImGuiTableColumnFlags_WidthFixed, 140.0f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 270.0f);
ImGui::TableHeadersRow();
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted("Title");
ImGui::TableNextColumn();
ImGui::TextUnformatted(info->GetTitle());
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted("Path");
ImGui::TableNextColumn();
ImGui::TextUnformatted(path.ToVisualString());
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted("Detected type");
ImGui::TableNextColumn();
ImGui::TextUnformatted(IdentifiedFileTypeToString(info->fileType));
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted("Detected region");
ImGui::TableNextColumn();
ImGui::TextUnformatted(GameRegionToString(info->region));
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted("HasConfig");
ImGui::TableNextColumn();
ImGui::TextUnformatted(BoolStr(info->hasConfig));
ImGui::EndTable();
}
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::End();
}
// TODO: Add popup menu, export file, export dir, etc...
static void RecurseFileSystem(IFileSystem *fs, std::string path, RequesterToken token) {
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, token);
ImGui::TreePop();
}
} else {
ImGui::Selectable(file.name.c_str());
if (ImGui::BeginPopupContextItem()) {
if (ImGui::MenuItem("Copy Path")) {
System_CopyStringToClipboard(path + "/" + file.name);
}
if (ImGui::MenuItem("Save file...")) {
std::string fullPath = path + "/" + file.name;
int size = file.size;
// save dialog
System_BrowseForFileSave(token, "Save file", file.name, BrowseFileType::ANY, [fullPath, fs, size](std::string_view responseString, int) {
int fd = fs->OpenFile(fullPath, FILEACCESS_READ);
if (fd >= 0) {
std::string data;
data.resize(size);
fs->ReadFile(fd, (u8 *)data.data(), size);
fs->CloseFile(fd);
Path dest(responseString);
File::WriteDataToFile(false, data.data(), data.size(), dest);
}
});
}
// your popup code
ImGui::EndPopup();
}
}
}
}
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, cfg.requesterToken);
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.selectedKernelObject == i, ImGuiSelectableFlags_AllowDoubleClick | ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedKernelObject = id;
}
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();
}
if (kernelObjects.IsValid(cfg.selectedKernelObject)) {
const int id = cfg.selectedKernelObject;
KernelObject *obj = kernelObjects.GetFast<KernelObject>(id);
if (obj) {
ImGui::Text("%08x: %s", id, obj->GetTypeName());
char longInfo[4096];
obj->GetLongInfo(longInfo, sizeof(longInfo));
ImGui::TextUnformatted(longInfo);
}
// TODO: Show details
}
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 DrawApctl(ImConfig &cfg) {
if (!ImGui::Begin("Apctl", &cfg.apctlOpen)) {
ImGui::End();
return;
}
ImGui::Text("State: %s", ApctlStateToString(netApctlState));
if (netApctlState != PSP_NET_APCTL_STATE_DISCONNECTED && ImGui::CollapsingHeader("ApCtl Details")) {
ImGui::Text("Name: %s", netApctlInfo.name);
ImGui::Text("IP: %s", netApctlInfo.ip);
ImGui::Text("SubnetMask: %s", netApctlInfo.ip);
ImGui::Text("SSID: %.*s", netApctlInfo.ssidLength, netApctlInfo.ssid);
ImGui::Text("SSID: %.*s", netApctlInfo.ssidLength, netApctlInfo.ssid);
ImGui::Text("Gateway: %s", netApctlInfo.gateway);
ImGui::Text("PrimaryDNS: %s", netApctlInfo.primaryDns);
ImGui::Text("SecondaryDNS: %s", netApctlInfo.secondaryDns);
}
if (g_Config.bInfrastructureAutoDNS) {
const InfraDNSConfig &dnsConfig = GetInfraDNSConfig();
if (dnsConfig.loaded) {
if (!dnsConfig.gameName.empty()) {
ImGui::Text("Known game: %s", dnsConfig.gameName.c_str());
}
ImGui::Text("connectAdhocForGrouping: %s", BoolStr(dnsConfig.connectAdHocForGrouping));
ImGui::Text("DNS: %s", dnsConfig.dns.c_str());
if (!dnsConfig.dyn_dns.empty()) {
ImGui::Text("DynDNS: %s", dnsConfig.dyn_dns.c_str());
}
if (!dnsConfig.fixedDNS.empty()) {
ImGui::TextUnformatted("Fixed DNS");
for (auto iter : dnsConfig.fixedDNS) {
ImGui::Text("%s -> %s", iter.first.c_str(), iter.second.c_str());
}
}
} else {
ImGui::TextUnformatted("(InfraDNSConfig not loaded)");
}
}
ImGui::End();
}
static void DrawInternals(ImConfig &cfg) {
if (!ImGui::Begin("PPSSPP Internals", &cfg.internalsOpen)) {
ImGui::End();
return;
}
struct entry {
PSPDirectories dir;
const char *name;
};
static const entry dirs[] = {
{DIRECTORY_PSP, "PSP"},
{DIRECTORY_CHEATS, "CHEATS"},
{DIRECTORY_SCREENSHOT, "SCREENSHOT"},
{DIRECTORY_SYSTEM, "SYSTEM"},
{DIRECTORY_GAME, "GAME"},
{DIRECTORY_SAVEDATA, "SAVEDATA"},
{DIRECTORY_PAUTH, "PAUTH"},
{DIRECTORY_DUMP, "DUMP"},
{DIRECTORY_SAVESTATE, "SAVESTATE"},
{DIRECTORY_CACHE, "CACHE"},
{DIRECTORY_TEXTURES, "TEXTURES"},
{DIRECTORY_PLUGINS, "PLUGINS"},
{DIRECTORY_APP_CACHE, "APP_CACHE"},
{DIRECTORY_VIDEO, "VIDEO"},
{DIRECTORY_AUDIO, "AUDIO"},
{DIRECTORY_MEMSTICK_ROOT, "MEMSTICK_ROOT"},
{DIRECTORY_EXDATA, "EXDATA"},
{DIRECTORY_CUSTOM_SHADERS, "CUSTOM_SHADERS"},
{DIRECTORY_CUSTOM_THEMES, "CUSTOM_THEMES"},
};
if (ImGui::CollapsingHeader("GetSysDirectory")) {
for (auto &dir : dirs) {
ImGui::Text("%s: %s", dir.name, GetSysDirectory(dir.dir).c_str());
}
}
if (ImGui::CollapsingHeader("Memory")) {
ImGui::Text("Base pointer: %p", Memory::base);
ImGui::Text("Main memory size: %08x", Memory::g_MemorySize);
if (MIPSComp::jit) {
const JitBlockCache *bc = MIPSComp::jit->GetBlockCache();
ImGui::Text("JIT block cache: %p", bc->GetBlock(0));
ImGui::Text("JIT code cache: %p", MIPSComp::jit->GetCodeBase());
ImGui::Text("MIPS state: %p", currentMIPS);
}
if (ImGui::Button("Copy to clipboard")) {
System_CopyStringToClipboard(StringFromFormat("0x%p", Memory::base));
}
}
if (ImGui::CollapsingHeader("ImGui state")) {
const auto &io = ImGui::GetIO();
ImGui::Text("WantCaptureMouse: %s", BoolStr(io.WantCaptureMouse));
ImGui::Text("WantCaptureKeyboard: %s", BoolStr(io.WantCaptureKeyboard));
ImGui::Text("WantCaptureMouseUnlessPopupClose: %s", BoolStr(io.WantCaptureMouseUnlessPopupClose));
ImGui::Text("WantTextInput: %s", BoolStr(io.WantTextInput));
}
if (ImGui::CollapsingHeader("Save detection")) {
ImGui::Text("Last in-game save/load: %0.1f seconds ago", SecondsSinceLastGameSave());
ImGui::Text("Last save/load state: %0.1f seconds ago", SaveState::SecondsSinceLastSavestate());
}
ImGui::End();
}
static void DrawAdhoc(ImConfig &cfg) {
if (!ImGui::Begin("AdHoc", &cfg.adhocOpen)) {
ImGui::End();
return;
}
const char *discoverStatusStr = "N/A";
switch (netAdhocDiscoverStatus) {
case 0: discoverStatusStr = "NONE"; break;
case 1: discoverStatusStr = "IN_PROGRESS"; break;
case 2: discoverStatusStr = "COMPLETED"; break;
default: break;
}
ImGui::Text("sceNetAdhoc inited: %s", BoolStr(netAdhocInited));
ImGui::Text("sceNetAdhocctl inited: %s", BoolStr(netAdhocctlInited));
ImGui::Text("sceNetAdhocctl state: %s", AdhocCtlStateToString(NetAdhocctl_GetState()));
ImGui::Text("sceNetAdhocMatching inited: %s", BoolStr(netAdhocctlInited));
ImGui::Text("GameMode entered: %s", BoolStr(netAdhocGameModeEntered));
ImGui::Text("FriendFinder running: %s", BoolStr(g_adhocServerConnected));
ImGui::Text("sceNetAdhocDiscover status: %s", discoverStatusStr);
if (ImGui::BeginTable("sock", 5, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH | ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Non-blocking", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("BufSize", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("IsClient", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
for (int i = 0; i < MAX_SOCKET; i++) {
const AdhocSocket *socket = adhocSockets[i];
if (!socket) {
continue;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%d", i + 1);
ImGui::TableNextColumn();
switch (socket->type) {
case SOCK_PDP: ImGui::TextUnformatted("PDP"); break;
case SOCK_PTP: ImGui::TextUnformatted("PTP"); break;
default: ImGui::Text("(%d)", socket->type); break;
}
ImGui::TableNextColumn();
ImGui::TextUnformatted(socket->nonblocking ? "Non-blocking" : "Blocking");
ImGui::TableNextColumn();
ImGui::Text("%d", socket->buffer_size);
ImGui::TableNextColumn();
ImGui::TextUnformatted(BoolStr(socket->isClient));
}
ImGui::EndTable();
}
ImGui::End();
}
static void DrawSockets(ImConfig &cfg) {
if (!ImGui::Begin("Sockets", &cfg.socketsOpen)) {
ImGui::End();
return;
}
if (ImGui::BeginTable("sock", 9, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH | ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Port", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("IP address", 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::TableSetupColumn("Host handle", ImGuiTableColumnFlags_WidthFixed);
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", inetSocket->port);
ImGui::TableNextColumn();
ImGui::TextUnformatted(inetSocket->addr.c_str());
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::Text("%d", (int)inetSocket->sock);
}
ImGui::EndTable();
}
ImGui::End();
}
static const char *MemCheckConditionToString(MemCheckCondition cond) {
// (int) casting to avoid "case not in enum" warnings
switch ((int)cond) {
case (int)MEMCHECK_READ: return "Read";
case (int)MEMCHECK_WRITE: return "Write";
case (int)MEMCHECK_READWRITE: return "Read/Write";
case (int)(MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE): return "Write Change";
case (int)(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); // Really means action is to pause
ImGui::TableSetupColumn("Log", ImGuiTableColumnFlags_WidthFixed); // Really means action is to pause
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++) {
BreakPoint &bp = bps[i];
bool temp = bp.temporary;
if (temp) {
continue;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(i);
// DONE: This clashes with the checkbox!
// TODO: Test to make sure this works properly
if (ImGui::Selectable("", cfg.selectedBreakpoint == i, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap) && !bp.temporary) {
cfg.selectedBreakpoint = i;
cfg.selectedMemCheck = -1;
cfg.selectedRegBreakpoint = -1;
}
ImGui::SameLine();
ImGui::CheckboxFlags("##enabled", (int *)&bp.action, (int)BREAK_ACTION_PAUSE);
ImGui::TableNextColumn();
ImGui::CheckboxFlags("##log", (int *)&bp.action, (int)BREAK_ACTION_LOG);
ImGui::TableNextColumn();
ImGui::TextUnformatted(bp.temporary ? "Temp" : "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("##memcheck", cfg.selectedMemCheck == i, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) {
cfg.selectedBreakpoint = -1;
cfg.selectedMemCheck = i;
cfg.selectedRegBreakpoint = -1;
}
ImGui::SameLine();
ImGui::CheckboxFlags("##enabled", (int *)&mc.action, (int)BREAK_ACTION_PAUSE);
ImGui::TableNextColumn();
ImGui::CheckboxFlags("##log", (int *)&mc.action, (int)BREAK_ACTION_LOG);
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();
}
// Finally, list register breakpoints.
for (auto &rbp : g_breakpoints.GetRegBreakpoints()) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(&rbp - &g_breakpoints.GetRegBreakpoints()[0] + 20000);
if (ImGui::Selectable("##regbp", cfg.selectedRegBreakpoint == &rbp - &g_breakpoints.GetRegBreakpoints()[0], ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) {
cfg.selectedBreakpoint = -1;
cfg.selectedMemCheck = -1;
cfg.selectedRegBreakpoint = &rbp - &g_breakpoints.GetRegBreakpoints()[0];
}
ImGui::SameLine();
ImGui::CheckboxFlags("", (int *)&rbp.result, BREAK_ACTION_PAUSE);
ImGui::TableNextColumn();
ImGui::TextUnformatted("Reg");
ImGui::TableNextColumn();
ImGui::TextUnformatted(mipsDebug->GetRegName(0, rbp.reg).c_str());
ImGui::TableNextColumn();
ImGui::TextUnformatted("-"); // size/label
ImGui::TableNextColumn();
ImGui::TextUnformatted("-"); // opcode
ImGui::TableNextColumn();
if (rbp.hasCond) {
ImGui::TextUnformatted(rbp.cond.expressionString.c_str());
} else {
ImGui::TextUnformatted("-"); // condition
}
ImGui::TableNextColumn();
ImGui::Text("%d", rbp.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");
// No cache invalidation needed for these two: the JIT emits its call for any address
// that has a breakpoint at all, and the action bits are read live in
// ExecBreakPoint(). Only the set of addresses affects generated code.
ImGui::CheckboxFlags("Enabled", (int *)&bp.action, (int)BREAK_ACTION_PAUSE);
ImGui::CheckboxFlags("Log", (int *)&bp.action, (int)BREAK_ACTION_LOG);
// Moving a breakpoint isn't just an assignment to bp.addr - the compiled code at
// both the old and the new address has to be invalidated, and landing on top of an
// existing breakpoint has to be refused. Edit a copy and let the manager do it.
// Committing on deactivation rather than per keystroke also avoids doing all that
// for each of the intermediate addresses you pass through while typing one.
u32 addr = bp.addr;
ImGui::InputScalar("Address", ImGuiDataType_U32, &addr, nullptr, nullptr, "%08x", ImGuiInputTextFlags_CharsHexadecimal);
if (ImGui::IsItemDeactivatedAfterEdit()) {
g_breakpoints.ChangeBreakPointAddress(bp.addr, addr);
}
if (ImGui::Button("Delete")) {
g_breakpoints.RemoveBreakPoint(bp.addr);
// bp (and bps) are dangling from here on - don't touch them again this frame.
cfg.selectedBreakpoint = -1;
}
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");
// Anything that edits mc in place has to end up calling NotifyChangedMemchecks(),
// see GetMemCheckRefs(). Note that Selectable() only returns true on the frame it's
// actually clicked, unlike BeginCombo(), which is true for every frame the popup
// stays open - so set this from the Selectables, not from the combo itself.
bool changed = false;
if (ImGui::BeginCombo("Condition", MemCheckConditionToString(mc.cond))) {
if (ImGui::Selectable("Read", mc.cond == MemCheckCondition::MEMCHECK_READ)) {
mc.cond = MemCheckCondition::MEMCHECK_READ;
changed = true;
}
if (ImGui::Selectable("Write", mc.cond == MemCheckCondition::MEMCHECK_WRITE)) {
mc.cond = MemCheckCondition::MEMCHECK_WRITE;
changed = true;
}
if (ImGui::Selectable("Read / Write", mc.cond == MemCheckCondition::MEMCHECK_READWRITE)) {
mc.cond = MemCheckCondition::MEMCHECK_READWRITE;
changed = true;
}
if (ImGui::Selectable("Write On Change", mc.cond == MemCheckCondition::MEMCHECK_WRITE_ONCHANGE)) {
mc.cond = MemCheckCondition::MEMCHECK_WRITE_ONCHANGE;
changed = true;
}
ImGui::EndCombo();
}
// The cached ranges don't carry the action bits (they're read live in ExecMemCheck),
// so toggling this doesn't strictly need a rebuild - but notify anyway rather than
// leaving a silent exception to the rule above for the next person to rediscover.
if (ImGui::CheckboxFlags("Enabled", (int *)&mc.action, (int)BREAK_ACTION_PAUSE)) {
changed = true;
}
if (ImGui::InputScalar("Start", ImGuiDataType_U32, &mc.start, NULL, NULL, "%08x", ImGuiInputTextFlags_CharsHexadecimal)) {
changed = true;
}
if (ImGui::InputScalar("End", ImGuiDataType_U32, &mc.end, NULL, NULL, "%08x", ImGuiInputTextFlags_CharsHexadecimal)) {
changed = true;
}
if (changed) {
g_breakpoints.NotifyChangedMemchecks();
}
if (ImGui::Button("Delete")) {
g_breakpoints.RemoveMemCheck(mc.start, mc.end);
// mc (and mcs) are dangling from here on - don't touch them again this frame.
cfg.selectedMemCheck = -1;
}
ImGui::EndChild();
}
}
ImGui::EndTable();
}
ImGui::End();
}
void DrawMediaDecodersView(ImConfig &cfg, ImControl &control) {
if (!ImGui::Begin("Media decoding contexts", &cfg.mediaDecodersOpen)) {
ImGui::End();
return;
}
const std::map<u32, MpegContext *> &mpegCtxs = __MpegGetContexts();
if (ImGui::CollapsingHeaderWithCount("sceMpeg", (int)mpegCtxs.size())) {
if (ImGui::BeginTable("mpegs", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Addr", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("VFrames", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
for (auto iter : mpegCtxs) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(iter.first);
ImGui::SetNextItemAllowOverlap();
char temp[16];
snprintf(temp, sizeof(temp), "%08x", iter.first);
if (ImGui::Selectable(temp, iter.first == cfg.selectedMpegCtx, ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedMpegCtx = iter.first;
}
ImGui::TableNextColumn();
const MpegContext *ctx = iter.second;
if (!ctx) {
ImGui::TextUnformatted("N/A");
ImGui::PopID();
continue;
}
ImGui::Text("%d", ctx->videoFrameCount);
ImGui::PopID();
}
ImGui::EndTable();
}
auto iter = mpegCtxs.find(cfg.selectedMpegCtx);
if (iter != mpegCtxs.end()) {
const MpegContext *ctx = iter->second;
char temp[28];
snprintf(temp, sizeof(temp), "sceMpeg context at %08x", iter->first);
if (ctx && ImGui::CollapsingHeader(temp, ImGuiTreeNodeFlags_DefaultOpen)) {
// ImGui::ProgressBar((float)sas->CurPos() / (float)info.fileDataEnd, ImVec2(200.0f, 0.0f));
ImGui::Text("Mpeg version: %d raw: %08x", ctx->mpegVersion, ctx->mpegRawVersion);
ImGui::Text("Frame counts: Audio %d, video %d", ctx->audioFrameCount, ctx->videoFrameCount);
ImGui::Text("Video pixel mode: %d", ctx->videoPixelMode);
ImGui::Text("AVC status=%d width=%d height=%d result=%d", ctx->avc.avcFrameStatus, ctx->avc.avcDetailFrameWidth, ctx->avc.avcDetailFrameHeight, ctx->avc.avcDecodeResult);
ImGui::Text("Stream size: %d", ctx->mpegStreamSize);
}
}
}
// Count the active atrac contexts so we can display it.
const int maxAtracContexts = __AtracMaxContexts();
int atracCount = 0;
for (int i = 0; i < maxAtracContexts; i++) {
u32 type;
if (__AtracGetCtx(i, &type)) {
atracCount++;
}
}
if (ImGui::CollapsingHeaderWithCount("sceAtrac", atracCount, ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Checkbox("Force FFMPEG", &g_Config.bForceFfmpegForAudioDec);
if (ImGui::BeginTable("atracs", 8, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Index", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Mute", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Channels", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("CurSample", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("RemFrames", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Impl", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
for (int i = 0; i < maxAtracContexts; i++) {
u32 codecType = 0;
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(i);
ImGui::SetNextItemAllowOverlap();
char temp[16];
snprintf(temp, sizeof(temp), "%d", i);
if (ImGui::Selectable(temp, i == cfg.selectedAtracCtx, ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedAtracCtx = i;
}
ImGui::TableNextColumn();
bool *mutePtr = __AtracMuteFlag(i);
if (mutePtr) {
ImGui::Checkbox("", mutePtr);
}
ImGui::TableNextColumn();
const AtracBase *ctx = __AtracGetCtx(i, &codecType);
if (!ctx) {
// Nothing more we can display about uninitialized contexts.
ImGui::PopID();
continue;
}
switch (codecType) {
case 0:
ImGui::TextUnformatted("-"); // Uninitialized
break;
case PSP_CODEC_AT3PLUS:
ImGui::TextUnformatted("Atrac3+");
break;
case PSP_CODEC_AT3:
ImGui::TextUnformatted("Atrac3");
break;
default:
ImGui::Text("%04x", codecType);
break;
}
ImGui::TableNextColumn();
ImGui::TextUnformatted(AtracStatusToString(ctx->BufferState()));
ImGui::TableNextColumn();
ImGui::Text("in:%d out:%d", ctx->Channels(), ctx->GetOutputChannels());
ImGui::TableNextColumn();
if (AtracStatusIsNormal(ctx->BufferState())) {
int pos;
ctx->GetNextDecodePosition(&pos);
ImGui::Text("%d", pos);
} else {
ImGui::TextUnformatted("N/A");
}
ImGui::TableNextColumn();
if (AtracStatusIsNormal(ctx->BufferState())) {
ImGui::Text("%d", ctx->RemainingFrames());
} else {
ImGui::TextUnformatted("N/A");
}
ImGui::TableNextColumn();
ImGui::TextUnformatted(ctx->GetContextVersion() >= 2 ? "NewImpl" : "Legacy");
ImGui::PopID();
}
ImGui::EndTable();
}
if (cfg.selectedAtracCtx >= 0 && cfg.selectedAtracCtx < PSP_MAX_ATRAC_IDS) {
u32 type = 0;
const AtracBase *ctx = __AtracGetCtx(cfg.selectedAtracCtx, &type);
// Show details about the selected atrac context here.
char header[32];
snprintf(header, sizeof(header), "Atrac context %d", cfg.selectedAtracCtx);
if (ctx && ImGui::CollapsingHeader(header, ImGuiTreeNodeFlags_DefaultOpen)) {
bool isNormal = AtracStatusIsNormal(ctx->BufferState());
if (isNormal) {
int pos;
ctx->GetNextDecodePosition(&pos);
int endSample, loopStart, loopEnd;
ctx->GetSoundSample(&endSample, &loopStart, &loopEnd);
ImGui::ProgressBar((float)pos / (float)endSample, ImVec2(200.0f, 0.0f));
ImGui::Text("Status: %s", AtracStatusToString(ctx->BufferState()));
ImGui::Text("cur/end sample: %d/%d", pos, endSample);
}
if (ctx->context_.IsValid()) {
ImGui::Text("ctx addr: ");
ImGui::SameLine();
ImClickableValue("ctx", ctx->context_.ptr, control, ImCmd::SHOW_IN_MEMORY_VIEWER);
}
if (ctx->context_.IsValid() && ctx->GetContextVersion() >= 2) {
const auto &info = ctx->context_->info;
if (isNormal) {
ImGui::Text("Buffer: (size: %d / %08x) Frame: %d", info.bufferByte, info.bufferByte, info.sampleSize);
ImGui::SameLine();
ImClickableValue("buffer", info.buffer, control, ImCmd::SHOW_IN_MEMORY_VIEWER);
if (info.secondBuffer || info.secondBufferByte) {
ImGui::Text("Second: (size: %d / %08x)", info.secondBufferByte, info.secondBufferByte);
ImGui::SameLine();
ImClickableValue("second", info.secondBuffer, control, ImCmd::SHOW_IN_MEMORY_VIEWER);
}
ImGui::Text("Data: %d/%d", info.dataOff, info.fileDataEnd);
if (info.state != ATRAC_STATUS_STREAMED_WITHOUT_LOOP) {
ImGui::Text("LoopNum: %d (%d-%d)", info.loopNum, info.loopStart, info.loopEnd);
}
ImGui::Text("DecodePos: %d EndSample: %d", info.decodePos, info.fileDataEnd);
if (AtracStatusIsStreaming(info.state)) {
ImGui::Text("Stream: offset %d, streamDataBytes: %d", info.streamOff, info.streamDataByte);
}
ImGui::Text("numFrame: %d curBuffer: %d streamOff2: %d", info.numSkipFrames, info.curBuffer, info.secondStreamOff);
} else if (ctx->BufferState() == ATRAC_STATUS_FOR_SCESAS) {
// A different set of state!
const AtracSasStreamState *sas = ctx->StreamStateForSas();
if (sas) {
ImGui::ProgressBar((float)sas->CurPos() / (float)info.fileDataEnd, ImVec2(200.0f, 0.0f));
ImGui::ProgressBar((float)sas->streamOffset / (float)sas->bufSize[sas->curBuffer], ImVec2(200.0f, 0.0f));
ImGui::Text("Cur pos: %08x File offset: %08x File end: %08x%s", sas->CurPos(), sas->fileOffset, info.fileDataEnd, sas->fileOffset >= info.fileDataEnd ? " (END)" : "");
ImGui::Text("Second (next buffer): %08x (sz: %08x)", info.secondBuffer, info.secondBufferByte);
ImGui::Text("Cur buffer: %d (%08x, sz: %08x)", sas->curBuffer, sas->bufPtr[sas->curBuffer], sas->bufSize[sas->curBuffer]);
ImGui::Text("2nd buffer: %d (%08x, sz: %08x)", sas->curBuffer ^ 1, sas->bufPtr[sas->curBuffer ^ 1], sas->bufSize[sas->curBuffer ^ 1]);
ImGui::Text("Loop points: %08x, %08x", info.loopStart, info.loopEnd);
ImGui::TextUnformatted(sas->isStreaming ? "Streaming mode!" : "Non-streaming mode");
} else {
ImGui::Text("can't access sas state");
}
}
if (ctx->BufferState() == ATRAC_STATUS_ALL_DATA_LOADED) {
if (ImGui::Button("Save to disk...")) {
System_BrowseForFileSave(cfg.requesterToken, "Save AT3 file", "song.at3", BrowseFileType::ATRAC3, [&info](std::string_view filename, int) {
if (!Memory::IsValidRange(info.buffer, info.bufferByte)) {
return;
}
const u8 *data = Memory::GetPointerRangeOrException(info.buffer, info.bufferByte);
if (!data) {
return;
}
FILE *file = File::OpenCFile(Path(filename), "wb");
if (!file) {
return;
}
fwrite(data, 1, info.bufferByte, file);
fclose(file);
});
}
}
} else {
ImGui::Text("loop: %d", ctx->LoopNum());
}
}
}
}
if (ImGui::CollapsingHeaderWithCount("sceMp3", (int)g_mp3Map.size(), ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::BeginTable("mp3", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Handle", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Channels", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("ReadPos", ImGuiTableColumnFlags_WidthFixed);
for (auto &iter : g_mp3Map) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(iter.first);
ImGui::SetNextItemAllowOverlap();
char temp[16];
snprintf(temp, sizeof(temp), "%d", iter.first);
if (ImGui::Selectable(temp, iter.first == cfg.selectedMp3Ctx, ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedMp3Ctx = iter.first;
}
if (!iter.second) {
continue;
}
ImGui::TableNextColumn();
ImGui::Text("%d", iter.second->Channels);
ImGui::TableNextColumn();
ImGui::Text("%d", (int)iter.second->ReadPos());
ImGui::PopID();
}
ImGui::EndTable();
}
auto iter = g_mp3Map.find(cfg.selectedMp3Ctx);
if (iter != g_mp3Map.end() && ImGui::CollapsingHeader("MP3 %d", iter->first)) {
ImGui::Text("MP3 Context %d", iter->first);
if (iter->second) {
AuCtx *ctx = iter->second;
ImGui::Text("%d Hz, %d channels", ctx->SamplingRate, ctx->Channels);
ImGui::Text("AUBuf: %08x AUSize: %08x", ctx->AuBuf, ctx->AuBufSize);
ImGui::Text("PCMBuf: %08x PCMSize: %08x", ctx->PCMBuf, ctx->PCMBufSize);
ImGui::Text("Pos: %d (%d -> %d)", ctx->ReadPos(), (int)ctx->startPos, (int)ctx->endPos);
}
}
}
if (ImGui::CollapsingHeaderWithCount("sceAac", (int)g_aacMap.size(), ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::BeginTable("aac", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Handle", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Channels", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("ReadPos", ImGuiTableColumnFlags_WidthFixed);
for (auto &iter : g_aacMap) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(iter.first);
ImGui::SetNextItemAllowOverlap();
char temp[16];
snprintf(temp, sizeof(temp), "%d", iter.first);
if (ImGui::Selectable(temp, iter.first == cfg.selectedAacCtx, ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedAacCtx = iter.first;
}
if (!iter.second) {
continue;
}
ImGui::TableNextColumn();
ImGui::Text("%d", iter.second->Channels);
ImGui::TableNextColumn();
ImGui::Text("%d", (int)iter.second->ReadPos());
ImGui::PopID();
}
ImGui::EndTable();
}
auto iter = g_mp3Map.find(cfg.selectedAacCtx);
if (iter != g_mp3Map.end() && ImGui::CollapsingHeader("AAC %d", iter->first)) {
ImGui::Text("AAC Context %d", iter->first);
if (iter->second) {
AuCtx *ctx = iter->second;
ImGui::Text("%d Hz, %d channels", ctx->SamplingRate, ctx->Channels);
ImGui::Text("AUBuf: %08x AUSize: %08x", ctx->AuBuf, ctx->AuBufSize);
ImGui::Text("PCMBuf: %08x PCMSize: %08x", ctx->PCMBuf, ctx->PCMBufSize);
ImGui::Text("Pos: %d (%d -> %d)", ctx->ReadPos(), (int)ctx->startPos, (int)ctx->endPos);
}
}
}
if (ImGui::CollapsingHeaderWithCount("sceAudiocodec", (int)g_audioDecoderContexts.size(), 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();
}
}
ImGui::End();
}
void DrawAudioOut(ImConfig &cfg, ImControl &control) {
if (!ImGui::Begin("Audio output", &cfg.audioOutOpen)) {
ImGui::End();
return;
}
if (g_Config.iAudioPlaybackMode == (int)AudioSyncMode::GRANULAR) {
// Show granular stats
GranularStats stats;
g_granular.GetStats(&stats);
ImGui::Text("Granules");
ImGui::Text("Read size: %0.2f", stats.smoothedReadSize);
ImGui::Text("Frame time estimate: %0.1f ms", stats.frameTimeEstimate * 1000.0f);
ImGui::Text("Queued samples target: %d", stats.queuedSamplesTarget);
ImGui::Text("Queued min/max: %d / %d", stats.queuedGranulesMin, stats.queuedGranulesMax);
ImGui::Text("Queued (smooth): %0.3f", stats.smoothedQueuedGranules);
ImGui::Text("Target: %d", stats.targetQueueSize);
ImGui::Text("Max: %d", stats.maxQueuedGranules);
ImGui::Text("Computed queue latency: %d ms", (int)(stats.smoothedQueuedGranules * (GranularMixer::GRANULE_SIZE * 1000.0 / 44100.0)));
ImGui::Text("Fade volume: %0.2f", stats.fadeVolume);
ImGui::Text("Looping: %s", BoolStr(stats.looping));
ImGui::Text("Under/Over: %d / %d", stats.underruns, stats.overruns);
} else {
ImGui::Text("StereoResampler classic");
char buf[1024];
g_resampler.GetAudioDebugStats(buf, sizeof(buf));
ImGui::Text("%s", buf);
}
ImGui::End();
}
void DrawAudioChannels(ImConfig &cfg, ImControl &control) {
if (!ImGui::Begin("Raw audio channels", &cfg.audioChannelsOpen)) {
ImGui::End();
return;
}
if (ImGui::BeginTable("audios", 7, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Index", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Mute", 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 (!g_audioChans[i].reserved) {
continue;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(i);
if (i == 8) {
ImGui::TextUnformatted("audio2");
} else {
ImGui::Text("%d", i);
}
ImGui::TableNextColumn();
ImGui::Checkbox("", &g_audioChans[i].mute);
ImGui::TableNextColumn();
char id[2]{};
id[0] = i + 1;
ImClickableValue(id, g_audioChans[i].sampleAddress, control, ImCmd::SHOW_IN_MEMORY_VIEWER);
ImGui::TableNextColumn();
ImGui::Text("%08x", g_audioChans[i].sampleCount);
ImGui::TableNextColumn();
ImGui::Text("%d | %d", g_audioChans[i].leftVolume, g_audioChans[i].rightVolume);
ImGui::TableNextColumn();
switch (g_audioChans[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 : g_audioChans[i].waitingThreads) {
KernelObject *thread = kernelObjects.GetFast<KernelObject>(t.threadID);
if (thread) {
ImGui::Text("%s: %d", thread->GetName(), t.numSamples);
}
}
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::End();
}
void ImLogWindow::Draw(ImConfig &cfg) {
ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Log", &cfg.logOpen)) {
ImGui::End();
return;
}
const RingbufferLog &ring = g_logManager.GetRingbuffer();
// Options menu
if (ImGui::BeginPopup("Options")) {
ImGui::Checkbox("Auto-scroll", &AutoScroll);
ImGui::EndPopup();
}
// Main window
if (ImGui::Button("Options"))
ImGui::OpenPopup("Options");
ImGui::SameLine();
bool copy = ImGui::Button("Copy");
ImGui::Separator();
if (ImGui::BeginChild("scrolling", ImVec2(0, 0), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar)) {
if (copy)
ImGui::LogToClipboard();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
ImGuiListClipper clipper;
clipper.Begin(ring.GetCount());
while (clipper.Step()) {
for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) {
int n = ring.GetCount() - 1 - line_no;
const std::string line = ring.TextAt(n);
const LogLevel level = ring.LevelAt(n);
const u32 color = 0xFF000000 | LogManager::GetLevelColor(level);
ImGui::PushStyleColor(ImGuiCol_Text, color);
ImGui::TextUnformatted(line);
ImGui::PopStyleColor();
}
}
clipper.End();
ImGui::PopStyleVar();
// Keep up at the bottom of the scroll region if we were already at the bottom at the beginning of the frame.
// Using a scrollbar or mouse-wheel will take away from the bottom edge.
if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 20)
ImGui::SetScrollHereY(1.0f);
}
ImGui::EndChild();
ImGui::End();
}
void DrawLogConfig(ImConfig &cfg) {
if (!ImGui::Begin("Logs", &cfg.logConfigOpen)) {
ImGui::End();
return;
}
static const char *logLevels[] = {
"N/A",
"Notice", // starts at 1 for some reason
"Error",
"Warn",
"Info",
"Debug",
"Verb."
};
_dbg_assert_(ARRAY_SIZE(logLevels) == (int)LogLevel::LVERBOSE + 1);
if (ImGui::BeginTable("logs", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Log", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthFixed, 150.0f);
ImGui::TableHeadersRow();
for (int i = 0; i < (int)Log::NUMBER_OF_LOGS; i++) {
LogChannel *chan = g_logManager.GetLogChannel((Log)i);
ImGui::TableNextRow();
ImGui::TableNextColumn();
const char *logName = LogManager::GetLogTypeName((Log)i);
ImGui::PushID(logName);
ImGui::Checkbox(logName, &chan->enabled);
ImGui::TableNextColumn();
if (ImGui::BeginCombo("-", logLevels[(int)chan->level])) {
for (int i = 1; i < ARRAY_SIZE(logLevels); ++i) {
LogLevel current = static_cast<LogLevel>(i);
bool isSelected = (chan->level == current);
if (ImGui::Selectable(logLevels[(int)current], isSelected)) {
chan->level = current;
}
if (isSelected) {
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::End();
}
static const char *VoiceTypeToString(VoiceType type) {
switch (type) {
case VOICETYPE_OFF: return "OFF";
case VOICETYPE_VAG: return "VAG"; // default
case VOICETYPE_NOISE: return "NOISE";
case VOICETYPE_TRIWAVE: return "TRIWAVE"; // are these used? there are functions for them (sceSetTriangularWave)
case VOICETYPE_PULSEWAVE: return "PULSEWAVE";
case VOICETYPE_PCM: return "PCM";
case VOICETYPE_ATRAC3: return "ATRAC3";
default: return "(unknown!)";
}
}
void DrawSasAudio(ImConfig &cfg) {
if (!ImGui::Begin("sasAudio", &cfg.sasAudioOpen)) {
ImGui::End();
return;
}
const SasInstance *sas = GetSasInstance();
if (!sas) {
ImGui::Text("Sas instance not available");
ImGui::End();
return;
}
ImGui::Checkbox("Mute", __SasGetGlobalMuteFlag());
ImGui::SameLine();
ImGui::Checkbox("Show all voices", &cfg.sasShowAllVoices);
if (ImGui::BeginTable("saschannels", 9, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Index", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("On", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Loop", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Pause", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Cur", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Addr", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Volume", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
for (int i = 0; i < PSP_SAS_VOICES_MAX; i++) {
const SasVoice &voice = sas->voices[i];
if (!voice.on && !cfg.sasShowAllVoices) {
continue;
}
if (!voice.on) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128));
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%d", i);
ImGui::TableNextColumn();
ImGui::TextUnformatted(VoiceTypeToString(voice.type));
ImGui::TableNextColumn();
ImGui::TextUnformatted(BoolStr(voice.on));
ImGui::TableNextColumn();
ImGui::TextUnformatted(BoolStr(voice.loop));
ImGui::TableNextColumn();
ImGui::TextUnformatted(BoolStr(voice.paused));
ImGui::TableNextColumn();
switch (voice.type) {
case VOICETYPE_OFF: ImGui::TextUnformatted("(off)"); break;
case VOICETYPE_VAG: ImGui::Text("%08x", voice.vag.GetReadPtr()); break;
case VOICETYPE_PCM: ImGui::Text("%08x", voice.pcmAddr + voice.pcmIndex * 2); break;
default:
ImGui::TextUnformatted("N/A");
break;
}
ImGui::TableNextColumn();
switch (voice.type) {
case VOICETYPE_OFF: ImGui::TextUnformatted("(off)"); break;
case VOICETYPE_VAG: ImGui::Text("%08x", voice.vagAddr); break;
case VOICETYPE_PCM: ImGui::Text("%08x", voice.pcmAddr); break;
case VOICETYPE_ATRAC3: ImGui::Text("atrac: %d", voice.atrac3.AtracID()); break;
default:
ImGui::TextUnformatted("N/A");
break;
}
ImGui::TableNextColumn();
switch (voice.type) {
case VOICETYPE_OFF: ImGui::TextUnformatted("(off)"); break;
case VOICETYPE_VAG: ImGui::Text("%08x", voice.vagSize); break;
case VOICETYPE_PCM: ImGui::Text("%08x", voice.pcmSize); break;
case VOICETYPE_ATRAC3: ImGui::Text("atrac: n/a"); break;
default:
ImGui::TextUnformatted("N/A");
break;
}
ImGui::TableNextColumn();
ImGui::Text("%d | %d", voice.volumeLeft, voice.volumeRight);
if (!voice.on) {
ImGui::PopStyleColor();
}
}
ImGui::EndTable();
}
ImGui::End();
}
static void DrawCallStacks(const MIPSDebugInterface *debug, ImConfig &config, ImControl &control) {
if (!ImGui::Begin("Callstacks", &config.callstackOpen)) {
ImGui::End();
return;
}
std::vector<DebugThreadInfo> info = GetThreadsInfo();
// TODO: Add dropdown for thread choice, so you can check the callstacks of other threads.
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();
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
int i = 0;
for (auto &frame : frames) {
const std::string entrySym = g_symbolMap->GetLabelString(frame.entry);
ImGui::PushID(i);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted(entrySym.c_str());
ImGui::TableSetColumnIndex(1);
ImClickableValue("frameentry", frame.entry, control, ImCmd::SHOW_IN_CPU_DISASM);
ImGui::TableSetColumnIndex(2);
ImClickableValue("framepc", frame.pc, control, ImCmd::SHOW_IN_CPU_DISASM);
ImGui::TableSetColumnIndex(3);
ImGui::TextUnformatted("N/A"); // opcode, see the old debugger
ImGui::TableSetColumnIndex(4);
ImClickableValue("framepc", frame.sp, control, ImCmd::SHOW_IN_MEMORY_VIEWER);
ImGui::TableSetColumnIndex(5);
ImGui::Text("%d", frame.stackSize);
// TODO: More fields?
ImGui::PopID();
i++;
}
ImGui::EndTable();
}
ImGui::End();
}
static void DrawUtilityModules(ImConfig &cfg, ImControl &control) {
if (!ImGui::Begin("Utility Modules", &cfg.utilityModulesOpen) || !g_symbolMap) {
ImGui::End();
return;
}
ImGui::TextUnformatted(
"These are fake module representations loaded by sceUtilityLoadModule\n"
"On a real PSP, these would be loaded from the BIOS.\n");
const std::map<int, u32> &modules = __UtilityGetLoadedModules();
if (ImGui::BeginTable("modules", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Load Address", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Load Size", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
// TODO: Add context menu and clickability
int i = 0;
for (const auto &iter : modules) {
u32 loadedAddr = iter.second;
const ModuleLoadInfo *info = __UtilityModuleInfo(iter.first);
ImGui::PushID(i);
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(info->name, cfg.selectedUtilityModule == i, ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedUtilityModule = i;
}
ImGui::TableNextColumn();
if (loadedAddr) {
ImClickableValue("addr", loadedAddr, control, ImCmd::SHOW_IN_MEMORY_VIEWER);
} else {
ImGui::TextUnformatted("-");
}
ImGui::TableNextColumn();
ImGui::Text("%08x", info->size);
ImGui::PopID();
i++;
}
ImGui::EndTable();
}
ImGui::End();
}
static void DrawModules(const MIPSDebugInterface *debug, ImConfig &cfg, ImControl &control) {
if (!ImGui::Begin("Modules", &cfg.modulesOpen) || !g_symbolMap) {
ImGui::End();
return;
}
ImGui::TextUnformatted("This shows modules that have been loaded by the game (not plain HLE)");
if (ImGui::BeginChild("module_list", ImVec2(170.0f, 0.0), ImGuiChildFlags_ResizeX)) {
if (ImGui::BeginTable("modules", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow();
// TODO: Add context menu and clickability
kernelObjects.Iterate<PSPModule>([&cfg](int id, PSPModule *module) -> bool {
ImGui::PushID(id);
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(module->GetName(), cfg.selectedModuleId == id, ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedModuleId = id;
}
ImGui::TableNextColumn();
ImGui::TextUnformatted(module->isFake ? "FAKE/HLE" : "normal");
ImGui::PopID();
return true;
});
ImGui::EndTable();
}
ImGui::EndChild();
}
ImGui::SameLine();
if (ImGui::BeginChild("info")) {
if (kernelObjects.Is<PSPModule>(cfg.selectedModuleId)) {
PSPModule *mod = kernelObjects.GetFast<PSPModule>(cfg.selectedModuleId);
if (mod) {
if (mod->isFake) {
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 170));
}
ImGui::Text("%s %d.%d (%s)\n", mod->GetName(), mod->nm.version[1], mod->nm.version[0], mod->isFake ? "FAKE/HLE" : "normal");
ImGui::Text("Attr: %08x (%s)\n", mod->nm.attribute, (mod->nm.attribute & 0x1000) ? "Kernel" : "User");
char buf[512];
mod->GetLongInfo(buf, sizeof(buf));
ImGui::TextUnformatted(buf);
if (mod->isFake) {
ImGui::PopStyleColor();
}
if (!mod->impModuleNames.empty() && ImGui::CollapsingHeader("Imported modules")) {
for (auto &name : mod->impModuleNames) {
ImGui::TextUnformatted(name);
}
}
if (!mod->expModuleNames.empty() && ImGui::CollapsingHeader("Exported modules")) {
for (auto &name : mod->expModuleNames) {
ImGui::TextUnformatted(name);
}
}
if (!mod->importedFuncs.empty() || !mod->importedVars.empty()) {
if (ImGui::CollapsingHeader("Imports")) {
if (!mod->importedVars.empty() && ImGui::CollapsingHeader("Vars")) {
for (auto &var : mod->importedVars) {
ImGui::TextUnformatted("(some var)"); // TODO
}
}
for (auto &import : mod->importedFuncs) {
// Look the name up in our HLE database.
const HLEFunction *func = GetHLEFunc(import.moduleName, import.nid);
ImGui::TextUnformatted(import.moduleName);
if (func) {
ImGui::SameLine();
ImGui::TextUnformatted(func->name);
}
ImGui::SameLine(); ImClickableValue("addr", import.stubAddr, control, ImCmd::SHOW_IN_CPU_DISASM);
}
}
}
if (!mod->exportedFuncs.empty() || !mod->exportedVars.empty()) {
if (ImGui::CollapsingHeader("Exports")) {
if (!mod->exportedVars.empty() && ImGui::CollapsingHeader("Vars")) {
for (auto &var : mod->importedVars) {
ImGui::TextUnformatted("(some var)"); // TODO
}
}
for (auto &exportFunc : mod->exportedFuncs) {
// Look the name up in our HLE database.
const HLEFunction *func = GetHLEFunc(exportFunc.moduleName, exportFunc.nid);
ImGui::TextUnformatted(exportFunc.moduleName);
if (func) {
ImGui::SameLine();
ImGui::TextUnformatted(func->name);
}
ImGui::SameLine(); ImClickableValue("addr", exportFunc.symAddr, control, ImCmd::SHOW_IN_CPU_DISASM);
}
}
}
}
} else {
ImGui::TextUnformatted("(no module selected)");
}
ImGui::EndChild();
}
ImGui::End();
}
// Started as a module browser but really only draws from the symbols database, so let's
// evolve it to that.
static void DrawSymbols(const MIPSDebugInterface *debug, ImConfig &cfg, ImControl &control) {
if (!ImGui::Begin("Symbols", &cfg.symbolsOpen) || !g_symbolMap) {
ImGui::End();
return;
}
// Reads from the symbol map.
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::PushID(i);
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(module.name.c_str(), cfg.selectedSymbolModule == i, ImGuiSelectableFlags_SpanAllColumns)) {
cfg.selectedSymbolModule = i;
}
ImGui::TableNextColumn();
ImClickableValue("addr", module.address, control, ImCmd::SHOW_IN_MEMORY_VIEWER);
ImGui::TableNextColumn();
ImGui::Text("%08x", module.size);
ImGui::TableNextColumn();
ImGui::TextUnformatted(module.active ? "yes" : "no");
ImGui::PopID();
}
ImGui::EndTable();
}
if (cfg.selectedModuleId >= 0 && cfg.selectedModuleId < (int)modules.size()) {
// TODO: Show details
}
ImGui::End();
}
ImWatchWindow::ImWatchWindow() {}
void ImWatchWindow::Draw(ImConfig &cfg, ImControl &control, MIPSDebugInterface *mipsDebug) {
if (!ImGui::Begin("Watch", &cfg.watchOpen) || !g_symbolMap) {
ImGui::End();
return;
}
// Refresh watches
int steppingCounter = Core_GetSteppingCounter();
int changes = false;
for (auto &watch : watches_) {
if (watch.steppingCounter != steppingCounter) {
watch.lastValue = watch.currentValue;
watch.steppingCounter = steppingCounter;
}
uint32_t prevValue = watch.currentValue;
watch.evaluateFailed = !parseExpression(mipsDebug, watch.expression, watch.currentValue);
}
if (ImGui::Button("Add Watch")) {
watches_.push_back(WatchInfo("untitled", "[0x88000000]", mipsDebug));
}
if (ImGui::BeginTable("watches", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH | ImGuiTableFlags_Resizable)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 120.0f);
ImGui::TableSetupColumn("Expression", ImGuiTableColumnFlags_WidthFixed, 120.0f);
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 100.0f);
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 50.0f);
ImGui::TableHeadersRow();
for (int i = 0; i < (int)watches_.size(); i++) {
auto &watch = watches_[i];
ImGui::PushID(i);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(watch.name.c_str());
ImGui::TableNextColumn();
if (editingWatchIndex_ == i && editingColumn_ == 1) {
ImGui::SetNextItemWidth(-1.0f); // Full width
if (setEditFocus_) {
ImGui::FocusItem();
setEditFocus_ = false;
}
bool confirmed = ImGui::InputText("##edit", editBuffer_, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll);
// Filter for only enter presses
confirmed = confirmed && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter));
if (confirmed || ImGui::IsItemDeactivated()) {
watch.SetExpression(editBuffer_, mipsDebug);
editingWatchIndex_ = -1;
}
} else {
ImGui::SetNextItemWidth(-1.0f); // Full width
ImGui::TextUnformatted(watch.originalExpression.c_str());
auto cellRect = ImGui::TableGetCellBgRect(ImGui::GetCurrentTable(), 1);
bool cellHovered = ImGui::IsMouseHoveringRect(cellRect.Min, cellRect.Max);
if (cellHovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
editingWatchIndex_ = i;
editingColumn_ = 1;
truncate_cpy(editBuffer_, watch.originalExpression.c_str());
setEditFocus_ = true;
}
}
ImGui::TableNextColumn();
if (watch.evaluateFailed) {
ImGui::TextUnformatted("(Error)");
} else {
bool changed = watch.currentValue != watch.lastValue;
if (changed) {
//ImGui::PushStyleColor(ImGuiCol_Text, ImDebuggerColor_Diff);
}
const uint32_t value = watch.currentValue;
float valuef = 0.0f;
switch (watch.format) {
case WatchFormat::HEX:
ImGui::Text("%08x", value);
break;
case WatchFormat::INT:
ImGui::Text("%d", value);
break;
case WatchFormat::FLOAT:
memcpy(&valuef, &value, sizeof(valuef));
ImGui::Text("%f", static_cast<float>(value));
break;
case WatchFormat::STR:
if (Memory::IsValidAddress(value)) {
uint32_t len = Memory::ClampValidSizeAt(value, 255);
ImGui::Text("%.*s", len, Memory::GetCharPointer(value));
} else {
ImGui::Text("%08x", value);
}
break;
}
if (changed) {
//ImGui::PopStyleColor(ImGuiCol_Text);
}
}
ImGui::TableNextColumn();
if (ImGui::SmallButton("X")) {
watches_.erase(watches_.begin() + i);
}
ImGui::SameLine();
if (ImGui::Button("Type")) {
ImGui::OpenPopup("watchType");
}
if (ImGui::BeginPopup("watchType")) {
ImGui::Text("Watch type");
if (ImGui::MenuItem("Hex", nullptr, watch.format == WatchFormat::HEX)) { watch.format = WatchFormat::HEX; }
if (ImGui::MenuItem("Int", nullptr, watch.format == WatchFormat::INT)) { watch.format = WatchFormat::INT; }
if (ImGui::MenuItem("Float", nullptr, watch.format == WatchFormat::FLOAT)) { watch.format = WatchFormat::FLOAT; }
if (ImGui::MenuItem("Str", nullptr, watch.format == WatchFormat::STR)) { watch.format = WatchFormat::STR; }
ImGui::EndPopup();
}
ImGui::PopID();
}
ImGui::EndTable();
}
ImGui::End();
}
void ImAtracToolWindow::Load() {
if (File::ReadBinaryFileToString(Path(atracPath_), &data_)) {
track_.reset(new Track());
AnalyzeAtracTrack((const u8 *)data_.data(), (u32)data_.size(), track_.get(), &error_);
} else {
error_ = "Failed to read file from disk. Bad path?";
}
}
void ImAtracToolWindow::Draw(ImConfig &cfg) {
if (!ImGui::Begin("Atrac Tool", &cfg.atracToolOpen) || !g_symbolMap) {
ImGui::End();
return;
}
ImGui::InputText("File", atracPath_, sizeof(atracPath_));
ImGui::SameLine();
if (ImGui::Button("Choose...")) {
System_BrowseForFile(cfg.requesterToken, "Choose AT3 file", BrowseFileType::ATRAC3, [this](std::string_view filename, int) {
truncate_cpy(atracPath_, filename);
Load();
}, nullptr);
}
if (strlen(atracPath_) > 0) {
if (ImGui::Button("Load")) {
Load();
}
}
if (track_.get() != 0) {
ImGui::Text("Codec: %s", track_->codecType != PSP_CODEC_AT3 ? "at3+" : "at3");
ImGui::Text("Bitrate: %d kbps Channels: %d", track_->Bitrate(), track_->channels);
ImGui::Text("Frame size in bytes: %d (%04x) Output frame in samples: %d", track_->BytesPerFrame(), track_->BytesPerFrame(), track_->SamplesPerFrame());
ImGui::Text("First valid sample: %08x", track_->FirstSampleOffsetFull());
ImGui::Text("EndSample: %08x", track_->endSample);
}
if (data_.size()) {
if (ImGui::Button("Dump 64 raw frames")) {
std::string firstFrames = data_.substr(track_->dataByteOffset, track_->bytesPerFrame * 64);
System_BrowseForFileSave(cfg.requesterToken, "Save .at3raw", "at3.raw", BrowseFileType::ANY, [firstFrames](std::string_view filename, int) {
FILE *f = File::OpenCFile(Path(filename), "wb");
if (f) {
fwrite(firstFrames.data(), 1, firstFrames.size(), f);
fclose(f);
}
});
}
if (ImGui::Button("Unload")) {
data_.clear();
track_.reset(nullptr);
}
}
if (!error_.empty()) {
ImGui::TextUnformatted(error_.c_str());
}
ImGui::End();
}
void DrawHLEModules(ImConfig &config) {
if (!ImGui::Begin("HLE Modules", &config.hleModulesOpen)) {
ImGui::End();
return;
}
const int moduleCount = GetNumRegisteredHLEModules();
std::vector<const HLEModule *> modules;
modules.reserve(moduleCount);
for (int i = 0; i < moduleCount; i++) {
modules.push_back(GetHLEModuleByIndex(i));
}
std::sort(modules.begin(), modules.end(), [](const HLEModule* a, const HLEModule* b) {
return a->name < b->name;
});
std::string label;
for (auto mod : modules) {
label = mod->name;
if (ImGui::TreeNode(label.c_str())) {
for (int j = 0; j < mod->numFunctions; j++) {
auto &func = mod->funcTable[j];
ImGui::Text("%s(%s)", func.name, func.argmask);
}
ImGui::TreePop();
}
if (ImGui::BeginPopupContextItem(label.c_str())) {
if (ImGui::MenuItem("Copy as JpscpTrace")) {
char *buffer = new char[1000000];
StringWriter w(buffer, 1000000);
for (int j = 0; j < mod->numFunctions; j++) {
auto &func = mod->funcTable[j];
// Translate the argmask to fit jpscptrace
std::string amask = func.argmask;
for (int i = 0; i < amask.size(); i++) {
switch (amask[i]) {
case 'i':
case 'I': amask[i] = 'd'; break;
case 'f':
case 'F': amask[i] = 'x'; break;
default:
// others go straight through (p)
break;
}
}
w.F("%s 0x%08x %d %s", func.name, func.ID, (int)strlen(func.argmask), amask.c_str()).endl();
}
System_CopyStringToClipboard(w.as_view());
delete[] buffer;
}
if (ImGui::MenuItem("Copy as imports.S")) {
char *buffer = new char[100000];
StringWriter w(buffer, 100000);
w.C(".set noreorder\n\n#include \"pspimport.s\"\n\n");
w.F("IMPORT_START \"%.*s\",0x00090011\n", (int)mod->name.size(), mod->name.data());
for (int j = 0; j < mod->numFunctions; j++) {
auto &func = mod->funcTable[j];
w.F("IMPORT_FUNC \"%.*s\",0x%08X,%s\n", (int)mod->name.size(), mod->name.data(), func.ID, func.name);
}
w.endl();
System_CopyStringToClipboard(w.as_view());
delete[] buffer;
}
ImGui::EndPopup();
}
}
ImGui::End();
}
ImDebugger::ImDebugger() {
reqToken_ = g_requestManager.GenerateRequesterToken();
cfg_.LoadConfig(ConfigPath());
g_normalTextColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
// Just enable ring buffer logging here, it's cheap (but needed for the log window)
g_logManager.EnableOutput(LogOutput::RingBuffer);
}
ImDebugger::~ImDebugger() {
cfg_.SaveConfig(ConfigPath());
}
void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw::DrawContext *draw) {
// Snapshot the coreState to avoid inconsistency.
const CoreState coreState = ::coreState;
const bool showDebugger = g_Config.bShowImDebugger && PSP_IsInited(); // This will be separated from showing the IM UI in general.
const bool allowSaveStates = !Achievements::HardcoreModeActive() && PSP_IsInited() && (!IsNetworkConnected() || g_Config.bAllowSavestateWhileConnected);
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 (showDebugger) {
if (lastCpuStepCount_ != Core_GetSteppingCounter()) {
lastCpuStepCount_ = Core_GetSteppingCounter();
snapshot_ = newSnapshot_; // Compare against the previous snapshot.
Snapshot(currentMIPS);
disasm_.NotifyStep();
}
if (gpuDebug && 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("File")) {
if (ImGui::MenuItem("Load")) {
System_BrowseForFile(reqToken_, "Load", BrowseFileType::SAVE_STATE, [this](std::string_view responseString, int) {
Path path(responseString);
System_PostUIMessage(UIMessage::REQUEST_GAME_BOOT, path.ToString());
});
}
if (ImGui::MenuItem("Open directory...")) {
System_BrowseForFolder(reqToken_, "Load", Path(), [](std::string_view value, int) {
System_PostUIMessage(UIMessage::REQUEST_GAME_BOOT, value);
});
}
if (System_GetPropertyBool(SYSPROP_HAS_OPEN_DIRECTORY)) {
if (ImGui::MenuItem("Open memory stick")) {
System_LaunchUrl(LaunchUrlType::LOCAL_FOLDER, g_Config.memStickDirectory.ToString());
}
}
// TODO: Add "Open new instance"
if (allowSaveStates) {
ImGui::Separator();
if (ImGui::MenuItem(StringFromFormat("Load state (%d)", g_Config.iCurrentStateSlot).c_str(), "F4")) {
SaveState::LoadSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, ShowMessageAfterSaveStateAction);
}
if (ImGui::MenuItem(StringFromFormat("Save state (%d)", g_Config.iCurrentStateSlot).c_str(), "F2")) {
SaveState::SaveSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, ShowMessageAfterSaveStateAction);
}
if (ImGui::MenuItem("Previous slot")) {
SaveState::PrevSlot();
}
if (ImGui::MenuItem("Next slot", "F3")) {
SaveState::NextSlot();
}
ImGui::Separator();
if (ImGui::MenuItem("Load state from file...")) {
System_BrowseForFile(reqToken_, "Load", BrowseFileType::SAVE_STATE, [this](std::string_view fn, int) {
SaveState::Load(Path(fn), -1, ShowMessageAfterSaveStateAction);
});
}
if (ImGui::MenuItem("Save state to file...")) {
System_BrowseForFile(reqToken_, "Save", BrowseFileType::SAVE_STATE, [this](std::string_view fn, int) {
SaveState::Save(Path(fn), -1, ShowMessageAfterSaveStateAction);
});
}
} else {
ImGui::BeginDisabled();
ImGui::Text("Save states are not available");
ImGui::EndDisabled();
}
ImGui::Separator();
if (ImGui::MenuItem("Exit")) {
System_ExitApp();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Emulation")) {
if (ImGui::MenuItem("Pause")) {
System_PostUIMessage(UIMessage::REQUEST_GAME_PAUSE);
}
if (ImGui::MenuItem("Stop")) {
System_PostUIMessage(UIMessage::REQUEST_GAME_STOP);
}
if (ImGui::MenuItem("Reset")) {
System_PostUIMessage(UIMessage::REQUEST_GAME_RESET);
}
ImGui::Separator();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("View")) {
if (PSP_IsInited()) {
ImGui::BeginDisabled(); // Can't change this setting while running.
}
ImGui::MenuItem("Show DevMenu button", nullptr, &g_Config.bShowDeveloperMenu);
if (PSP_IsInited()) {
ImGui::EndDisabled();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Debug")) {
if (PSP_IsInited()) {
switch (coreState) {
case CoreState::CORE_STEPPING_CPU:
if (ImGui::MenuItem("Run")) {
Core_Resume();
}
ImGui::Separator();
break;
case CoreState::CORE_RUNNING_CPU:
if (ImGui::MenuItem("Break")) {
Core_Break(BreakReason::DebugBreak);
}
ImGui::Separator();
break;
default:
break;
}
}
ImGui::MenuItemInverted("Break on load", nullptr, &g_Config.bAutoRun);
ImGui::MenuItem("Ignore bad memory accesses", nullptr, &g_Config.bIgnoreBadMemAccess);
ImGui::MenuItem("Break on frame timeout", nullptr, &g_Config.bBreakOnFrameTimeout);
ImGui::MenuItem("Fast memory", nullptr, &g_Config.bFastMemory);
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::Separator();
if (ImGui::MenuItem("Open web debugger")) {
OpenWebDebugger();
}
ImGui::Separator();
if (ImGui::MenuItem("Close")) {
g_Config.bShowImDebugger = false;
}
ImGui::EndMenu();
}
/*
if (ImGui::BeginMenu("Options")) {
ImGui::EndMenu();
}
*/
if (showDebugger) {
if (ImGui::BeginMenu("Core")) {
ImGui::MenuItem("Scheduler", nullptr, &cfg_.schedulerOpen);
ImGui::MenuItem("Time", nullptr, &cfg_.timeOpen);
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::Separator();
ImGui::MenuItem("Callstacks", nullptr, &cfg_.callstackOpen);
ImGui::MenuItem("Breakpoints", nullptr, &cfg_.breakpointsOpen);
ImGui::MenuItem("Watch", nullptr, &cfg_.watchOpen);
ImGui::MenuItem("JIT viewer", nullptr, &cfg_.jitViewerOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Symbols")) {
ImGui::MenuItem("Symbol browser", nullptr, &cfg_.symbolsOpen);
ImGui::Separator();
if (ImGui::MenuItem("Load .ppmap...")) {
System_BrowseForFile(reqToken_, "Load PPSSPP symbol map", BrowseFileType::SYMBOL_MAP, [this](std::string_view 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 .ppmap...")) {
System_BrowseForFileSave(reqToken_, "Save PPSSPP symbol map", "symbols.ppmap", BrowseFileType::SYMBOL_MAP, [](std::string_view responseString, int) {
Path path(responseString);
if (!g_symbolMap->SaveSymbolMap(path)) {
ERROR_LOG(Log::Common, "Failed to save symbol map");
}
});
}
if (ImGui::MenuItem("Load No$ .sym...")) {
System_BrowseForFile(reqToken_, "Load No$ symbol map", BrowseFileType::SYMBOL_MAP, [this](std::string_view responseString, int) {
Path path(responseString);
if (!g_symbolMap->LoadNocashSym(path)) {
ERROR_LOG(Log::Common, "Failed to load No$ symbol map");
}
disasm_.DirtySymbolMap();
});
}
if (ImGui::MenuItem("Save No$ .sym...")) {
System_BrowseForFileSave(reqToken_, "Save No$ symbol map", "symbols.sym", BrowseFileType::SYMBOL_MAP, [](std::string_view responseString, int) {
Path path(responseString);
if (!g_symbolMap->SaveNocashSym(path)) {
ERROR_LOG(Log::Common, "Failed to save No$ symbol map");
}
});
}
ImGui::Separator();
ImGui::MenuItem("Compress .ppmap files", nullptr, &g_Config.bCompressSymbols);
if (ImGui::MenuItem("Reset symbol map")) {
g_symbolMap->Clear();
disasm_.DirtySymbolMap();
// NotifyDebuggerMapLoaded();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Memory")) {
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("OS HLE")) {
ImGui::MenuItem("HLE module browser", nullptr, &cfg_.hleModulesOpen);
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::MenuItem("Utility Modules", nullptr, &cfg_.utilityModulesOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Graphics")) {
ImGui::MenuItem("GE Debugger", nullptr, &cfg_.geDebuggerOpen);
ImGui::MenuItem("GE State", nullptr, &cfg_.geStateOpen);
ImGui::MenuItem("GE Vertices", nullptr, &cfg_.geVertsOpen);
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/Video")) {
ImGui::MenuItem("SasAudio mixer", nullptr, &cfg_.sasAudioOpen);
ImGui::MenuItem("Raw audio channels", nullptr, &cfg_.audioChannelsOpen);
ImGui::MenuItem("AV Decoder contexts", nullptr, &cfg_.mediaDecodersOpen);
ImGui::Separator();
ImGui::MenuItem("Audio output / buffer", nullptr, &cfg_.audioOutOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Network")) {
ImGui::MenuItem("ApCtl", nullptr, &cfg_.apctlOpen);
ImGui::MenuItem("Sockets", nullptr, &cfg_.socketsOpen);
ImGui::MenuItem("NP", nullptr, &cfg_.npOpen);
ImGui::MenuItem("AdHoc", nullptr, &cfg_.adhocOpen);
ImGui::EndMenu();
}
}
if (ImGui::BeginMenu("Tools")) {
ImGui::MenuItem("Lua Console", nullptr, &cfg_.luaConsoleOpen);
ImGui::MenuItem("Log", nullptr, &cfg_.logOpen);
ImGui::MenuItem("Log channels", nullptr, &cfg_.logConfigOpen);
ImGui::MenuItem("Debug stats", nullptr, &cfg_.debugStatsOpen);
ImGui::Separator();
if (PSP_IsInited()) {
ImGui::MenuItem("ParamSFO viewer", nullptr, &cfg_.paramSFOOpen);
}
ImGui::MenuItem("Struct viewer", nullptr, &cfg_.structViewerOpen);
ImGui::MenuItem("Atrac Tool", nullptr, &cfg_.atracToolOpen);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Misc")) {
ImGui::MenuItem("PPSSPP Internals", nullptr, &cfg_.internalsOpen);
ImGui::MenuItem("Dear ImGui Demo", nullptr, &cfg_.demoOpen);
ImGui::MenuItem("Dear ImGui Style editor", nullptr, &cfg_.styleEditorOpen);
ImGui::EndMenu();
}
if (ImGui::MenuItem("Close")) {
g_Config.bShowImDebugger = false;
}
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(BreakReason::DebugBreak);
}
break;
default:
break;
}
ImGui::EndMainMenuBar();
}
if (cfg_.demoOpen) {
ImGui::ShowDemoWindow(&cfg_.demoOpen);
}
if (cfg_.styleEditorOpen) {
ImGui::ShowStyleEditor();
}
if (PSP_IsInited()) {
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_, control);
}
if (cfg_.audioOutOpen) {
DrawAudioOut(cfg_, control);
}
if (cfg_.sasAudioOpen) {
DrawSasAudio(cfg_);
}
if (cfg_.kernelObjectsOpen) {
DrawKernelObjects(cfg_);
}
if (cfg_.threadsOpen) {
DrawThreadView(cfg_, control);
}
if (cfg_.callstackOpen) {
DrawCallStacks(mipsDebug, cfg_, control);
}
if (cfg_.modulesOpen) {
DrawModules(mipsDebug, cfg_, control);
}
if (cfg_.symbolsOpen) {
DrawSymbols(mipsDebug, cfg_, control);
}
if (cfg_.utilityModulesOpen) {
DrawUtilityModules(cfg_, control);
}
if (cfg_.mediaDecodersOpen) {
DrawMediaDecodersView(cfg_, control);
}
if (cfg_.hleModulesOpen) {
DrawHLEModules(cfg_);
}
if (cfg_.atracToolOpen) {
atracToolWindow_.Draw(cfg_);
}
if (gpuDebug) {
if (cfg_.framebuffersOpen) {
DrawFramebuffersWindow(cfg_, gpuDebug->GetFramebufferManagerCommon());
}
if (cfg_.texturesOpen) {
DrawTexturesWindow(cfg_, gpuDebug->GetTextureCacheCommon());
}
}
if (cfg_.jitViewerOpen) {
jitViewer_.Draw(cfg_, control);
}
if (cfg_.displayOpen) {
DrawDisplayWindow(cfg_, gpuDebug->GetFramebufferManagerCommon());
}
if (cfg_.debugStatsOpen) {
DrawDebugStatsWindow(cfg_);
}
if (cfg_.paramSFOOpen) {
DrawParamSFO(cfg_, control);
}
if (cfg_.geDebuggerOpen) {
geDebugger_.Draw(cfg_, control, gpuDebug, draw);
}
if (cfg_.geStateOpen) {
geStateWindow_.Draw(cfg_, control, gpuDebug);
}
if (cfg_.geVertsOpen) {
DrawImGeVertsWindow(cfg_, control, gpuDebug);
}
if (cfg_.schedulerOpen) {
DrawSchedulerView(cfg_);
}
if (cfg_.timeOpen) {
DrawTimeView(cfg_);
}
if (cfg_.pixelViewerOpen) {
pixelViewer_.Draw(cfg_, control, gpuDebug, draw);
}
if (cfg_.memDumpOpen) {
memDumpWindow_.Draw(cfg_, mipsDebug);
}
if (cfg_.watchOpen) {
watchWindow_.Draw(cfg_, control, 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_);
}
if (cfg_.adhocOpen) {
DrawAdhoc(cfg_);
}
if (cfg_.apctlOpen) {
DrawApctl(cfg_);
}
}
if (cfg_.logConfigOpen) {
DrawLogConfig(cfg_);
}
if (cfg_.logOpen) {
logWindow_.Draw(cfg_);
}
if (cfg_.structViewerOpen) {
structViewer_.Draw(cfg_, control, mipsDebug);
}
if (cfg_.internalsOpen) {
DrawInternals(cfg_);
}
if (externalCommand_.cmd != ImCmd::NONE) {
control.command = externalCommand_;
externalCommand_.cmd = ImCmd::NONE;
}
// 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;
case ImCmd::SHOW_IN_PIXEL_VIEWER:
break;
}
if (cfg_.luaConsoleOpen) {
luaConsole_.Draw(cfg_);
}
}
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(GPUCommon *gpuDebug) {
pixelViewer_.Snapshot();
}
void ImDebugger::DeviceLost() {
pixelViewer_.DeviceLost();
geDebugger_.DeviceLost();
}
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) {
requesterToken = g_requestManager.GenerateRequesterToken();
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 {
if (!section_->Get(key, value)) {
*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("symbolsOpen", &symbolsOpen, false);
sync.Sync("modulesOpen", &modulesOpen, false);
sync.Sync("hleModulesOpen", &hleModulesOpen, false);
sync.Sync("mediaDecodersOpen", &mediaDecodersOpen, 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("audioOutOpen", &audioOutOpen, false);
sync.Sync("texturesOpen", &texturesOpen, false);
sync.Sync("debugStatsOpen", &debugStatsOpen, false);
sync.Sync("geDebuggerOpen", &geDebuggerOpen, false);
sync.Sync("geStateOpen", &geStateOpen, false);
sync.Sync("geVertsOpen", &geVertsOpen, false);
sync.Sync("schedulerOpen", &schedulerOpen, false);
sync.Sync("timeOpen", &timeOpen, false);
sync.Sync("socketsOpen", &socketsOpen, false);
sync.Sync("npOpen", &npOpen, false);
sync.Sync("adhocOpen", &adhocOpen, false);
sync.Sync("apctlOpen", &apctlOpen, false);
sync.Sync("pixelViewerOpen", &pixelViewerOpen, false);
sync.Sync("internalsOpen", &internalsOpen, false);
sync.Sync("sasAudioOpen", &sasAudioOpen, false);
sync.Sync("logConfigOpen", &logConfigOpen, false);
sync.Sync("logOpen", &logOpen, false);
sync.Sync("luaConsoleOpen", &luaConsoleOpen, false);
sync.Sync("utilityModulesOpen", &utilityModulesOpen, false);
sync.Sync("memDumpOpen", &memDumpOpen, false);
sync.Sync("watchOpen", &watchOpen, false);
sync.Sync("paramSFOOpen", &paramSFOOpen, false);
sync.Sync("atracToolOpen", &atracToolOpen, false);
sync.Sync("jitViewerOpen", &jitViewerOpen, 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);
}