System: PSF (Portable Sound Format) loading support

This commit is contained in:
Connor McLaughlin 2020-04-17 00:29:56 +10:00
parent 38847de8ee
commit 205297ac27
9 changed files with 288 additions and 10 deletions

View file

@ -54,6 +54,8 @@ add_library(core
memory_card.h
pad.cpp
pad.h
psf_loader.cpp
psf_loader.h
save_state_version.h
settings.cpp
settings.h
@ -83,7 +85,7 @@ set(RECOMPILER_SRCS
target_include_directories(core PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_include_directories(core PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
target_link_libraries(core PUBLIC Threads::Threads common imgui tinyxml2)
target_link_libraries(core PUBLIC Threads::Threads common imgui tinyxml2 zlib)
target_link_libraries(core PRIVATE glad stb)
if(WIN32)

View file

@ -77,6 +77,7 @@
<ClCompile Include="memory_card.cpp" />
<ClCompile Include="pad.cpp" />
<ClCompile Include="controller.cpp" />
<ClCompile Include="psf_loader.cpp" />
<ClCompile Include="settings.cpp" />
<ClCompile Include="sio.cpp" />
<ClCompile Include="spu.cpp" />
@ -116,6 +117,7 @@
<ClInclude Include="memory_card.h" />
<ClInclude Include="pad.h" />
<ClInclude Include="controller.h" />
<ClInclude Include="psf_loader.h" />
<ClInclude Include="save_state_version.h" />
<ClInclude Include="settings.h" />
<ClInclude Include="sio.h" />
@ -288,7 +290,7 @@
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -313,7 +315,7 @@
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -338,7 +340,7 @@
<PreprocessorDefinitions>WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@ -366,7 +368,7 @@
<PreprocessorDefinitions>WITH_RECOMPILER=1;_ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<BasicRuntimeChecks>Default</BasicRuntimeChecks>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
@ -393,7 +395,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -419,7 +421,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -446,7 +448,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>false</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>
@ -472,7 +474,7 @@
<Optimization>MaxSpeed</Optimization>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WITH_RECOMPILER=1;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)dep\msvc\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xbyak\xbyak;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LanguageStandard>stdcpp17</LanguageStandard>

View file

@ -40,6 +40,7 @@
<ClCompile Include="host_display.cpp" />
<ClCompile Include="timing_event.cpp" />
<ClCompile Include="cdrom_async_reader.cpp" />
<ClCompile Include="psf_loader.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -81,6 +82,7 @@
<ClInclude Include="analog_controller.h" />
<ClInclude Include="timing_event.h" />
<ClInclude Include="cdrom_async_reader.h" />
<ClInclude Include="psf_loader.h" />
</ItemGroup>
<ItemGroup>
<None Include="cpu_core.inl" />

View file

@ -201,6 +201,12 @@ bool GameList::IsExeFileName(const char* path)
(StringUtil::Strcasecmp(extension, ".exe") == 0 || StringUtil::Strcasecmp(extension, ".psexe") == 0));
}
bool GameList::IsPsfFileName(const char* path)
{
const char* extension = std::strrchr(path, '.');
return (extension && StringUtil::Strcasecmp(extension, ".psf") == 0);
}
static std::string_view GetFileNameFromPath(const char* path)
{
const char* filename_end = path + std::strlen(path);

View file

@ -50,6 +50,9 @@ public:
/// Returns true if the filename is a PlayStation executable we can inject.
static bool IsExeFileName(const char* path);
/// Returns true if the filename is a Portable Sound Format file we can uncompress/load.
static bool IsPsfFileName(const char* path);
static std::string GetGameCodeForImage(CDImage* cdi);
static std::string GetGameCodeForPath(const char* image_path);
static DiscRegion GetRegionForCode(std::string_view code);

142
src/core/psf_loader.cpp Normal file
View file

@ -0,0 +1,142 @@
#include "psf_loader.h"
#include "common/assert.h"
#include "common/file_system.h"
#include "common/log.h"
#include "zlib.h"
#include <cctype>
#include <cstring>
Log_SetChannel(PSFLoader);
namespace PSFLoader {
std::string File::GetTagString(const char* tag_name, const char* default_value) const
{
auto it = m_tags.find(tag_name);
if (it == m_tags.end())
return default_value;
return it->second;
}
int File::GetTagInt(const char* tag_name, int default_value) const
{
auto it = m_tags.find(tag_name);
if (it == m_tags.end())
return default_value;
return std::atoi(it->second.c_str());
}
float File::GetTagFloat(const char* tag_name, float default_value) const
{
auto it = m_tags.find(tag_name);
if (it == m_tags.end())
return default_value;
return static_cast<float>(std::atof(it->second.c_str()));
}
bool File::Load(const char* path)
{
auto fp = FileSystem::OpenManagedCFile(path, "rb");
if (!fp)
return false;
// we could mmap this instead
std::fseek(fp.get(), 0, SEEK_END);
const u32 file_size = static_cast<u32>(std::ftell(fp.get()));
std::fseek(fp.get(), 0, SEEK_SET);
std::vector<u8> file_data(file_size);
if (std::fread(file_data.data(), 1, file_size, fp.get()) != file_size)
{
Log_ErrorPrintf("Failed to read data from PSF '%s'", path);
return false;
}
const u8* file_pointer = file_data.data();
const u8* file_pointer_end = file_data.data() + file_data.size();
PSFHeader header;
std::memcpy(&header, file_pointer, sizeof(header));
file_pointer += sizeof(header);
if (header.id[0] != 'P' || header.id[1] != 'S' || header.id[2] != 'F' || header.version != 0x01 ||
header.compressed_program_size == 0 ||
(sizeof(header) + header.reserved_area_size + header.compressed_program_size) > file_size)
{
Log_ErrorPrintf("Invalid or incompatible header in PSF '%s'", path);
return false;
}
file_pointer += header.reserved_area_size;
m_program_data.resize(MAX_PROGRAM_SIZE);
z_stream strm = {};
strm.avail_in = static_cast<uInt>(file_pointer_end - file_pointer);
strm.next_in = static_cast<Bytef*>(const_cast<u8*>(file_pointer));
strm.avail_out = static_cast<uInt>(m_program_data.size());
strm.next_out = static_cast<Bytef*>(m_program_data.data());
int err = inflateInit(&strm);
if (err != Z_OK)
{
Log_ErrorPrintf("inflateInit() failed: %d", err);
return false;
}
// we can do this in one pass because we preallocate the max size
err = inflate(&strm, Z_NO_FLUSH);
if (err != Z_STREAM_END)
{
Log_ErrorPrintf("inflate() failed: %d", err);
inflateEnd(&strm);
return false;
}
else if (strm.total_in != header.compressed_program_size)
{
Log_WarningPrintf("Mismatch between compressed size in header and stream %u/%u", header.compressed_program_size,
static_cast<u32>(strm.total_in));
}
m_program_data.resize(strm.total_out);
file_pointer += header.compressed_program_size;
inflateEnd(&strm);
u32 remaining_tag_data = static_cast<u32>(file_pointer_end - file_pointer);
static constexpr char tag_signature[] = {'[', 'T', 'A', 'G', ']'};
if (remaining_tag_data >= sizeof(tag_signature) &&
std::memcmp(file_pointer, tag_signature, sizeof(tag_signature)) == 0)
{
file_pointer += sizeof(tag_signature);
while (file_pointer < file_pointer_end)
{
// skip whitespace
while (file_pointer < file_pointer_end && *file_pointer <= 0x20)
file_pointer++;
std::string tag_key;
while (file_pointer < file_pointer_end && *file_pointer != '=')
tag_key += (static_cast<char>(*(file_pointer++)));
// skip =
if (file_pointer < file_pointer_end)
file_pointer++;
std::string tag_value;
while (file_pointer < file_pointer_end && *file_pointer != '\n')
tag_value += (static_cast<char>(*(file_pointer++)));
if (!tag_key.empty())
{
Log_InfoPrintf("PSF Tag: '%s' = '%s'", tag_key.c_str(), tag_value.c_str());
m_tags.emplace(std::move(tag_key), std::move(tag_value));
}
}
}
return true;
}
} // namespace PSFLoader

47
src/core/psf_loader.h Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include "types.h"
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
namespace PSFLoader {
#pragma pack(push, 1)
struct PSFHeader
{
u8 id[3];
u8 version;
u32 reserved_area_size;
u32 compressed_program_size;
u32 program_crc32;
};
#pragma pack(pop)
class File
{
public:
using TagMap = std::map<std::string, std::string>;
using ProgramData = std::vector<u8>;
ALWAYS_INLINE const ProgramData& GetProgramData() const { return m_program_data; }
ALWAYS_INLINE const TagMap& GetTagMap() const { return m_tags; }
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;
bool Load(const char* path);
private:
enum : u32
{
MAX_PROGRAM_SIZE = 2 * 1024 * 1024
};
ProgramData m_program_data;
TagMap m_tags;
};
} // namespace PSFLoader

View file

@ -17,6 +17,7 @@
#include "mdec.h"
#include "memory_card.h"
#include "pad.h"
#include "psf_loader.h"
#include "save_state_version.h"
#include "sio.h"
#include "spu.h"
@ -131,11 +132,14 @@ bool System::Boot(const SystemBootParameters& params)
// Load CD image up and detect region.
std::unique_ptr<CDImage> media;
bool exe_boot = false;
bool psf_boot = false;
if (!params.filename.empty())
{
exe_boot = GameList::IsExeFileName(params.filename.c_str());
if (exe_boot)
psf_boot = (!exe_boot && GameList::IsPsfFileName(params.filename.c_str()));
if (exe_boot || psf_boot)
{
// TODO: Pull region from PSF
if (m_region == ConsoleRegion::Auto)
{
Log_InfoPrintf("Defaulting to NTSC-U region for executable.");
@ -203,6 +207,11 @@ bool System::Boot(const SystemBootParameters& params)
m_host_interface->ReportFormattedError("Failed to load EXE file '%s'", params.filename.c_str());
return false;
}
else if (psf_boot && !LoadPSF(params.filename.c_str(), *bios_image))
{
m_host_interface->ReportFormattedError("Failed to load EXE file '%s'", params.filename.c_str());
return false;
}
// Notify change of disc.
UpdateRunningGame(params.filename.c_str(), media.get());
@ -604,6 +613,69 @@ bool System::LoadEXE(const char* filename, std::vector<u8>& bios_image)
return BIOS::PatchBIOSForEXE(bios_image, r_pc, r_gp, r_sp, r_fp);
}
bool System::LoadEXEFromBuffer(const void* buffer, u32 buffer_size, std::vector<u8>& bios_image)
{
const u8* buffer_ptr = static_cast<const u8*>(buffer);
const u8* buffer_end = static_cast<const u8*>(buffer) + buffer_size;
BIOS::PSEXEHeader header;
if (buffer_size < sizeof(header))
return false;
std::memcpy(&header, buffer_ptr, sizeof(header));
buffer_ptr += sizeof(header);
if (!BIOS::IsValidPSExeHeader(header, static_cast<u32>(buffer_end - buffer_ptr)))
return false;
if (header.memfill_size > 0)
{
const u32 words_to_write = header.memfill_size / 4;
u32 address = header.memfill_start & ~UINT32_C(3);
for (u32 i = 0; i < words_to_write; i++)
{
m_cpu->SafeWriteMemoryWord(address, 0);
address += sizeof(u32);
}
}
if (header.file_size >= 4)
{
std::vector<u32> data_words((header.file_size + 3) / 4);
if ((buffer_end - buffer_ptr) < header.file_size)
return false;
std::memcpy(data_words.data(), buffer_ptr, header.file_size);
const u32 num_words = header.file_size / 4;
u32 address = header.load_address;
for (u32 i = 0; i < num_words; i++)
{
m_cpu->SafeWriteMemoryWord(address, data_words[i]);
address += sizeof(u32);
}
}
// 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(bios_image, r_pc, r_gp, r_sp, r_fp);
}
bool System::LoadPSF(const char* filename, std::vector<u8>& bios_image)
{
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()), bios_image);
}
bool System::SetExpansionROM(const char* filename)
{
std::FILE* fp = std::fopen(filename, "rb");

View file

@ -120,6 +120,8 @@ public:
void ResetPerformanceCounters();
bool LoadEXE(const char* filename, std::vector<u8>& bios_image);
bool LoadEXEFromBuffer(const void* buffer, u32 buffer_size, std::vector<u8>& bios_image);
bool LoadPSF(const char* filename, std::vector<u8>& bios_image);
bool SetExpansionROM(const char* filename);
// Adds ticks to the global tick counter, simulating the CPU being stalled.