mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-27 08:05:41 +00:00
PSFLoader: Support loading minipsfs/libraries
This commit is contained in:
parent
baf8b1af43
commit
10135e08a2
|
@ -2,6 +2,7 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/file_system.h"
|
#include "common/file_system.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
|
#include "system.h"
|
||||||
#include "zlib.h"
|
#include "zlib.h"
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
@ -9,38 +10,60 @@ Log_SetChannel(PSFLoader);
|
||||||
|
|
||||||
namespace PSFLoader {
|
namespace PSFLoader {
|
||||||
|
|
||||||
std::string File::GetTagString(const char* tag_name, const char* default_value) const
|
std::optional<std::string> File::GetTagString(const char* tag_name) const
|
||||||
{
|
{
|
||||||
auto it = m_tags.find(tag_name);
|
auto it = m_tags.find(tag_name);
|
||||||
if (it == m_tags.end())
|
if (it == m_tags.end())
|
||||||
return default_value;
|
return std::nullopt;
|
||||||
|
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
int File::GetTagInt(const char* tag_name, int default_value) const
|
std::optional<int> File::GetTagInt(const char* tag_name) const
|
||||||
{
|
{
|
||||||
auto it = m_tags.find(tag_name);
|
auto it = m_tags.find(tag_name);
|
||||||
if (it == m_tags.end())
|
if (it == m_tags.end())
|
||||||
return default_value;
|
return std::nullopt;
|
||||||
|
|
||||||
return std::atoi(it->second.c_str());
|
return std::atoi(it->second.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
float File::GetTagFloat(const char* tag_name, float default_value) const
|
std::optional<float> File::GetTagFloat(const char* tag_name) const
|
||||||
{
|
{
|
||||||
auto it = m_tags.find(tag_name);
|
auto it = m_tags.find(tag_name);
|
||||||
if (it == m_tags.end())
|
if (it == m_tags.end())
|
||||||
return default_value;
|
return std::nullopt;
|
||||||
|
|
||||||
return static_cast<float>(std::atof(it->second.c_str()));
|
return static_cast<float>(std::atof(it->second.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string File::GetTagString(const char* tag_name, const char* default_value) const
|
||||||
|
{
|
||||||
|
std::optional<std::string> 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)
|
bool File::Load(const char* path)
|
||||||
{
|
{
|
||||||
auto fp = FileSystem::OpenManagedCFile(path, "rb");
|
auto fp = FileSystem::OpenManagedCFile(path, "rb");
|
||||||
if (!fp)
|
if (!fp)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to open PSF file '%s'", path);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// we could mmap this instead
|
// we could mmap this instead
|
||||||
std::fseek(fp.get(), 0, SEEK_END);
|
std::fseek(fp.get(), 0, SEEK_END);
|
||||||
|
@ -130,7 +153,7 @@ bool File::Load(const char* path)
|
||||||
|
|
||||||
if (!tag_key.empty())
|
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));
|
m_tags.emplace(std::move(tag_key), std::move(tag_value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,4 +162,82 @@ bool File::Load(const char* path)
|
||||||
return true;
|
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<std::string> 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<u32>(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
|
} // namespace PSFLoader
|
|
@ -28,6 +28,10 @@ public:
|
||||||
ALWAYS_INLINE const ProgramData& GetProgramData() const { return m_program_data; }
|
ALWAYS_INLINE const ProgramData& GetProgramData() const { return m_program_data; }
|
||||||
ALWAYS_INLINE const TagMap& GetTagMap() const { return m_tags; }
|
ALWAYS_INLINE const TagMap& GetTagMap() const { return m_tags; }
|
||||||
|
|
||||||
|
std::optional<std::string> GetTagString(const char* tag_name) const;
|
||||||
|
std::optional<int> GetTagInt(const char* tag_name) const;
|
||||||
|
std::optional<float> GetTagFloat(const char* tag_name) const;
|
||||||
|
|
||||||
std::string GetTagString(const char* tag_name, const char* default_value) const;
|
std::string GetTagString(const char* tag_name, const char* default_value) const;
|
||||||
int GetTagInt(const char* tag_name, int default_value) const;
|
int GetTagInt(const char* tag_name, int default_value) const;
|
||||||
float GetTagFloat(const char* tag_name, float default_value) const;
|
float GetTagFloat(const char* tag_name, float default_value) const;
|
||||||
|
@ -44,4 +48,6 @@ private:
|
||||||
TagMap m_tags;
|
TagMap m_tags;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool Load(const char* path);
|
||||||
|
|
||||||
} // namespace PSFLoader
|
} // namespace PSFLoader
|
|
@ -52,8 +52,6 @@ SystemBootParameters::~SystemBootParameters() = default;
|
||||||
namespace System {
|
namespace System {
|
||||||
|
|
||||||
static bool LoadEXE(const char* filename);
|
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);
|
static bool SetExpansionROM(const char* filename);
|
||||||
|
|
||||||
/// Opens CD image, preloading if needed.
|
/// Opens CD image, preloading if needed.
|
||||||
|
@ -241,7 +239,8 @@ bool IsExeFileName(const char* path)
|
||||||
bool IsPsfFileName(const char* path)
|
bool IsPsfFileName(const char* path)
|
||||||
{
|
{
|
||||||
const char* extension = std::strrchr(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)
|
bool IsM3UFileName(const char* path)
|
||||||
|
@ -688,7 +687,7 @@ bool Boot(const SystemBootParameters& params)
|
||||||
Shutdown();
|
Shutdown();
|
||||||
return false;
|
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());
|
g_host_interface->ReportFormattedError("Failed to load PSF file '%s'", params.filename.c_str());
|
||||||
Shutdown();
|
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);
|
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<const u8*>(buffer);
|
const u8* buffer_ptr = static_cast<const u8*>(buffer);
|
||||||
const u8* buffer_end = static_cast<const u8*>(buffer) + buffer_size;
|
const u8* buffer_end = static_cast<const u8*>(buffer) + buffer_size;
|
||||||
|
@ -1478,23 +1477,17 @@ bool LoadEXEFromBuffer(const void* buffer, u32 buffer_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// patch the BIOS to jump to the executable directly
|
// patch the BIOS to jump to the executable directly
|
||||||
const u32 r_pc = header.initial_pc;
|
if (patch_bios)
|
||||||
const u32 r_gp = header.initial_gp;
|
{
|
||||||
const u32 r_sp = header.initial_sp_base + header.initial_sp_offset;
|
const u32 r_pc = header.initial_pc;
|
||||||
const u32 r_fp = header.initial_sp_base + header.initial_sp_offset;
|
const u32 r_gp = header.initial_gp;
|
||||||
return BIOS::PatchBIOSForEXE(Bus::g_bios, Bus::BIOS_SIZE, r_pc, r_gp, r_sp, r_fp);
|
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)
|
return true;
|
||||||
{
|
|
||||||
Log_InfoPrintf("Loading PSF file from '%s'", filename);
|
|
||||||
|
|
||||||
PSFLoader::File psf;
|
|
||||||
if (!psf.Load(filename))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const std::vector<u8>& exe_data = psf.GetProgramData();
|
|
||||||
return LoadEXEFromBuffer(exe_data.data(), static_cast<u32>(exe_data.size()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SetExpansionROM(const char* filename)
|
bool SetExpansionROM(const char* filename)
|
||||||
|
|
|
@ -119,6 +119,10 @@ ALWAYS_INLINE_RELEASE TickCount UnscaleTicksToOverclock(TickCount ticks, TickCou
|
||||||
TickCount GetMaxSliceTicks();
|
TickCount GetMaxSliceTicks();
|
||||||
void UpdateOverclock();
|
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 GetFrameNumber();
|
||||||
u32 GetInternalFrameNumber();
|
u32 GetInternalFrameNumber();
|
||||||
void FrameDone();
|
void FrameDone();
|
||||||
|
|
|
@ -35,7 +35,7 @@ static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP(
|
||||||
"MainWindow",
|
"MainWindow",
|
||||||
"All File Types (*.bin *.img *.iso *.cue *.chd *.exe *.psexe *.psf *.m3u);;Single-Track Raw Images (*.bin *.img "
|
"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 "
|
"*.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()
|
ALWAYS_INLINE static QString getWindowTitle()
|
||||||
{
|
{
|
||||||
|
|
|
@ -1806,7 +1806,7 @@ void SDLHostInterface::DoStartDisc()
|
||||||
Assert(System::IsShutdown());
|
Assert(System::IsShutdown());
|
||||||
|
|
||||||
nfdchar_t* path = nullptr;
|
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;
|
return;
|
||||||
|
|
||||||
AddFormattedOSDMessage(2.0f, "Starting disc from '%s'...", path);
|
AddFormattedOSDMessage(2.0f, "Starting disc from '%s'...", path);
|
||||||
|
|
Loading…
Reference in a new issue