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 "rif_generator.h" |
| 5 | #include <fstream> |
| 6 | #include <regex> |
| 7 | #include <iomanip> |
| 8 | #include <sstream> |
| 9 | #include <cryptopp/md5.h> |
| 10 | #include "common/logging/log.h" |
| 11 | |
| 12 | RIFGenerator::RIFGenerator() = default; |
| 13 | RIFGenerator::~RIFGenerator() = default; |
| 14 | |
| 15 | bool RIFGenerator::ValidateContentID(const std::string& content_id) { |
| 16 | // Formato: REGION-CUSAXXXXX_XX-PRODUCTCODE |
| 17 | // Esempio: EP0001-CUSA12345_00-TESTGAMERETAIL01 |
| 18 | // Esempio: JP2551-CUSA16074_00-RASPBERRY000000 |
| 19 | std::regex pattern(R"(^[A-Z]{2}\d{4}-CUSA\d{5}_\d{2}-[A-Z0-9]{15,16}$)"); |
| 20 | return std::regex_match(content_id, pattern); |
| 21 | } |
| 22 | |
| 23 | u32 RIFGenerator::GenerateTimestamp(const std::string& content_id) { |
| 24 | // Genera un timestamp deterministico usando MD5 del Content ID |
| 25 | CryptoPP::MD5 md5; |
| 26 | u8 hash[CryptoPP::MD5::DIGESTSIZE]; |
| 27 | |
| 28 | md5.Update(reinterpret_cast<const u8*>(content_id.data()), content_id.size()); |
| 29 | md5.Final(hash); |
| 30 | |
| 31 | // Usa i primi 4 byte dell'hash MD5 come timestamp |
| 32 | u32 timestamp = 0; |
| 33 | timestamp |= static_cast<u32>(hash[0]) << 24; |
| 34 | timestamp |= static_cast<u32>(hash[1]) << 16; |
| 35 | timestamp |= static_cast<u32>(hash[2]) << 8; |
| 36 | timestamp |= static_cast<u32>(hash[3]); |
| 37 | |
| 38 | // Assicurati che sia nel range ragionevole (2013-2019) |
| 39 | // Range osservato: 0x522ec370 - 0x5d96edf0 |
| 40 | const u32 min_timestamp = 0x522ec370; // ~2013 |
| 41 | const u32 max_timestamp = 0x5d96edf0; // ~2019 |
| 42 | |
| 43 | // Normalizza il timestamp nel range |
| 44 | timestamp = min_timestamp + (timestamp % (max_timestamp - min_timestamp)); |
| 45 | |
| 46 | return timestamp; |
| 47 | } |
| 48 | |
| 49 | std::vector<u8> RIFGenerator::GenerateRIFContent(const std::string& content_id) { |
| 50 | std::vector<u8> rif_data(RIF_SIZE, 0); |
| 51 | |
| 52 | // Magic number "RIF\0" |
| 53 | std::copy(RIF_MAGIC.begin(), RIF_MAGIC.end(), rif_data.begin() + MAGIC_OFFSET); |
| 54 | |
| 55 | // Version |
| 56 | std::copy(RIF_VERSION.begin(), RIF_VERSION.end(), rif_data.begin() + VERSION_OFFSET); |
| 57 | |
| 58 | // Unknown field |
| 59 | std::copy(RIF_UNKNOWN.begin(), RIF_UNKNOWN.end(), rif_data.begin() + UNKNOWN_OFFSET); |
| 60 | |
| 61 | // Padding (già inizializzato a 0) |
| 62 | |
| 63 | // Timestamp (big-endian) |
| 64 | u32 timestamp = GenerateTimestamp(content_id); |
| 65 | rif_data[TIMESTAMP_OFFSET + 0] = (timestamp >> 24) & 0xFF; |
| 66 | rif_data[TIMESTAMP_OFFSET + 1] = (timestamp >> 16) & 0xFF; |
| 67 | rif_data[TIMESTAMP_OFFSET + 2] = (timestamp >> 8) & 0xFF; |
| 68 | rif_data[TIMESTAMP_OFFSET + 3] = timestamp & 0xFF; |
| 69 | |
| 70 | // Content ID |
| 71 | if (content_id.size() <= (RIF_SIZE - CONTENT_ID_OFFSET)) { |
| 72 | std::copy(content_id.begin(), content_id.end(), rif_data.begin() + CONTENT_ID_OFFSET); |
| 73 | } |
| 74 | |
| 75 | return rif_data; |
| 76 | } |
| 77 | |
| 78 | bool RIFGenerator::GenerateRIF(const std::string& content_id, const std::filesystem::path& output_path) { |
| 79 | if (!ValidateContentID(content_id)) { |
| 80 | LOG_ERROR(Lib_Kernel, "Invalid Content ID format: {}", content_id); |
| 81 | return false; |
| 82 | } |
| 83 | |
| 84 | try { |
| 85 | // Genera il contenuto del file RIF |
| 86 | auto rif_content = GenerateRIFContent(content_id); |
| 87 | |
| 88 | // Crea il nome del file RIF |
| 89 | std::filesystem::path rif_filename = content_id + ".rif"; |
| 90 | std::filesystem::path full_path = output_path / rif_filename; |
| 91 | |
| 92 | // Crea la directory se non esiste |
| 93 | std::filesystem::create_directories(output_path); |
| 94 | |
| 95 | // Scrivi il file RIF |
| 96 | std::ofstream file(full_path, std::ios::binary); |
| 97 | if (!file) { |
| 98 | LOG_ERROR(Lib_Kernel, "Failed to create RIF file: {}", full_path.string()); |
| 99 | return false; |
| 100 | } |
| 101 | |
| 102 | file.write(reinterpret_cast<const char*>(rif_content.data()), rif_content.size()); |
| 103 | file.close(); |
| 104 | |
| 105 | if (file.fail()) { |
| 106 | LOG_ERROR(Lib_Kernel, "Failed to write RIF file: {}", full_path.string()); |
| 107 | return false; |
| 108 | } |
| 109 | |
| 110 | LOG_INFO(Lib_Kernel, "Generated RIF file: {} (size: {} bytes, timestamp: 0x{:08x})", |
| 111 | full_path.string(), rif_content.size(), GenerateTimestamp(content_id)); |
| 112 | |
| 113 | return true; |
| 114 | } catch (const std::exception& e) { |
| 115 | LOG_ERROR(Lib_Kernel, "Exception while generating RIF: {}", e.what()); |
| 116 | return false; |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | bool RIFGenerator::ValidateRIF(const std::filesystem::path& rif_path) { |
| 121 | try { |
| 122 | std::ifstream file(rif_path, std::ios::binary); |
| 123 | if (!file) { |
| 124 | return false; |
| 125 | } |
| 126 | |
| 127 | // Verifica la dimensione del file |
| 128 | file.seekg(0, std::ios::end); |
| 129 | auto file_size = file.tellg(); |
| 130 | if (file_size != RIF_SIZE) { |
| 131 | return false; |
| 132 | } |
| 133 | |
| 134 | file.seekg(0, std::ios::beg); |
| 135 | |
| 136 | // Verifica il magic number |
| 137 | std::array<u8, 4> magic; |
| 138 | file.read(reinterpret_cast<char*>(magic.data()), magic.size()); |
| 139 | if (magic != RIF_MAGIC) { |
| 140 | return false; |
| 141 | } |
| 142 | |
| 143 | // Verifica la versione |
| 144 | std::array<u8, 2> version; |
| 145 | file.read(reinterpret_cast<char*>(version.data()), version.size()); |
| 146 | if (version != RIF_VERSION) { |
| 147 | return false; |
| 148 | } |
| 149 | |
| 150 | return true; |
| 151 | } catch (const std::exception&) { |
| 152 | return false; |
| 153 | } |
| 154 | } |