A tool for deriving PKG packet encryption keys for ps4 written in c++
| 1 | // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | |
| 4 | #include <cstring> |
| 5 | |
| 6 | #include "common/assert.h" |
| 7 | #include "common/io_file.h" |
| 8 | #include "common/logging/log.h" |
| 9 | #include "core/file_format/psf.h" |
| 10 | #include <optional> |
| 11 | |
| 12 | #include "common/logging/log.h" |
| 13 | |
| 14 | static const std::unordered_map<std::string_view, u32> psf_known_max_sizes = { |
| 15 | {"ACCOUNT_ID", 8}, {"CATEGORY", 4}, {"DETAIL", 1024}, {"FORMAT", 4}, |
| 16 | {"MAINTITLE", 128}, {"PARAMS", 1024}, {"SAVEDATA_BLOCKS", 8}, {"SAVEDATA_DIRECTORY", 32}, |
| 17 | {"SUBTITLE", 128}, {"TITLE_ID", 12}, |
| 18 | }; |
| 19 | static inline u32 get_max_size(std::string_view key, u32 default_value) { |
| 20 | if (const auto& v = psf_known_max_sizes.find(key); v != psf_known_max_sizes.end()) { |
| 21 | return v->second; |
| 22 | } |
| 23 | return default_value; |
| 24 | } |
| 25 | |
| 26 | bool PSF::Open(const std::filesystem::path& filepath) { |
| 27 | using namespace std::chrono; |
| 28 | if (std::filesystem::exists(filepath)) { |
| 29 | const auto t = std::filesystem::last_write_time(filepath); |
| 30 | const auto rel = |
| 31 | duration_cast<seconds>(t - std::filesystem::file_time_type::clock::now()).count(); |
| 32 | const auto tp = system_clock::to_time_t(system_clock::now() + seconds{rel}); |
| 33 | last_write = system_clock::from_time_t(tp); |
| 34 | } |
| 35 | |
| 36 | Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Read); |
| 37 | if (!file.IsOpen()) { |
| 38 | return false; |
| 39 | } |
| 40 | |
| 41 | const u64 psfSize = file.GetSize(); |
| 42 | std::vector<u8> psf(psfSize); |
| 43 | file.Seek(0); |
| 44 | file.Read(psf); |
| 45 | file.Close(); |
| 46 | return Open(psf); |
| 47 | } |
| 48 | |
| 49 | bool PSF::Open(const std::vector<u8>& psf_buffer) { |
| 50 | const u8* psf_data = psf_buffer.data(); |
| 51 | |
| 52 | entry_list.clear(); |
| 53 | map_binaries.clear(); |
| 54 | map_strings.clear(); |
| 55 | map_integers.clear(); |
| 56 | |
| 57 | // Parse file contents |
| 58 | PSFHeader header{}; |
| 59 | std::memcpy(&header, psf_data, sizeof(header)); |
| 60 | |
| 61 | if (header.magic != PSF_MAGIC) { |
| 62 | LOG_ERROR(Core, "Invalid PSF magic number"); |
| 63 | return false; |
| 64 | } |
| 65 | if (header.version != PSF_VERSION_1_1 && header.version != PSF_VERSION_1_0) { |
| 66 | LOG_ERROR(Core, "Unsupported PSF version: 0x{:08x}", header.version); |
| 67 | return false; |
| 68 | } |
| 69 | |
| 70 | for (u32 i = 0; i < header.index_table_entries; i++) { |
| 71 | PSFRawEntry raw_entry{}; |
| 72 | std::memcpy(&raw_entry, psf_data + sizeof(PSFHeader) + i * sizeof(PSFRawEntry), |
| 73 | sizeof(raw_entry)); |
| 74 | |
| 75 | Entry& entry = entry_list.emplace_back(); |
| 76 | entry.key = std::string{(char*)(psf_data + header.key_table_offset + raw_entry.key_offset)}; |
| 77 | entry.param_fmt = static_cast<PSFEntryFmt>(raw_entry.param_fmt.Raw()); |
| 78 | entry.max_len = raw_entry.param_max_len; |
| 79 | |
| 80 | const u8* data = psf_data + header.data_table_offset + raw_entry.data_offset; |
| 81 | |
| 82 | switch (entry.param_fmt) { |
| 83 | case PSFEntryFmt::Binary: { |
| 84 | std::vector<u8> value(raw_entry.param_len); |
| 85 | std::memcpy(value.data(), data, raw_entry.param_len); |
| 86 | map_binaries.emplace(i, std::move(value)); |
| 87 | } break; |
| 88 | case PSFEntryFmt::Text: { |
| 89 | std::string c_str{reinterpret_cast<const char*>(data)}; |
| 90 | map_strings.emplace(i, std::move(c_str)); |
| 91 | } break; |
| 92 | case PSFEntryFmt::Integer: { |
| 93 | ASSERT_MSG(raw_entry.param_len == sizeof(s32), "PSF integer entry size mismatch"); |
| 94 | s32 integer = *(s32*)data; |
| 95 | map_integers.emplace(i, integer); |
| 96 | } break; |
| 97 | default: |
| 98 | UNREACHABLE_MSG("Unknown PSF entry format"); |
| 99 | } |
| 100 | } |
| 101 | return true; |
| 102 | } |
| 103 | |
| 104 | bool PSF::Encode(const std::filesystem::path& filepath) const { |
| 105 | Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write); |
| 106 | if (!file.IsOpen()) { |
| 107 | return false; |
| 108 | } |
| 109 | |
| 110 | last_write = std::chrono::system_clock::now(); |
| 111 | |
| 112 | const auto psf_buffer = Encode(); |
| 113 | const size_t written = file.Write(psf_buffer); |
| 114 | if (written != psf_buffer.size()) { |
| 115 | LOG_ERROR(Core, "Failed to write PSF file. Written {} Expected {}", written, |
| 116 | psf_buffer.size()); |
| 117 | } |
| 118 | return written == psf_buffer.size(); |
| 119 | } |
| 120 | |
| 121 | std::vector<u8> PSF::Encode() const { |
| 122 | std::vector<u8> psf_buffer; |
| 123 | Encode(psf_buffer); |
| 124 | return psf_buffer; |
| 125 | } |
| 126 | |
| 127 | void PSF::Encode(std::vector<u8>& psf_buffer) const { |
| 128 | psf_buffer.resize(sizeof(PSFHeader) + sizeof(PSFRawEntry) * entry_list.size()); |
| 129 | |
| 130 | { |
| 131 | auto& header = *(PSFHeader*)psf_buffer.data(); |
| 132 | header.magic = PSF_MAGIC; |
| 133 | header.version = PSF_VERSION_1_1; |
| 134 | header.index_table_entries = entry_list.size(); |
| 135 | } |
| 136 | |
| 137 | const size_t key_table_offset = psf_buffer.size(); |
| 138 | ((PSFHeader*)psf_buffer.data())->key_table_offset = key_table_offset; |
| 139 | for (size_t i = 0; i < entry_list.size(); i++) { |
| 140 | auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i]; |
| 141 | const Entry& entry = entry_list[i]; |
| 142 | raw_entry.key_offset = psf_buffer.size() - key_table_offset; |
| 143 | raw_entry.param_fmt.FromRaw(static_cast<u16>(entry.param_fmt)); |
| 144 | raw_entry.param_max_len = entry.max_len; |
| 145 | std::ranges::copy(entry.key, std::back_inserter(psf_buffer)); |
| 146 | psf_buffer.push_back(0); // NULL terminator |
| 147 | } |
| 148 | |
| 149 | const size_t data_table_offset = psf_buffer.size(); |
| 150 | ((PSFHeader*)psf_buffer.data())->data_table_offset = data_table_offset; |
| 151 | for (size_t i = 0; i < entry_list.size(); i++) { |
| 152 | if (psf_buffer.size() % 4 != 0) { |
| 153 | std::ranges::fill_n(std::back_inserter(psf_buffer), 4 - psf_buffer.size() % 4, 0); |
| 154 | } |
| 155 | auto& raw_entry = ((PSFRawEntry*)(psf_buffer.data() + sizeof(PSFHeader)))[i]; |
| 156 | const Entry& entry = entry_list[i]; |
| 157 | raw_entry.data_offset = psf_buffer.size() - data_table_offset; |
| 158 | |
| 159 | s32 additional_padding = s32(raw_entry.param_max_len); |
| 160 | |
| 161 | switch (entry.param_fmt) { |
| 162 | case PSFEntryFmt::Binary: { |
| 163 | const auto& value = map_binaries.at(i); |
| 164 | raw_entry.param_len = value.size(); |
| 165 | additional_padding -= s32(raw_entry.param_len); |
| 166 | std::ranges::copy(value, std::back_inserter(psf_buffer)); |
| 167 | } break; |
| 168 | case PSFEntryFmt::Text: { |
| 169 | const auto& value = map_strings.at(i); |
| 170 | raw_entry.param_len = value.size() + 1; |
| 171 | additional_padding -= s32(raw_entry.param_len); |
| 172 | std::ranges::copy(value, std::back_inserter(psf_buffer)); |
| 173 | psf_buffer.push_back(0); // NULL terminator |
| 174 | } break; |
| 175 | case PSFEntryFmt::Integer: { |
| 176 | const auto& value = map_integers.at(i); |
| 177 | raw_entry.param_len = sizeof(s32); |
| 178 | additional_padding -= s32(raw_entry.param_len); |
| 179 | const auto value_bytes = reinterpret_cast<const u8*>(&value); |
| 180 | std::ranges::copy(value_bytes, value_bytes + sizeof(s32), |
| 181 | std::back_inserter(psf_buffer)); |
| 182 | } break; |
| 183 | default: |
| 184 | UNREACHABLE_MSG("Unknown PSF entry format"); |
| 185 | } |
| 186 | ASSERT_MSG(additional_padding >= 0, "PSF entry max size mismatch"); |
| 187 | std::ranges::fill_n(std::back_inserter(psf_buffer), additional_padding, 0); |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | std::optional<std::span<const u8>> PSF::GetBinary(std::string_view key) const { |
| 192 | const auto& [it, index] = FindEntry(key); |
| 193 | if (it == entry_list.end()) { |
| 194 | return {}; |
| 195 | } |
| 196 | ASSERT(it->param_fmt == PSFEntryFmt::Binary); |
| 197 | return std::span{map_binaries.at(index)}; |
| 198 | } |
| 199 | |
| 200 | std::optional<std::string_view> PSF::GetString(std::string_view key) const { |
| 201 | const auto& [it, index] = FindEntry(key); |
| 202 | if (it == entry_list.end()) { |
| 203 | return {}; |
| 204 | } |
| 205 | ASSERT(it->param_fmt == PSFEntryFmt::Text); |
| 206 | return std::string_view{map_strings.at(index)}; |
| 207 | } |
| 208 | |
| 209 | std::optional<s32> PSF::GetInteger(std::string_view key) const { |
| 210 | const auto& [it, index] = FindEntry(key); |
| 211 | if (it == entry_list.end()) { |
| 212 | return {}; |
| 213 | } |
| 214 | ASSERT(it->param_fmt == PSFEntryFmt::Integer); |
| 215 | return map_integers.at(index); |
| 216 | } |
| 217 | |
| 218 | void PSF::AddBinary(std::string key, std::vector<u8> value, bool update) { |
| 219 | auto [it, index] = FindEntry(key); |
| 220 | bool exist = it != entry_list.end(); |
| 221 | if (exist && !update) { |
| 222 | LOG_ERROR(Core, "PSF: Tried to add binary key that already exists: {}", key); |
| 223 | return; |
| 224 | } |
| 225 | if (exist) { |
| 226 | ASSERT_MSG(it->param_fmt == PSFEntryFmt::Binary, "PSF: Change format is not supported"); |
| 227 | it->max_len = get_max_size(key, value.size()); |
| 228 | map_binaries.at(index) = std::move(value); |
| 229 | return; |
| 230 | } |
| 231 | Entry& entry = entry_list.emplace_back(); |
| 232 | entry.max_len = get_max_size(key, value.size()); |
| 233 | entry.key = std::move(key); |
| 234 | entry.param_fmt = PSFEntryFmt::Binary; |
| 235 | map_binaries.emplace(entry_list.size() - 1, std::move(value)); |
| 236 | } |
| 237 | |
| 238 | void PSF::AddBinary(std::string key, uint64_t value, bool update) { |
| 239 | std::vector<u8> data(8); |
| 240 | std::memcpy(data.data(), &value, 8); |
| 241 | return AddBinary(std::move(key), std::move(data), update); |
| 242 | } |
| 243 | |
| 244 | void PSF::AddString(std::string key, std::string value, bool update) { |
| 245 | auto [it, index] = FindEntry(key); |
| 246 | bool exist = it != entry_list.end(); |
| 247 | if (exist && !update) { |
| 248 | LOG_ERROR(Core, "PSF: Tried to add string key that already exists: {}", key); |
| 249 | return; |
| 250 | } |
| 251 | if (exist) { |
| 252 | ASSERT_MSG(it->param_fmt == PSFEntryFmt::Text, "PSF: Change format is not supported"); |
| 253 | it->max_len = get_max_size(key, value.size() + 1); |
| 254 | map_strings.at(index) = std::move(value); |
| 255 | return; |
| 256 | } |
| 257 | Entry& entry = entry_list.emplace_back(); |
| 258 | entry.max_len = get_max_size(key, value.size() + 1); |
| 259 | entry.key = std::move(key); |
| 260 | entry.param_fmt = PSFEntryFmt::Text; |
| 261 | map_strings.emplace(entry_list.size() - 1, std::move(value)); |
| 262 | } |
| 263 | |
| 264 | void PSF::AddInteger(std::string key, s32 value, bool update) { |
| 265 | auto [it, index] = FindEntry(key); |
| 266 | bool exist = it != entry_list.end(); |
| 267 | if (exist && !update) { |
| 268 | LOG_ERROR(Core, "PSF: Tried to add integer key that already exists: {}", key); |
| 269 | return; |
| 270 | } |
| 271 | if (exist) { |
| 272 | ASSERT_MSG(it->param_fmt == PSFEntryFmt::Integer, "PSF: Change format is not supported"); |
| 273 | it->max_len = sizeof(s32); |
| 274 | map_integers.at(index) = value; |
| 275 | return; |
| 276 | } |
| 277 | Entry& entry = entry_list.emplace_back(); |
| 278 | entry.key = std::move(key); |
| 279 | entry.param_fmt = PSFEntryFmt::Integer; |
| 280 | entry.max_len = sizeof(s32); |
| 281 | map_integers.emplace(entry_list.size() - 1, value); |
| 282 | } |
| 283 | |
| 284 | std::pair<std::vector<PSF::Entry>::iterator, size_t> PSF::FindEntry(std::string_view key) { |
| 285 | auto entry = |
| 286 | std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; }); |
| 287 | return {entry, std::distance(entry_list.begin(), entry)}; |
| 288 | } |
| 289 | |
| 290 | std::pair<std::vector<PSF::Entry>::const_iterator, size_t> PSF::FindEntry( |
| 291 | std::string_view key) const { |
| 292 | auto entry = |
| 293 | std::ranges::find_if(entry_list, [&](const auto& entry) { return entry.key == key; }); |
| 294 | return {entry, std::distance(entry_list.begin(), entry)}; |
| 295 | } |
| 296 |