duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

psf_loader.cpp (7154B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "psf_loader.h"
      5 #include "bios.h"
      6 #include "bus.h"
      7 #include "system.h"
      8 
      9 #include "common/error.h"
     10 #include "common/file_system.h"
     11 #include "common/log.h"
     12 #include "common/path.h"
     13 
     14 #include "zlib.h"
     15 
     16 #include <cstring>
     17 
     18 Log_SetChannel(PSFLoader);
     19 
     20 namespace PSFLoader {
     21 static bool LoadLibraryPSF(const std::string& path, bool use_pc_sp, Error* error, u32 depth = 0);
     22 }
     23 
     24 std::optional<std::string> PSFLoader::File::GetTagString(const char* tag_name) const
     25 {
     26   auto it = m_tags.find(tag_name);
     27   if (it == m_tags.end())
     28     return std::nullopt;
     29 
     30   return it->second;
     31 }
     32 
     33 std::optional<int> PSFLoader::File::GetTagInt(const char* tag_name) const
     34 {
     35   auto it = m_tags.find(tag_name);
     36   if (it == m_tags.end())
     37     return std::nullopt;
     38 
     39   return std::atoi(it->second.c_str());
     40 }
     41 
     42 std::optional<float> PSFLoader::File::GetTagFloat(const char* tag_name) const
     43 {
     44   auto it = m_tags.find(tag_name);
     45   if (it == m_tags.end())
     46     return std::nullopt;
     47 
     48   return static_cast<float>(std::atof(it->second.c_str()));
     49 }
     50 
     51 std::string PSFLoader::File::GetTagString(const char* tag_name, const char* default_value) const
     52 {
     53   std::optional<std::string> value(GetTagString(tag_name));
     54   if (value.has_value())
     55     return value.value();
     56 
     57   return default_value;
     58 }
     59 
     60 int PSFLoader::File::GetTagInt(const char* tag_name, int default_value) const
     61 {
     62   return GetTagInt(tag_name).value_or(default_value);
     63 }
     64 
     65 float PSFLoader::File::GetTagFloat(const char* tag_name, float default_value) const
     66 {
     67   return GetTagFloat(tag_name).value_or(default_value);
     68 }
     69 
     70 bool PSFLoader::File::Load(const char* path, Error* error)
     71 {
     72   std::optional<DynamicHeapArray<u8>> file_data(FileSystem::ReadBinaryFile(path, error));
     73   if (!file_data.has_value() || file_data->empty())
     74     return false;
     75 
     76   const u8* file_pointer = file_data->data();
     77   const u8* file_pointer_end = file_data->data() + file_data->size();
     78   const u32 file_size = static_cast<u32>(file_data->size());
     79 
     80   PSFHeader header;
     81   std::memcpy(&header, file_pointer, sizeof(header));
     82   file_pointer += sizeof(header);
     83   if (header.id[0] != 'P' || header.id[1] != 'S' || header.id[2] != 'F' || header.version != 0x01 ||
     84       header.compressed_program_size == 0 ||
     85       (sizeof(header) + header.reserved_area_size + header.compressed_program_size) > file_size)
     86   {
     87     Error::SetStringView(error, "Invalid or incompatible PSF header.");
     88     return false;
     89   }
     90 
     91   file_pointer += header.reserved_area_size;
     92 
     93   m_program_data.resize(MAX_PROGRAM_SIZE);
     94 
     95   z_stream strm = {};
     96   strm.avail_in = static_cast<uInt>(file_pointer_end - file_pointer);
     97   strm.next_in = static_cast<Bytef*>(const_cast<u8*>(file_pointer));
     98   strm.avail_out = static_cast<uInt>(m_program_data.size());
     99   strm.next_out = static_cast<Bytef*>(m_program_data.data());
    100 
    101   int err = inflateInit(&strm);
    102   if (err != Z_OK)
    103   {
    104     Error::SetStringFmt(error, "inflateInit() failed: {}", err);
    105     return false;
    106   }
    107 
    108   // we can do this in one pass because we preallocate the max size
    109   err = inflate(&strm, Z_NO_FLUSH);
    110   if (err != Z_STREAM_END)
    111   {
    112     Error::SetStringFmt(error, "inflate() failed: {}", err);
    113     inflateEnd(&strm);
    114     return false;
    115   }
    116   else if (strm.total_in != header.compressed_program_size)
    117   {
    118     WARNING_LOG("Mismatch between compressed size in header and stream {}/{}", header.compressed_program_size,
    119                 static_cast<u32>(strm.total_in));
    120   }
    121 
    122   m_program_data.resize(strm.total_out);
    123   file_pointer += header.compressed_program_size;
    124   inflateEnd(&strm);
    125 
    126   u32 remaining_tag_data = static_cast<u32>(file_pointer_end - file_pointer);
    127   static constexpr char tag_signature[] = {'[', 'T', 'A', 'G', ']'};
    128   if (remaining_tag_data >= sizeof(tag_signature) &&
    129       std::memcmp(file_pointer, tag_signature, sizeof(tag_signature)) == 0)
    130   {
    131     file_pointer += sizeof(tag_signature);
    132 
    133     while (file_pointer < file_pointer_end)
    134     {
    135       // skip whitespace
    136       while (file_pointer < file_pointer_end && *file_pointer <= 0x20)
    137         file_pointer++;
    138 
    139       std::string tag_key;
    140       while (file_pointer < file_pointer_end && *file_pointer != '=')
    141         tag_key += (static_cast<char>(*(file_pointer++)));
    142 
    143       // skip =
    144       if (file_pointer < file_pointer_end)
    145         file_pointer++;
    146 
    147       std::string tag_value;
    148       while (file_pointer < file_pointer_end && *file_pointer != '\n')
    149         tag_value += (static_cast<char>(*(file_pointer++)));
    150 
    151       if (!tag_key.empty())
    152       {
    153         DEV_LOG("PSF Tag: '{}' = '{}'", tag_key, tag_value);
    154         m_tags.emplace(std::move(tag_key), std::move(tag_value));
    155       }
    156     }
    157   }
    158 
    159   // Region detection.
    160   m_region = BIOS::GetPSExeDiscRegion(*reinterpret_cast<const BIOS::PSEXEHeader*>(m_program_data.data()));
    161 
    162   // _refresh tag takes precedence.
    163   const int refresh_tag = GetTagInt("_region", 0);
    164   if (refresh_tag == 60)
    165     m_region = DiscRegion::NTSC_U;
    166   else if (refresh_tag == 50)
    167     m_region = DiscRegion::PAL;
    168 
    169   return true;
    170 }
    171 
    172 bool PSFLoader::LoadLibraryPSF(const std::string& path, bool use_pc_sp, Error* error, u32 depth)
    173 {
    174   // don't recurse past 10 levels just in case of broken files
    175   if (depth >= 10)
    176   {
    177     Error::SetStringFmt(error, "Recursion depth exceeded when loading PSF '{}'", Path::GetFileName(path));
    178     return false;
    179   }
    180 
    181   File file;
    182   if (!file.Load(path.c_str(), error))
    183   {
    184     Error::AddPrefixFmt(error, "Failed to load {} PSF '{}': ", (depth == 0) ? "main" : "parent",
    185                         Path::GetFileName(path));
    186     return false;
    187   }
    188 
    189   // load the main parent library - this has to be done first so the specified PSF takes precedence
    190   std::optional<std::string> lib_name(file.GetTagString("_lib"));
    191   if (lib_name.has_value())
    192   {
    193     const std::string lib_path = Path::BuildRelativePath(path, lib_name.value());
    194     INFO_LOG("Loading parent PSF '{}'", Path::GetFileName(lib_path));
    195 
    196     // We should use the initial SP/PC from the **first** parent lib.
    197     const bool lib_use_pc_sp = (depth == 0);
    198     if (!LoadLibraryPSF(lib_path.c_str(), lib_use_pc_sp, error, depth + 1))
    199       return false;
    200 
    201     // Don't apply the PC/SP from the minipsf file.
    202     if (lib_use_pc_sp)
    203       use_pc_sp = false;
    204   }
    205 
    206   // apply the main psf
    207   if (!Bus::InjectExecutable(file.GetProgramData(), use_pc_sp, error))
    208   {
    209     Error::AddPrefixFmt(error, "Failed to inject {} PSF '{}': ", (depth == 0) ? "main" : "parent",
    210                         Path::GetFileName(path));
    211     return false;
    212   }
    213 
    214   // load any other parent psfs
    215   u32 lib_counter = 2;
    216   for (;;)
    217   {
    218     lib_name = file.GetTagString(TinyString::from_format("_lib{}", lib_counter++));
    219     if (!lib_name.has_value())
    220       break;
    221 
    222     const std::string lib_path = Path::BuildRelativePath(path, lib_name.value());
    223     INFO_LOG("Loading parent PSF '{}'", Path::GetFileName(lib_path));
    224     if (!LoadLibraryPSF(lib_path.c_str(), false, error, depth + 1))
    225       return false;
    226   }
    227 
    228   return true;
    229 }
    230 
    231 bool PSFLoader::Load(const std::string& path, Error* error)
    232 {
    233   INFO_LOG("Loading PSF file from '{}'", path);
    234   return LoadLibraryPSF(path, true, error);
    235 }