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 }