extern bool verify_npdrm_self_headers(const fs::file& self, u8* klic_key) { if (!self) return false; self.seek(0); if (self.size() >= 4 && self.read<u32>() == "SCE\0"_u32) { // Check the ELF file class (32 or 64 bit). bool isElf32 = IsSelfElf32(self); // Start the decrypter on this SELF file. SELFDecrypter self_dec(self); // Load the SELF file headers. if (!self_dec.LoadHeaders(isElf32)) { LOG_ERROR(LOADER, "SELF: Failed to load SELF file headers!"); return false; } // Load and decrypt the SELF file metadata. if (!self_dec.LoadMetadata(klic_key)) { LOG_ERROR(LOADER, "SELF: Failed to load SELF file metadata!"); return false; } } return true; }
extern fs::file decrypt_self(fs::file elf_or_self, u8* klic_key) { if (!elf_or_self) { return fs::file{}; } elf_or_self.seek(0); // Check SELF header first. Check for a debug SELF. if (elf_or_self.size() >= 4 && elf_or_self.read<u32>() == "SCE\0"_u32 && !CheckDebugSelf(elf_or_self)) { // Check the ELF file class (32 or 64 bit). bool isElf32 = IsSelfElf32(elf_or_self); // Start the decrypter on this SELF file. SELFDecrypter self_dec(elf_or_self); // Load the SELF file headers. if (!self_dec.LoadHeaders(isElf32)) { LOG_ERROR(LOADER, "SELF: Failed to load SELF file headers!"); return fs::file{}; } // Load and decrypt the SELF file metadata. if (!self_dec.LoadMetadata(klic_key)) { LOG_ERROR(LOADER, "SELF: Failed to load SELF file metadata!"); return fs::file{}; } // Decrypt the SELF file data. if (!self_dec.DecryptData()) { LOG_ERROR(LOADER, "SELF: Failed to decrypt SELF file data!"); return fs::file{}; } // Make a new ELF file from this SELF. return self_dec.MakeElf(isElf32); } return elf_or_self; }
static bool CheckDebugSelf(fs::file& s) { if (s.size() < 0x18) { return false; } // Get the key version. s.seek(0x08); const u16 key_version = s.read<le_t<u16>>(); // Check for DEBUG version. if (key_version == 0x80 || key_version == 0xc0) { LOG_WARNING(LOADER, "Debug SELF detected! Removing fake header..."); // Get the real elf offset. s.seek(0x10); // Start at the real elf offset. s.seek(key_version == 0x80 ? +s.read<be_t<u64>>() : +s.read<le_t<u64>>()); // Write the real ELF file back. fs::file e = fs::make_stream<std::vector<u8>>(); // Copy the data. char buf[2048]; while (u64 size = s.read(buf, 2048)) { e.write(buf, size); } s = std::move(e); return true; } // Leave the file untouched. return false; }
bool pkg_install(const fs::file& pkg_f, const std::string& dir, atomic_t<double>& sync, const std::string& pkg_filepath) { const std::size_t BUF_SIZE = 8192 * 1024; // 8 MB std::vector<fs::file> filelist; u32 cur_file=0; fs::file original_file(pkg_filepath); original_file.seek(pkg_f.pos()); filelist.push_back(std::move(original_file)); // Save current file offset (probably zero) const u64 start_offset = pkg_f.pos(); u64 cur_offset = start_offset; u64 cur_file_offset = start_offset; // Get basic PKG information PKGHeader header; auto archive_seek = [&](const s64 new_offset, const fs::seek_mode damode = fs::seek_set) { if(damode == fs::seek_set) cur_offset = new_offset; else if (damode == fs::seek_cur) cur_offset += new_offset; u64 _offset = 0; for (u32 i = 0; i < filelist.size(); i++) { if (cur_offset < (_offset + filelist[i].size())) { cur_file = i; cur_file_offset = cur_offset - _offset; filelist[i].seek(cur_file_offset); break; } _offset += filelist[i].size(); } }; auto archive_read = [&](const void *data_ptr, const u64 num_bytes) { u64 num_bytes_left = filelist[cur_file].size() - cur_file_offset; //check if it continues in another file if (num_bytes > num_bytes_left) { filelist[cur_file].read((u8 *)data_ptr, num_bytes_left); if ((cur_file + 1) < filelist.size()) cur_file++; else { cur_offset += num_bytes_left; cur_file_offset = filelist[cur_file].size(); return num_bytes_left; } u64 num_read = filelist[cur_file].read((u8 *)data_ptr + num_bytes_left, num_bytes - num_bytes_left); cur_offset += (num_read + num_bytes_left); cur_file_offset = num_read; return (num_read+num_bytes_left); } u64 num_read = filelist[cur_file].read((u8 *)data_ptr, num_bytes); cur_offset += num_read; cur_file_offset += num_read; return num_read; }; if (archive_read(&header, sizeof(header)) != sizeof(header)) { LOG_ERROR(LOADER, "PKG file is too short!"); return false; } if (header.pkg_magic != "\x7FPKG"_u32) { LOG_ERROR(LOADER, "Not a PKG file!"); return false; } switch (const u16 type = header.pkg_type) { case PKG_RELEASE_TYPE_DEBUG: break; case PKG_RELEASE_TYPE_RELEASE: break; default: { LOG_ERROR(LOADER, "Unknown PKG type (0x%x)", type); return false; } } switch (const u16 platform = header.pkg_platform) { case PKG_PLATFORM_TYPE_PS3: break; case PKG_PLATFORM_TYPE_PSP: break; default: { LOG_ERROR(LOADER, "Unknown PKG platform (0x%x)", platform); return false; } } if (header.pkg_size > pkg_f.size()) { //Check if multi-files pkg if (pkg_filepath.length() < 7 || pkg_filepath.substr(pkg_filepath.length() - 7).compare("_00.pkg") != 0) { LOG_ERROR(LOADER, "PKG file size mismatch (pkg_size=0x%llx)", header.pkg_size); return false; } std::string name_wo_number = pkg_filepath.substr(0, pkg_filepath.length() - 7); u64 cursize = pkg_f.size(); while (cursize != header.pkg_size) { std::string archive_filename = fmt::format("%s_%2d.pkg", name_wo_number, filelist.size()); fs::file archive_file(archive_filename); if (!archive_file) { LOG_ERROR(LOADER, "Missing part of the multi-files pkg: %s", archive_filename); return false; } cursize += archive_file.size(); filelist.push_back(std::move(archive_file)); } } if (header.data_size + header.data_offset > header.pkg_size) { LOG_ERROR(LOADER, "PKG data size mismatch (data_size=0x%llx, data_offset=0x%llx, file_size=0x%llx)", header.data_size, header.data_offset, header.pkg_size); return false; } be_t<u32> drm_type{0}; be_t<u32> content_type{0}; archive_seek(header.pkg_info_off); for (u32 i = 0; i < header.pkg_info_num; i++) { struct packet_T { be_t<u32> id; be_t<u32> size; } packet; archive_read(&packet, sizeof(packet)); // TODO switch (+packet.id) { case 0x1: { if (packet.size == sizeof(drm_type)) { archive_read(&drm_type, sizeof(drm_type)); continue; } break; } case 0x2: { if (packet.size == sizeof(content_type)) { archive_read(&content_type, sizeof(content_type)); continue; } break; } } archive_seek(packet.size, fs::seek_cur); } // Allocate buffer with BUF_SIZE size or more if required const std::unique_ptr<u128[]> buf(new u128[std::max<u64>(BUF_SIZE, sizeof(PKGEntry) * header.file_count) / sizeof(u128)]); // Define decryption subfunction (`psp` arg selects the key for specific block) auto decrypt = [&](u64 offset, u64 size, const uchar* key) -> u64 { archive_seek(start_offset + header.data_offset + offset); // Read the data and set available size const u64 read = archive_read(buf.get(), size); // Get block count const u64 blocks = (read + 15) / 16; if (header.pkg_type == PKG_RELEASE_TYPE_DEBUG) { // Debug key be_t<u64> input[8] = { header.qa_digest[0], header.qa_digest[0], header.qa_digest[1], header.qa_digest[1], }; for (u64 i = 0; i < blocks; i++) { // Initialize stream cipher for current position input[7] = offset / 16 + i; union sha1_hash { u8 data[20]; u128 _v128; } hash; sha1(reinterpret_cast<const u8*>(input), sizeof(input), hash.data); buf[i] ^= hash._v128; } } if (header.pkg_type == PKG_RELEASE_TYPE_RELEASE) { aes_context ctx; // Set encryption key for stream cipher aes_setkey_enc(&ctx, key, 128); // Initialize stream cipher for start position be_t<u128> input = header.klicensee.value() + offset / 16; // Increment stream position for every block for (u64 i = 0; i < blocks; i++, input++) { u128 key; aes_crypt_ecb(&ctx, AES_ENCRYPT, reinterpret_cast<const u8*>(&input), reinterpret_cast<u8*>(&key)); buf[i] ^= key; } } // Return the amount of data written in buf return read; }; std::array<uchar, 16> dec_key; if (header.pkg_platform == PKG_PLATFORM_TYPE_PSP && content_type >= 0x15 && content_type <= 0x17) { const uchar psp2t1[] = {0xE3, 0x1A, 0x70, 0xC9, 0xCE, 0x1D, 0xD7, 0x2B, 0xF3, 0xC0, 0x62, 0x29, 0x63, 0xF2, 0xEC, 0xCB}; const uchar psp2t2[] = {0x42, 0x3A, 0xCA, 0x3A, 0x2B, 0xD5, 0x64, 0x9F, 0x96, 0x86, 0xAB, 0xAD, 0x6F, 0xD8, 0x80, 0x1F}; const uchar psp2t3[] = {0xAF, 0x07, 0xFD, 0x59, 0x65, 0x25, 0x27, 0xBA, 0xF1, 0x33, 0x89, 0x66, 0x8B, 0x17, 0xD9, 0xEA}; aes_context ctx; aes_setkey_enc(&ctx, content_type == 0x15 ? psp2t1 : content_type == 0x16 ? psp2t2 : psp2t3, 128); aes_crypt_ecb(&ctx, AES_ENCRYPT, reinterpret_cast<const uchar*>(&header.klicensee), dec_key.data()); decrypt(0, header.file_count * sizeof(PKGEntry), dec_key.data()); } else { std::memcpy(dec_key.data(), PKG_AES_KEY, dec_key.size()); decrypt(0, header.file_count * sizeof(PKGEntry), header.pkg_platform == PKG_PLATFORM_TYPE_PSP ? PKG_AES_KEY2 : dec_key.data()); } std::vector<PKGEntry> entries(header.file_count); std::memcpy(entries.data(), buf.get(), entries.size() * sizeof(PKGEntry)); for (const auto& entry : entries) { const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0; if (entry.name_size > 256) { LOG_ERROR(LOADER, "PKG name size is too big (0x%x)", entry.name_size); continue; } decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : dec_key.data()); const std::string name(reinterpret_cast<char*>(buf.get()), entry.name_size); LOG_NOTICE(LOADER, "Entry 0x%08x: %s", entry.type, name); switch (entry.type & 0xff) { case PKG_FILE_ENTRY_NPDRM: case PKG_FILE_ENTRY_NPDRMEDAT: case PKG_FILE_ENTRY_SDAT: case PKG_FILE_ENTRY_REGULAR: case PKG_FILE_ENTRY_UNK1: case 0xe: case 0x10: case 0x11: case 0x13: case 0x15: case 0x16: { const std::string path = dir + name; const bool did_overwrite = fs::is_file(path); if (did_overwrite && (entry.type&PKG_FILE_ENTRY_OVERWRITE) == 0) { LOG_NOTICE(LOADER, "Didn't overwrite %s", name); break; } if (fs::file out{ path, fs::rewrite }) { for (u64 pos = 0; pos < entry.file_size; pos += BUF_SIZE) { const u64 block_size = std::min<u64>(BUF_SIZE, entry.file_size - pos); if (decrypt(entry.file_offset + pos, block_size, is_psp ? PKG_AES_KEY2 : dec_key.data()) != block_size) { LOG_ERROR(LOADER, "Failed to extract file %s", path); break; } if (out.write(buf.get(), block_size) != block_size) { LOG_ERROR(LOADER, "Failed to write file %s", path); break; } if (sync.fetch_add((block_size + 0.0) / header.data_size) < 0.) { LOG_ERROR(LOADER, "Package installation cancelled: %s", dir); return false; } } if (did_overwrite) { LOG_WARNING(LOADER, "Overwritten file %s", name); } else { LOG_NOTICE(LOADER, "Created file %s", name); } } else { LOG_ERROR(LOADER, "Failed to create file %s", path); } break; } case PKG_FILE_ENTRY_FOLDER: case 0x12: { const std::string path = dir + name; if (fs::create_dir(path)) { LOG_NOTICE(LOADER, "Created directory %s", name); } else if (fs::is_dir(path)) { LOG_WARNING(LOADER, "Reused existing directory %s", name); } else { LOG_ERROR(LOADER, "Failed to create directory %s", path); } break; } default: { LOG_ERROR(LOADER, "Unknown PKG entry type (0x%x) %s", entry.type, name); } } } LOG_SUCCESS(LOADER, "Package successfully installed to %s", dir); return true; }
registry load_object(const fs::file& stream) { registry result; // Hack for empty input (TODO) if (!stream) { return result; } // Get header header_t header; verify(HERE), stream.read(header); // Check magic and version verify(HERE), header.magic == "\0PSF"_u32, header.version == 0x101, sizeof(header_t) + header.entries_num * sizeof(def_table_t) <= header.off_key_table, header.off_key_table <= header.off_data_table, header.off_data_table <= stream.size(); // Get indices std::vector<def_table_t> indices; verify(HERE), stream.read(indices, header.entries_num); // Get keys std::string keys; verify(HERE), stream.seek(header.off_key_table) == header.off_key_table; verify(HERE), stream.read(keys, header.off_data_table - header.off_key_table); // Load entries for (u32 i = 0; i < header.entries_num; ++i) { verify(HERE), indices[i].key_off < header.off_data_table - header.off_key_table; // Get key name (null-terminated string) std::string key(keys.data() + indices[i].key_off); // Check entry verify(HERE), result.count(key) == 0, indices[i].param_len <= indices[i].param_max, indices[i].data_off < stream.size() - header.off_data_table, indices[i].param_max < stream.size() - indices[i].data_off; // Seek data pointer stream.seek(header.off_data_table + indices[i].data_off); if (indices[i].param_fmt == format::integer && indices[i].param_max == sizeof(u32) && indices[i].param_len == sizeof(u32)) { // Integer data le_t<u32> value; verify(HERE), stream.read(value); result.emplace(std::piecewise_construct, std::forward_as_tuple(std::move(key)), std::forward_as_tuple(value)); } else if (indices[i].param_fmt == format::string || indices[i].param_fmt == format::array) { // String/array data std::string value; verify(HERE), stream.read(value, indices[i].param_len); if (indices[i].param_fmt == format::string) { // Find null terminator value.resize(std::strlen(value.c_str())); } result.emplace(std::piecewise_construct, std::forward_as_tuple(std::move(key)), std::forward_as_tuple(indices[i].param_fmt, indices[i].param_max, std::move(value))); } else { // Possibly unsupported format, entry ignored LOG_ERROR(LOADER, "Unknown entry format (key='%s', fmt=0x%x, len=0x%x, max=0x%x)", key, indices[i].param_fmt, indices[i].param_len, indices[i].param_max); } } return result; }