From 10135e08a2f31bae7e35632abefe772be6c770e1 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 24 Jan 2021 13:17:22 +1000 Subject: [PATCH] PSFLoader: Support loading minipsfs/libraries --- src/core/psf_loader.cpp | 115 +++++++++++++++++++-- src/core/psf_loader.h | 6 ++ src/core/system.cpp | 35 +++---- src/core/system.h | 4 + src/duckstation-qt/mainwindow.cpp | 2 +- src/duckstation-sdl/sdl_host_interface.cpp | 2 +- 6 files changed, 134 insertions(+), 30 deletions(-) diff --git a/src/core/psf_loader.cpp b/src/core/psf_loader.cpp index 5c9f51382..efff009f2 100644 --- a/src/core/psf_loader.cpp +++ b/src/core/psf_loader.cpp @@ -2,6 +2,7 @@ #include "common/assert.h" #include "common/file_system.h" #include "common/log.h" +#include "system.h" #include "zlib.h" #include #include @@ -9,38 +10,60 @@ Log_SetChannel(PSFLoader); namespace PSFLoader { -std::string File::GetTagString(const char* tag_name, const char* default_value) const +std::optional File::GetTagString(const char* tag_name) const { auto it = m_tags.find(tag_name); if (it == m_tags.end()) - return default_value; + return std::nullopt; return it->second; } -int File::GetTagInt(const char* tag_name, int default_value) const +std::optional File::GetTagInt(const char* tag_name) const { auto it = m_tags.find(tag_name); if (it == m_tags.end()) - return default_value; + return std::nullopt; return std::atoi(it->second.c_str()); } -float File::GetTagFloat(const char* tag_name, float default_value) const +std::optional File::GetTagFloat(const char* tag_name) const { auto it = m_tags.find(tag_name); if (it == m_tags.end()) - return default_value; + return std::nullopt; return static_cast(std::atof(it->second.c_str())); } +std::string File::GetTagString(const char* tag_name, const char* default_value) const +{ + std::optional value(GetTagString(tag_name)); + if (value.has_value()) + return value.value(); + + return default_value; +} + +int File::GetTagInt(const char* tag_name, int default_value) const +{ + return GetTagInt(tag_name).value_or(default_value); +} + +float File::GetTagFloat(const char* tag_name, float default_value) const +{ + return GetTagFloat(tag_name).value_or(default_value); +} + bool File::Load(const char* path) { auto fp = FileSystem::OpenManagedCFile(path, "rb"); if (!fp) + { + Log_ErrorPrintf("Failed to open PSF file '%s'", path); return false; + } // we could mmap this instead std::fseek(fp.get(), 0, SEEK_END); @@ -130,7 +153,7 @@ bool File::Load(const char* path) if (!tag_key.empty()) { - Log_InfoPrintf("PSF Tag: '%s' = '%s'", tag_key.c_str(), tag_value.c_str()); + Log_DevPrintf("PSF Tag: '%s' = '%s'", tag_key.c_str(), tag_value.c_str()); m_tags.emplace(std::move(tag_key), std::move(tag_value)); } } @@ -139,4 +162,82 @@ bool File::Load(const char* path) return true; } +static std::string GetLibraryPSFPath(const char* main_path, const char* lib_path) +{ + std::string path(FileSystem::GetPathDirectory(main_path)); + path += FS_OSPATH_SEPARATOR_CHARACTER; + path += lib_path; + return path; +} + +static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0) +{ + // don't recurse past 10 levels just in case of broken files + if (depth >= 10) + { + Log_ErrorPrintf("Recursion depth exceeded when loading PSF '%s'", path); + return false; + } + + File file; + if (!file.Load(path)) + { + Log_ErrorPrintf("Failed to load main PSF '%s'", path); + return false; + } + + // load the main parent library - this has to be done first so the specified PSF takes precedence + std::optional lib_name(file.GetTagString("_lib")); + if (lib_name.has_value()) + { + const std::string lib_path(GetLibraryPSFPath(path, lib_name->c_str())); + Log_InfoPrintf("Loading main parent PSF '%s'", lib_path.c_str()); + + // We should use the initial SP/PC from the **first** parent lib. + const bool lib_use_pc_sp = (depth == 0); + if (!LoadLibraryPSF(lib_path.c_str(), lib_use_pc_sp, depth + 1)) + { + Log_ErrorPrintf("Failed to load main parent PSF '%s'", lib_path.c_str()); + return false; + } + + // Don't apply the PC/SP from the minipsf file. + if (lib_use_pc_sp) + use_pc_sp = false; + } + + // apply the main psf + if (!System::InjectEXEFromBuffer(file.GetProgramData().data(), static_cast(file.GetProgramData().size()), + use_pc_sp)) + { + Log_ErrorPrintf("Failed to parse EXE from PSF '%s'", path); + return false; + } + + // load any other parent psfs + u32 lib_counter = 2; + for (;;) + { + lib_name = file.GetTagString(TinyString::FromFormat("_lib%u", lib_counter++)); + if (!lib_name.has_value()) + break; + + const std::string lib_path(GetLibraryPSFPath(path, lib_name->c_str())); + Log_InfoPrintf("Loading parent PSF '%s'", lib_path.c_str()); + if (!LoadLibraryPSF(lib_path.c_str(), false, depth + 1)) + { + Log_ErrorPrintf("Failed to load parent PSF '%s'", lib_path.c_str()); + return false; + } + } + + return true; +} + +bool Load(const char* path) +{ + Log_InfoPrintf("Loading PSF file from '%s'", path); + return LoadLibraryPSF(path, true); +} + } // namespace PSFLoader \ No newline at end of file diff --git a/src/core/psf_loader.h b/src/core/psf_loader.h index c2b6aa380..1c242c800 100644 --- a/src/core/psf_loader.h +++ b/src/core/psf_loader.h @@ -28,6 +28,10 @@ public: ALWAYS_INLINE const ProgramData& GetProgramData() const { return m_program_data; } ALWAYS_INLINE const TagMap& GetTagMap() const { return m_tags; } + std::optional GetTagString(const char* tag_name) const; + std::optional GetTagInt(const char* tag_name) const; + std::optional GetTagFloat(const char* tag_name) const; + std::string GetTagString(const char* tag_name, const char* default_value) const; int GetTagInt(const char* tag_name, int default_value) const; float GetTagFloat(const char* tag_name, float default_value) const; @@ -44,4 +48,6 @@ private: TagMap m_tags; }; +bool Load(const char* path); + } // namespace PSFLoader \ No newline at end of file diff --git a/src/core/system.cpp b/src/core/system.cpp index 693e41069..acdb6782a 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -52,8 +52,6 @@ SystemBootParameters::~SystemBootParameters() = default; namespace System { static bool LoadEXE(const char* filename); -static bool LoadEXEFromBuffer(const void* buffer, u32 buffer_size); -static bool LoadPSF(const char* filename); static bool SetExpansionROM(const char* filename); /// Opens CD image, preloading if needed. @@ -241,7 +239,8 @@ bool IsExeFileName(const char* path) bool IsPsfFileName(const char* path) { const char* extension = std::strrchr(path, '.'); - return (extension && StringUtil::Strcasecmp(extension, ".psf") == 0); + return (extension && + (StringUtil::Strcasecmp(extension, ".psf") == 0 || StringUtil::Strcasecmp(extension, ".minipsf") == 0)); } bool IsM3UFileName(const char* path) @@ -688,7 +687,7 @@ bool Boot(const SystemBootParameters& params) Shutdown(); return false; } - else if (psf_boot && !LoadPSF(params.filename.c_str())) + else if (psf_boot && !PSFLoader::Load(params.filename.c_str())) { g_host_interface->ReportFormattedError("Failed to load PSF file '%s'", params.filename.c_str()); Shutdown(); @@ -1434,7 +1433,7 @@ bool LoadEXE(const char* filename) return BIOS::PatchBIOSForEXE(Bus::g_bios, Bus::BIOS_SIZE, r_pc, r_gp, r_sp, r_fp); } -bool LoadEXEFromBuffer(const void* buffer, u32 buffer_size) +bool InjectEXEFromBuffer(const void* buffer, u32 buffer_size, bool patch_bios) { const u8* buffer_ptr = static_cast(buffer); const u8* buffer_end = static_cast(buffer) + buffer_size; @@ -1478,23 +1477,17 @@ bool LoadEXEFromBuffer(const void* buffer, u32 buffer_size) } // patch the BIOS to jump to the executable directly - const u32 r_pc = header.initial_pc; - const u32 r_gp = header.initial_gp; - const u32 r_sp = header.initial_sp_base + header.initial_sp_offset; - const u32 r_fp = header.initial_sp_base + header.initial_sp_offset; - return BIOS::PatchBIOSForEXE(Bus::g_bios, Bus::BIOS_SIZE, r_pc, r_gp, r_sp, r_fp); -} + if (patch_bios) + { + const u32 r_pc = header.initial_pc; + const u32 r_gp = header.initial_gp; + const u32 r_sp = header.initial_sp_base + header.initial_sp_offset; + const u32 r_fp = header.initial_sp_base + header.initial_sp_offset; + if (!BIOS::PatchBIOSForEXE(Bus::g_bios, Bus::BIOS_SIZE, r_pc, r_gp, r_sp, r_fp)) + return false; + } -bool LoadPSF(const char* filename) -{ - Log_InfoPrintf("Loading PSF file from '%s'", filename); - - PSFLoader::File psf; - if (!psf.Load(filename)) - return false; - - const std::vector& exe_data = psf.GetProgramData(); - return LoadEXEFromBuffer(exe_data.data(), static_cast(exe_data.size())); + return true; } bool SetExpansionROM(const char* filename) diff --git a/src/core/system.h b/src/core/system.h index a718e4e35..6711a7e1e 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -119,6 +119,10 @@ ALWAYS_INLINE_RELEASE TickCount UnscaleTicksToOverclock(TickCount ticks, TickCou TickCount GetMaxSliceTicks(); void UpdateOverclock(); +/// Injects a PS-EXE into memory at its specified load location. If patch_loader is set, the BIOS will be patched to +/// direct execution to this executable. +bool InjectEXEFromBuffer(const void* buffer, u32 buffer_size, bool patch_loader = true); + u32 GetFrameNumber(); u32 GetInternalFrameNumber(); void FrameDone(); diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index a364f3af4..48f2d3ab6 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -35,7 +35,7 @@ static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP( "MainWindow", "All File Types (*.bin *.img *.iso *.cue *.chd *.exe *.psexe *.psf *.m3u);;Single-Track Raw Images (*.bin *.img " "*.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;PlayStation Executables (*.exe *.psexe);;Portable Sound Format " - "Files (*.psf);;Playlists (*.m3u)"); + "Files (*.psf *.minipsf);;Playlists (*.m3u)"); ALWAYS_INLINE static QString getWindowTitle() { diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 64522151a..95c5781ee 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -1806,7 +1806,7 @@ void SDLHostInterface::DoStartDisc() Assert(System::IsShutdown()); nfdchar_t* path = nullptr; - if (!NFD_OpenDialog("bin,img,iso,cue,chd,exe,psexe,psf", nullptr, &path) || !path || std::strlen(path) == 0) + if (!NFD_OpenDialog("bin,img,iso,cue,chd,exe,psexe,psf,minipsf", nullptr, &path) || !path || std::strlen(path) == 0) return; AddFormattedOSDMessage(2.0f, "Starting disc from '%s'...", path);