From 47d8e292eb60b787e39ef3981f8864a0d3244e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 25 Nov 2024 23:03:15 +0100 Subject: [PATCH] ImDebugger: Add basic filesystem browser. Add partial support for "save file dialogs" to System. --- Common/System/Request.h | 6 ++ Common/System/System.h | 2 + Core/FileSystems/DirectoryFileSystem.cpp | 2 +- Core/FileSystems/DirectoryFileSystem.h | 2 +- Core/FileSystems/MetaFileSystem.h | 5 ++ Core/FileSystems/VirtualDiscFileSystem.cpp | 14 ++- Core/FileSystems/VirtualDiscFileSystem.h | 4 +- UI/ImDebugger/ImDebugger.cpp | 100 ++++++++++++++++++++- UI/ImDebugger/ImDebugger.h | 9 ++ UI/ImDebugger/ImDisasmView.h | 1 - UWP/PPSSPP_UWPMain.cpp | 3 + Windows/MainWindowMenu.cpp | 3 +- Windows/main.cpp | 57 ++++++------ 13 files changed, 165 insertions(+), 43 deletions(-) diff --git a/Common/System/Request.h b/Common/System/Request.h index b16fc43f97..e140877d9f 100644 --- a/Common/System/Request.h +++ b/Common/System/Request.h @@ -13,6 +13,7 @@ typedef std::function Reque typedef std::function RequestFailedCallback; typedef int RequesterToken; + #define NO_REQUESTER_TOKEN -1 #define NON_EPHEMERAL_TOKEN -2 @@ -101,6 +102,7 @@ enum class BrowseFileType { DB, SOUND_EFFECT, ZIP, + SYMBOL_MAP, ANY, }; @@ -108,6 +110,10 @@ inline void System_BrowseForFile(RequesterToken token, std::string_view title, B g_requestManager.MakeSystemRequest(SystemRequestType::BROWSE_FOR_FILE, token, callback, failedCallback, title, "", (int)type); } +inline void System_BrowseForFileSave(RequesterToken token, std::string_view title, std::string_view defaultFilename, BrowseFileType type, RequestCallback callback, RequestFailedCallback failedCallback = nullptr) { + g_requestManager.MakeSystemRequest(SystemRequestType::BROWSE_FOR_FILE_SAVE, token, callback, failedCallback, title, defaultFilename, (int)type); +} + void System_BrowseForFolder(RequesterToken token, std::string_view title, const Path &initialPath, RequestCallback callback, RequestFailedCallback failedCallback = nullptr); // The returned string is username + '\n' + password. diff --git a/Common/System/System.h b/Common/System/System.h index 3658451816..03826f4468 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -70,6 +70,8 @@ enum class SystemRequestType { BROWSE_FOR_FILE, BROWSE_FOR_FOLDER, + BROWSE_FOR_FILE_SAVE, + EXIT_APP, RESTART_APP, // For graphics backend changes RECREATE_ACTIVITY, // Android diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index eb2d6c68d4..ed44591f2c 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -964,7 +964,7 @@ VFSFileSystem::~VFSFileSystem() { entries.clear(); } -std::string VFSFileSystem::GetLocalPath(const std::string &localPath) { +std::string VFSFileSystem::GetLocalPath(const std::string &localPath) const { return basePath + localPath; } diff --git a/Core/FileSystems/DirectoryFileSystem.h b/Core/FileSystems/DirectoryFileSystem.h index d0a9f9d6f5..fc046f1186 100644 --- a/Core/FileSystems/DirectoryFileSystem.h +++ b/Core/FileSystems/DirectoryFileSystem.h @@ -145,5 +145,5 @@ private: std::string basePath; IHandleAllocator *hAlloc; - std::string GetLocalPath(const std::string &localpath); + std::string GetLocalPath(const std::string &localpath) const; }; diff --git a/Core/FileSystems/MetaFileSystem.h b/Core/FileSystems/MetaFileSystem.h index 54cc5c9d8f..4e9a0406a1 100644 --- a/Core/FileSystems/MetaFileSystem.h +++ b/Core/FileSystems/MetaFileSystem.h @@ -65,6 +65,11 @@ public: void UnmountAll(); void Unmount(const std::string &prefix); + // Would like to make this const, but... + std::vector &GetMounts() { + return fileSystems; + } + // The pointer returned from these are for temporary usage only. Do not store. IFileSystem *GetSystem(const std::string &prefix); IFileSystem *GetSystemFromFilename(const std::string &filename); diff --git a/Core/FileSystems/VirtualDiscFileSystem.cpp b/Core/FileSystems/VirtualDiscFileSystem.cpp index c4501705e8..ad5a2ec604 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.cpp +++ b/Core/FileSystems/VirtualDiscFileSystem.cpp @@ -243,7 +243,7 @@ void VirtualDiscFileSystem::DoState(PointerWrap &p) // We don't savestate handlers (loaded on fs load), but if they change, it may not load properly. } -Path VirtualDiscFileSystem::GetLocalPath(std::string localpath) { +Path VirtualDiscFileSystem::GetLocalPath(std::string localpath) const { if (localpath.empty()) return basePath; @@ -303,18 +303,14 @@ int VirtualDiscFileSystem::getFileListIndex(std::string &fileName) return (int)fileList.size()-1; } -int VirtualDiscFileSystem::getFileListIndex(u32 accessBlock, u32 accessSize, bool blockMode) -{ - for (size_t i = 0; i < fileList.size(); i++) - { - if (fileList[i].firstBlock <= accessBlock) - { +int VirtualDiscFileSystem::getFileListIndex(u32 accessBlock, u32 accessSize, bool blockMode) const { + for (size_t i = 0; i < fileList.size(); i++) { + if (fileList[i].firstBlock <= accessBlock) { u32 sectorOffset = (accessBlock-fileList[i].firstBlock)*2048; u32 totalFileSize = blockMode ? (fileList[i].totalSize+2047) & ~2047 : fileList[i].totalSize; u32 endOffset = sectorOffset+accessSize; - if (endOffset <= totalFileSize) - { + if (endOffset <= totalFileSize) { return (int)i; } } diff --git a/Core/FileSystems/VirtualDiscFileSystem.h b/Core/FileSystems/VirtualDiscFileSystem.h index e8110e0b2d..5889915a15 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.h +++ b/Core/FileSystems/VirtualDiscFileSystem.h @@ -60,8 +60,8 @@ private: void LoadFileListIndex(); // Warning: modifies input string. int getFileListIndex(std::string &fileName); - int getFileListIndex(u32 accessBlock, u32 accessSize, bool blockMode = false); - Path GetLocalPath(std::string localpath); + int getFileListIndex(u32 accessBlock, u32 accessSize, bool blockMode = false) const; + Path GetLocalPath(std::string localpath) const; typedef void *HandlerLibrary; typedef int HandlerHandle; diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index e0c8308f6d..1f68054c68 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -5,6 +5,7 @@ #include "Common/StringUtils.h" #include "Core/Config.h" +#include "Core/System.h" #include "Core/RetroAchievements.h" #include "Core/Core.h" #include "Core/Debugger/DebugInterface.h" @@ -12,6 +13,7 @@ #include "Core/Debugger/Breakpoints.h" #include "Core/MIPS/MIPSDebugInterface.h" #include "Core/MIPS/MIPSTables.h" +#include "Core/FileSystems/MetaFileSystem.h" #include "Core/Debugger/SymbolMap.h" #include "Core/MemMap.h" #include "Core/HLE/HLE.h" @@ -32,6 +34,8 @@ #include "UI/ImDebugger/ImDebugger.h" #include "UI/ImDebugger/ImGe.h" +extern bool g_TakeScreenshot; + void DrawRegisterView(MIPSDebugInterface *mipsDebug, bool *open) { if (!ImGui::Begin("Registers", open)) { ImGui::End(); @@ -170,6 +174,40 @@ void DrawThreadView(ImConfig &cfg) { ImGui::End(); } +// TODO: Add popup menu, export file, export dir, etc... +static void RecurseFileSystem(IFileSystem *fs, std::string path) { + 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); + ImGui::TreePop(); + } + } else { + ImGui::TextUnformatted(file.name.c_str()); + } + } +} + +static void DrawFilesystemBrowser(ImConfig &cfg) { + if (!ImGui::Begin("File System", &cfg.filesystemBrowserOpen)) { + ImGui::End(); + return; + } + + for (auto &fs : pspFileSystem.GetMounts()) { + std::string path; + if (ImGui::TreeNode(fs.prefix.c_str())) { + auto system = fs.system; + RecurseFileSystem(system.get(), path); + ImGui::TreePop(); + } + } + + ImGui::End(); +} + static const char *MemCheckConditionToString(MemCheckCondition cond) { switch (cond) { case MEMCHECK_READ: return "Read"; @@ -496,6 +534,10 @@ void DrawHLEModules(ImConfig &config) { ImGui::End(); } +ImDebugger::ImDebugger() { + reqToken_ = g_requestManager.GenerateRequesterToken(); +} + void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebug) { // Snapshot the coreState to avoid inconsistency. const CoreState coreState = ::coreState; @@ -520,6 +562,47 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu Core_Break("Menu:Break"); } } + ImGui::Separator(); + ImGui::MenuItem("Ignore bad memory accesses", nullptr, &g_Config.bIgnoreBadMemAccess); + ImGui::Separator(); + /* + // Symbol stuff. Move to separate menu? + // Doesn't quite seem to work yet. + if (ImGui::MenuItem("Load symbol map...")) { + System_BrowseForFile(reqToken_, "Load symbol map", BrowseFileType::SYMBOL_MAP, [&](const char *responseString, int) { + Path path(responseString); + if (!g_symbolMap->LoadSymbolMap(path)) { + ERROR_LOG(Log::Common, "Failed to load symbol map"); + } + disasm_.DirtySymbolMap(); + }); + } + if (ImGui::MenuItem("Save symbol map...")) { + System_BrowseForFileSave(reqToken_, "Save symbol map", "symbols.map", BrowseFileType::SYMBOL_MAP, [](const char *responseString, int) { + Path path(responseString); + if (!g_symbolMap->SaveSymbolMap(path)) { + ERROR_LOG(Log::Common, "Failed to save symbol map"); + } + }); + } + */ + if (ImGui::MenuItem("Reset symbol map")) { + g_symbolMap->Clear(); + disasm_.DirtySymbolMap(); + // NotifyDebuggerMapLoaded(); + } + ImGui::Separator(); + if (ImGui::MenuItem("Take screenshot")) { + g_TakeScreenshot = true; + } + if (ImGui::MenuItem("Restart graphics")) { + System_PostUIMessage(UIMessage::RESTART_GRAPHICS); + } + if (System_GetPropertyBool(SYSPROP_HAS_TEXT_CLIPBOARD)) { + if (ImGui::MenuItem("Copy memory base to clipboard")) { + System_CopyStringToClipboard(StringFromFormat("%016llx", (uint64_t)(uintptr_t)Memory::base)); + } + } ImGui::EndMenu(); } if (ImGui::BeginMenu("CPU")) { @@ -530,6 +613,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu ImGui::EndMenu(); } if (ImGui::BeginMenu("OS HLE")) { + ImGui::MenuItem("File System Browser", nullptr, &cfg_.filesystemBrowserOpen); ImGui::MenuItem("HLE Threads", nullptr, &cfg_.threadsOpen); ImGui::MenuItem("HLE Modules",nullptr, &cfg_.modulesOpen); ImGui::MenuItem("sceAtrac", nullptr, &cfg_.atracOpen); @@ -575,6 +659,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu DrawBreakpointsView(mipsDebug, cfg_); } + if (cfg_.filesystemBrowserOpen) { + DrawFilesystemBrowser(cfg_); + } + if (cfg_.threadsOpen) { DrawThreadView(cfg_); } @@ -664,6 +752,9 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, bool *open, CoreState c } if (ImGui::BeginPopup("disSearch")) { + if (ImGui::IsWindowAppearing()) { + ImGui::SetKeyboardFocusHere(); + } if (ImGui::InputText("Search", searchTerm_, sizeof(searchTerm_), ImGuiInputTextFlags_EnterReturnsTrue)) { disasmView_.Search(searchTerm_); ImGui::CloseCurrentPopup(); @@ -684,7 +775,14 @@ void ImDisasmWindow::Draw(MIPSDebugInterface *mipsDebug, bool *open, CoreState c } ImGui::SameLine(); - ImGui::Checkbox("Follow PC", &disasmView_.followPC_); + if (ImGui::SmallButton("Settings")) { + ImGui::OpenPopup("disSettings"); + } + + if (ImGui::BeginPopup("disSettings")) { + ImGui::Checkbox("Follow PC", &disasmView_.followPC_); + ImGui::EndPopup(); + } ImGui::SetNextItemWidth(100); if (ImGui::InputScalar("Go to addr: ", ImGuiDataType_U32, &gotoAddr_, NULL, NULL, "%08X", ImGuiInputTextFlags_EnterReturnsTrue)) { diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 8e155f4cd6..45bf6dfd33 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -8,6 +8,8 @@ #include "Common/CommonTypes.h" #include "Common/Log.h" +#include "Common/System/Request.h" + #include "Core/Core.h" #include "Core/Debugger/DisassemblyManager.h" @@ -32,6 +34,9 @@ public: ImDisasmView &View() { return disasmView_; } + void DirtySymbolMap() { + symsDirty_ = true; + } private: // We just keep the state directly in the window. Can refactor later. @@ -69,6 +74,7 @@ struct ImConfig { bool structViewerOpen = false; bool framebuffersOpen = false; bool styleEditorOpen = false; + bool filesystemBrowserOpen = false; // HLE explorer settings // bool filterByUsed = true; @@ -90,8 +96,11 @@ struct ImUiCommand { }; struct ImDebugger { + ImDebugger(); void Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebug); + RequesterToken reqToken_; + ImDisasmWindow disasm_; ImLuaConsole luaConsole_; ImStructViewer structViewer_; diff --git a/UI/ImDebugger/ImDisasmView.h b/UI/ImDebugger/ImDisasmView.h index 1ee22a558f..088ce753ac 100644 --- a/UI/ImDebugger/ImDisasmView.h +++ b/UI/ImDebugger/ImDisasmView.h @@ -23,7 +23,6 @@ public: // Public variables bounds to imgui checkboxes bool followPC_ = true; - void Draw(ImDrawList *drawList); void PopupMenu(); diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 140d0948fd..e98fc63a7b 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -514,6 +514,9 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string case BrowseFileType::ZIP: supportedExtensions = { ".zip" }; break; + case BrowseFileType::SYMBOL_MAP: + supportedExtensions = { ".map" }; + break; case BrowseFileType::DB: supportedExtensions = { ".db" }; break; diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index 1e3f4cc3fa..8d96989226 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -813,7 +813,7 @@ namespace MainWindow { case ID_DEBUG_MEMORYBASE: { - W32Util::CopyTextToClipboard(hWnd, ConvertUTF8ToWString(StringFromFormat("%016llx", (uint64_t)(uintptr_t)Memory::base))); + System_CopyStringToClipboard(StringFromFormat("%016llx", (uint64_t)(uintptr_t)Memory::base)); break; } @@ -823,7 +823,6 @@ namespace MainWindow { if (!InputBox_GetString(hInst, hWnd, L"Disc filename", filename, filename)) { break; } - const char *lastSlash = strrchr(filename.c_str(), '/'); if (lastSlash) { fn = lastSlash + 1; diff --git a/Windows/main.cpp b/Windows/main.cpp index 03f90d34c9..f91476545f 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -489,7 +489,7 @@ void System_Notify(SystemNotification notification) { } } -static std::wstring MakeFilter(std::wstring filter) { +static std::wstring FinalizeFilter(std::wstring filter) { for (size_t i = 0; i < filter.length(); i++) { if (filter[i] == '|') filter[i] = '\0'; @@ -497,6 +497,27 @@ static std::wstring MakeFilter(std::wstring filter) { return filter; } +static std::wstring MakeWindowsFilter(BrowseFileType type) { + switch (type) { + case BrowseFileType::BOOTABLE: + return FinalizeFilter(L"All supported file types (*.iso *.cso *.chd *.pbp *.elf *.prx *.zip *.ppdmp)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx;*.zip;*.ppdmp|PSP ROMs (*.iso *.cso *.chd *.pbp *.elf *.prx)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx|Homebrew/Demos installers (*.zip)|*.zip|All files (*.*)|*.*||"); + case BrowseFileType::INI: + return FinalizeFilter(L"Ini files (*.ini)|*.ini|All files (*.*)|*.*||"); + case BrowseFileType::ZIP: + return FinalizeFilter(L"ZIP files (*.zip)|*.zip|All files (*.*)|*.*||"); + case BrowseFileType::DB: + return FinalizeFilter(L"Cheat db files (*.db)|*.db|All files (*.*)|*.*||"); + case BrowseFileType::SOUND_EFFECT: + return FinalizeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||"); + case BrowseFileType::SYMBOL_MAP: + return FinalizeFilter(L"Symbol map files (*.ppmap)|*.ppmap|All files (*.*)|*.*||"); + case BrowseFileType::ANY: + return FinalizeFilter(L"All files (*.*)|*.*||"); + default: + return std::wstring(); + } +} + bool System_MakeRequest(SystemRequestType type, int requestId, const std::string ¶m1, const std::string ¶m2, int64_t param3, int64_t param4) { switch (type) { case SystemRequestType::EXIT_APP: @@ -574,7 +595,7 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string std::thread([=] { std::string out; if (W32Util::BrowseForFileName(true, MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), nullptr, - MakeFilter(L"All supported images (*.jpg *.jpeg *.png)|*.jpg;*.jpeg;*.png|All files (*.*)|*.*||").c_str(), L"jpg", out)) { + FinalizeFilter(L"All supported images (*.jpg *.jpeg *.png)|*.jpg;*.jpeg;*.png|All files (*.*)|*.*||").c_str(), L"jpg", out)) { g_requestManager.PostSystemSuccess(requestId, out.c_str()); } else { g_requestManager.PostSystemFailure(requestId); @@ -582,35 +603,19 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string }).detach(); return true; case SystemRequestType::BROWSE_FOR_FILE: + case SystemRequestType::BROWSE_FOR_FILE_SAVE: { - BrowseFileType type = (BrowseFileType)param3; - std::wstring filter; - switch (type) { - case BrowseFileType::BOOTABLE: - filter = MakeFilter(L"All supported file types (*.iso *.cso *.chd *.pbp *.elf *.prx *.zip *.ppdmp)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx;*.zip;*.ppdmp|PSP ROMs (*.iso *.cso *.chd *.pbp *.elf *.prx)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx|Homebrew/Demos installers (*.zip)|*.zip|All files (*.*)|*.*||"); - break; - case BrowseFileType::INI: - filter = MakeFilter(L"Ini files (*.ini)|*.ini|All files (*.*)|*.*||"); - break; - case BrowseFileType::ZIP: - filter = MakeFilter(L"ZIP files (*.zip)|*.zip|All files (*.*)|*.*||"); - break; - case BrowseFileType::DB: - filter = MakeFilter(L"Cheat db files (*.db)|*.db|All files (*.*)|*.*||"); - break; - case BrowseFileType::SOUND_EFFECT: - filter = MakeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||"); - break; - case BrowseFileType::ANY: - filter = MakeFilter(L"All files (*.*)|*.*||"); - break; - default: + const BrowseFileType browseType = (BrowseFileType)param3; + std::wstring filter = MakeWindowsFilter(browseType); + std::wstring initialFilename = ConvertUTF8ToWString(param2); // TODO: Plumb through + if (filter.empty()) { + // Unsupported. return false; } - + bool load = type == SystemRequestType::BROWSE_FOR_FILE; std::thread([=] { std::string out; - if (W32Util::BrowseForFileName(true, MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), nullptr, filter.c_str(), L"", out)) { + if (W32Util::BrowseForFileName(load, MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), nullptr, filter.c_str(), L"", out)) { g_requestManager.PostSystemSuccess(requestId, out.c_str()); } else { g_requestManager.PostSystemFailure(requestId);