#include #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 "Core/Config.h" #include "Core/System.h" #include "Core/SaveState.h" #include "Core/Debugger/MemBlockInfo.h" #include "Core/RetroAchievements.h" #include "Core/Core.h" #include "Core/Debugger/DebugInterface.h" #include "Core/Debugger/DisassemblyManager.h" #include "Core/Debugger/Breakpoints.h" #include "Core/MIPS/MIPSDebugInterface.h" #include "Core/MIPS/MIPSTables.h" #include "Core/HW/SimpleAudioDec.h" #include "Core/FileSystems/MetaFileSystem.h" #include "Core/Debugger/SymbolMap.h" #include "Core/MemMap.h" #include "Core/HLE/HLE.h" #include "Core/HLE/SocketManager.h" #include "Core/HLE/NetInetConstants.h" #include "Core/HLE/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" 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(); if (ImGui::BeginChild("event_list", ImVec2(300.0f, 0.0))) { const CoreTiming::Event *event = CoreTiming::GetFirstEvent(); while (event) { ImGui::Text("%s (%lld): %d", CoreTiming::GetEventTypes()[event->type].name, event->time - ticks, (int)event->userdata); event = event->next; } ImGui::EndChild(); } ImGui::SameLine(); if (ImGui::BeginChild("general")) { ImGui::Text("CoreState: %s", CoreStateToString(coreState)); ImGui::Text("downcount: %d", currentMIPS->downcount); ImGui::Text("slicelength: %d", CoreTiming::slicelength); ImGui::Text("Ticks: %lld", ticks); ImGui::Text("Clock (MHz): %0.1f", (float)CoreTiming::GetClockFrequencyHz() / 1000000.0f); ImGui::Text("Global time (us): %lld", CoreTiming::GetGlobalTimeUs()); ImGui::EndChild(); } ImGui::End(); } static void DrawGPRs(ImConfig &config, ImControl &control, const MIPSDebugInterface *mipsDebug, const ImSnapshotState &prev) { ImGui::SetNextWindowSize(ImVec2(320, 600), ImGuiCond_FirstUseEver); if (!ImGui::Begin("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", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersH)) { ImGui::TableSetupColumn("Reg", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Decimal", ImGuiTableColumnFlags_WidthStretch); ImGui::TableHeadersRow(); auto gprLine = [&](int index, const char *regname, int value, int prevValue) { bool diff = value != prevValue && !noDiff; bool disabled = value == 0xdeadbeef; ImGui::TableNextColumn(); ImGui::TextUnformatted(regname); ImGui::TableNextColumn(); if (diff) { ImGui::PushStyleColor(ImGuiCol_Text, !disabled ? ImDebuggerColor_Diff : ImDebuggerColor_DiffAlpha); } else if (disabled) { ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128)); } // 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); } 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(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 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 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 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(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(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); ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Size/Label", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("OpCode", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Cond", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Hits", ImGuiTableColumnFlags_WidthStretch); ImGui::TableHeadersRow(); for (int i = 0; i < (int)bps.size(); i++) { auto &bp = bps[i]; bool temp = bp.temporary; if (temp) { continue; } ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::PushID(i); // 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; } ImGui::SameLine(); ImGui::CheckboxFlags("##enabled", (int *)&bp.result, (int)BREAK_ACTION_PAUSE); ImGui::TableNextColumn(); ImGui::TextUnformatted("Exec"); ImGui::TableNextColumn(); ImGui::Text("%08x", bp.addr); ImGui::TableNextColumn(); const std::string sym = g_symbolMap->GetLabelString(bp.addr); if (!sym.empty()) { ImGui::TextUnformatted(sym.c_str()); // size/label } else { ImGui::TextUnformatted("-"); // size/label } ImGui::TableNextColumn(); // disasm->getOpcodeText(displayedBreakPoints_[index].addr, temp, sizeof(temp)); ImGui::TextUnformatted("-"); // opcode ImGui::TableNextColumn(); if (bp.hasCond) { ImGui::TextUnformatted(bp.cond.expressionString.c_str()); } else { ImGui::TextUnformatted("-"); // condition } ImGui::TableNextColumn(); ImGui::Text("-"); // hits not available on exec bps yet ImGui::PopID(); } // OK, now list the memchecks. for (int i = 0; i < (int)mcs.size(); i++) { auto &mc = mcs[i]; ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::PushID(i + 10000); if (ImGui::Selectable("##memcheck", cfg.selectedMemCheck == i, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap)) { cfg.selectedBreakpoint = -1; cfg.selectedMemCheck = i; } ImGui::SameLine(); ImGui::CheckboxFlags("", (int *)&mc.result, BREAK_ACTION_PAUSE); ImGui::TableNextColumn(); ImGui::TextUnformatted(MemCheckConditionToString(mc.cond)); ImGui::TableNextColumn(); ImGui::Text("%08x", mc.start); ImGui::TableNextColumn(); ImGui::Text("%08x", mc.end ? (mc.end - mc.start) : 1); ImGui::TableNextColumn(); ImGui::TextUnformatted("-"); // opcode ImGui::TableNextColumn(); ImGui::TextUnformatted("-"); // cond ImGui::TableNextColumn(); ImGui::Text("%d", mc.numHits); ImGui::PopID(); } ImGui::EndTable(); } ImGui::TableNextColumn(); if (cfg.selectedBreakpoint >= 0) { // Add edit form for breakpoints if (ImGui::BeginChild("bp_edit")) { auto &bp = bps[cfg.selectedBreakpoint]; ImGui::TextUnformatted("Edit breakpoint"); ImGui::CheckboxFlags("Enabled", (int *)&bp.result, (int)BREAK_ACTION_PAUSE); ImGui::InputScalar("Address", ImGuiDataType_U32, &bp.addr, nullptr, nullptr, "%08x", ImGuiInputTextFlags_CharsHexadecimal); if (ImGui::Button("Delete")) { g_breakpoints.RemoveBreakPoint(bp.addr); } ImGui::EndChild(); } } if (cfg.selectedMemCheck >= 0) { // Add edit form for memchecks if (ImGui::BeginChild("mc_edit")) { auto &mc = mcs[cfg.selectedMemCheck]; ImGui::TextUnformatted("Edit memcheck"); if (ImGui::BeginCombo("Condition", MemCheckConditionToString(mc.cond))) { if (ImGui::Selectable("Read", mc.cond == MemCheckCondition::MEMCHECK_READ)) { mc.cond = MemCheckCondition::MEMCHECK_READ; } if (ImGui::Selectable("Write", mc.cond == MemCheckCondition::MEMCHECK_WRITE)) { mc.cond = MemCheckCondition::MEMCHECK_WRITE; } if (ImGui::Selectable("Read / Write", mc.cond == MemCheckCondition::MEMCHECK_READWRITE)) { mc.cond = MemCheckCondition::MEMCHECK_READWRITE; } if (ImGui::Selectable("Write On Change", mc.cond == MemCheckCondition::MEMCHECK_WRITE_ONCHANGE)) { mc.cond = MemCheckCondition::MEMCHECK_WRITE_ONCHANGE; } ImGui::EndCombo(); } ImGui::CheckboxFlags("Enabled", (int *)&mc.result, (int)BREAK_ACTION_PAUSE); ImGui::InputScalar("Start", ImGuiDataType_U32, &mc.start, NULL, NULL, "%08x", ImGuiInputTextFlags_CharsHexadecimal); ImGui::InputScalar("End", ImGuiDataType_U32, &mc.end, NULL, NULL, "%08x", ImGuiInputTextFlags_CharsHexadecimal); if (ImGui::Button("Delete")) { g_breakpoints.RemoveMemCheck(mcs[cfg.selectedMemCheck].start, mcs[cfg.selectedMemCheck].end); } 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 &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::GetPointerRange(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(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(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 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 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 &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([&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(cfg.selectedModuleId)) { PSPModule *mod = kernelObjects.GetFast(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 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(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 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; if (Achievements::HardcoreModeActive()) { ImGui::Begin("RetroAchievements hardcore mode"); ImGui::Text("The debugger may not be used when the\nRetroAchievements hardcore mode is enabled."); ImGui::Text("To use the debugger, go into Settings / Tools / RetroAchievements and disable them."); ImGui::End(); return; } // TODO: Pass mipsDebug in where needed instead. g_disassemblyManager.setCpu(mipsDebug); disasm_.View().setDebugger(mipsDebug); for (int i = 0; i < 4; i++) { mem_[i].View().setDebugger(mipsDebug); } // Watch the step counters to figure out when to update things. if (lastCpuStepCount_ != Core_GetSteppingCounter()) { lastCpuStepCount_ = Core_GetSteppingCounter(); snapshot_ = newSnapshot_; // Compare against the previous snapshot. Snapshot(currentMIPS); disasm_.NotifyStep(); } if (lastGpuStepCount_ != GPUStepping::GetSteppingCounter()) { // A GPU step has happened since last time. This means that we should re-center the cursor. // Snapshot(); lastGpuStepCount_ = GPUStepping::GetSteppingCounter(); SnapshotGPU(gpuDebug); geDebugger_.NotifyStep(); } ImControl control{}; if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("Debug")) { switch (coreState) { case CoreState::CORE_STEPPING_CPU: if (ImGui::MenuItem("Run")) { Core_Resume(); } break; case CoreState::CORE_RUNNING_CPU: if (ImGui::MenuItem("Break")) { Core_Break(BreakReason::DebugBreak); } break; default: break; } ImGui::Separator(); ImGui::MenuItem("Ignore bad memory accesses", nullptr, &g_Config.bIgnoreBadMemAccess); ImGui::MenuItem("Break on frame timeout", nullptr, &g_Config.bBreakOnFrameTimeout); ImGui::MenuItem("Don't break on start", nullptr, &g_Config.bAutoRun); // should really invert this bool! ImGui::MenuItem("Fast memory", nullptr, &g_Config.bFastMemory); ImGui::Separator(); 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("Close")) { g_Config.bShowImDebugger = false; } ImGui::EndMenu(); } 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(); 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 (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 (cfg_.framebuffersOpen) { DrawFramebuffersWindow(cfg_, gpuDebug->GetFramebufferManagerCommon()); } if (cfg_.texturesOpen) { DrawTexturesWindow(cfg_, gpuDebug->GetTextureCacheCommon()); } if (cfg_.logConfigOpen) { DrawLogConfig(cfg_); } if (cfg_.logOpen) { logWindow_.Draw(cfg_); } if (cfg_.jitViewerOpen) { jitViewer_.Draw(cfg_, control); } if (cfg_.displayOpen) { DrawDisplayWindow(cfg_, gpuDebug->GetFramebufferManagerCommon()); } if (cfg_.debugStatsOpen) { DrawDebugStatsWindow(cfg_); } if (cfg_.structViewerOpen) { structViewer_.Draw(cfg_, control, mipsDebug); } 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_.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 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", ¶mSFOOpen, 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); }