Files
ppsspp/Core/ELF/PrxDecrypter.cpp
T
Henrik RydgårdandClaude Opus 5.5 fb371af6bb PSAR: Unpack the 1.00 updater, and the pre-decrypted 3.60 archive
The 1.00 updater's DATA.PSAR is a version 1 archive, the only one there is,
and its blocks carry tag 0x06000000, which the PRX decrypter had no key for.
1.50's mesg_led.prx keeps its keys raw in a table after their tags, the
0x0E000000 one we already use among them, and the 0x06000000 one there
decrypts all of 1.00 (with KIRK code 0x49, following the other tags). Its
first record has no version text either, so the version now comes from the
firmware's own version.txt when that's missing.

Fix a zlib usage bug.

Headless --unpack-updater also exited 0 when every file failed, so now it
fails if any did.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 19:00:48 +01:00

1630 lines
72 KiB
C++

#include <algorithm>
#include <array>
#include <string.h>
#include "ext/libkirk/kirk_engine.h"
#include "ext/libkirk/amctrl.h"
#include "ext/libkirk/SHA1.h"
#include "Common/Common.h"
#include "Common/Log.h"
#include "Common/Swap.h"
#include "Core/ELF/PrxDecrypter.h"
#define ROUNDUP16(x) (((x)+15)&~15)
// PSP_Header::decrypt_mode, the byte at 0x7C. Only the one we act on is named; the rest select
// which decryption variant a real PSP would use, which we don't need since we try them in turn.
enum {
PRX_DECRYPT_MODE_SPRX = 23,
};
// A module that arrived inside an NPDRM EDAT carries this fixed XOR on top of its tag's key. It
// goes with the decrypt_mode above rather than with any particular tag - tag 0x407810F0's table
// entry has no seed of its own, in JPCSP's tables as well as ours, so keying this on the tag
// would be wrong for a 0x407810F0 module that arrived some other way.
static const u8 xor_91E0A9AD[16] = {
0x84, 0x7B, 0xF5, 0xFE, 0xE8, 0x4D, 0xAD, 0x7A, 0xB5, 0x06, 0x28, 0x0E, 0x09, 0xFA, 0x81, 0xE1,
};
// Thank you PSARDUMPER & JPCSP keys
// PRXDecrypter 16-byte tag keys.
static const u8 keys260_0[] = {0xC3, 0x24, 0x89, 0xD3, 0x80, 0x87, 0xB2, 0x4E, 0x4C, 0xD7, 0x49, 0xE4, 0x9D, 0x1D, 0x34, 0xD1};
static const u8 keys260_1[] = {0xF3, 0xAC, 0x6E, 0x7C, 0x04, 0x0A, 0x23, 0xE7, 0x0D, 0x33, 0xD8, 0x24, 0x73, 0x39, 0x2B, 0x4A};
static const u8 keys260_2[] = {0x72, 0xB4, 0x39, 0xFF, 0x34, 0x9B, 0xAE, 0x82, 0x30, 0x34, 0x4A, 0x1D, 0xA2, 0xD8, 0xB4, 0x3C};
static const u8 keys280_0[] = {0xCA, 0xFB, 0xBF, 0xC7, 0x50, 0xEA, 0xB4, 0x40, 0x8E, 0x44, 0x5C, 0x63, 0x53, 0xCE, 0x80, 0xB1};
static const u8 keys280_1[] = {0x40, 0x9B, 0xC6, 0x9B, 0xA9, 0xFB, 0x84, 0x7F, 0x72, 0x21, 0xD2, 0x36, 0x96, 0x55, 0x09, 0x74};
static const u8 keys280_2[] = {0x03, 0xA7, 0xCC, 0x4A, 0x5B, 0x91, 0xC2, 0x07, 0xFF, 0xFC, 0x26, 0x25, 0x1E, 0x42, 0x4B, 0xB5};
static const u8 keys300_0[] = {0x9F, 0x67, 0x1A, 0x7A, 0x22, 0xF3, 0x59, 0x0B, 0xAA, 0x6D, 0xA4, 0xC6, 0x8B, 0xD0, 0x03, 0x77};
static const u8 keys300_1[] = {0x15, 0x07, 0x63, 0x26, 0xDB, 0xE2, 0x69, 0x34, 0x56, 0x08, 0x2A, 0x93, 0x4E, 0x4B, 0x8A, 0xB2};
static const u8 keys300_2[] = {0x56, 0x3B, 0x69, 0xF7, 0x29, 0x88, 0x2F, 0x4C, 0xDB, 0xD5, 0xDE, 0x80, 0xC6, 0x5C, 0xC8, 0x73};
static const u8 keys303_0[] = {0x7b, 0xa1, 0xe2, 0x5a, 0x91, 0xb9, 0xd3, 0x13, 0x77, 0x65, 0x4a, 0xb7, 0xc2, 0x8a, 0x10, 0xaf};
static const u8 keys310_0[] = {0xa2, 0x41, 0xe8, 0x39, 0x66, 0x5b, 0xfa, 0xbb, 0x1b, 0x2d, 0x6e, 0x0e, 0x33, 0xe5, 0xd7, 0x3f};
static const u8 keys310_1[] = {0xA4, 0x60, 0x8F, 0xAB, 0xAB, 0xDE, 0xA5, 0x65, 0x5D, 0x43, 0x3A, 0xD1, 0x5E, 0xC3, 0xFF, 0xEA};
static const u8 keys310_2[] = {0xE7, 0x5C, 0x85, 0x7A, 0x59, 0xB4, 0xE3, 0x1D, 0xD0, 0x9E, 0xCE, 0xC2, 0xD6, 0xD4, 0xBD, 0x2B};
static const u8 keys310_3[] = {0x2E, 0x00, 0xF6, 0xF7, 0x52, 0xCF, 0x95, 0x5A, 0xA1, 0x26, 0xB4, 0x84, 0x9B, 0x58, 0x76, 0x2F};
static const u8 keys330_0[] = {0x3B, 0x9B, 0x1A, 0x56, 0x21, 0x80, 0x14, 0xED, 0x8E, 0x8B, 0x08, 0x42, 0xFA, 0x2C, 0xDC, 0x3A};
static const u8 keys330_1[] = {0xE8, 0xBE, 0x2F, 0x06, 0xB1, 0x05, 0x2A, 0xB9, 0x18, 0x18, 0x03, 0xE3, 0xEB, 0x64, 0x7D, 0x26};
static const u8 keys330_2[] = {0xAB, 0x82, 0x25, 0xD7, 0x43, 0x6F, 0x6C, 0xC1, 0x95, 0xC5, 0xF7, 0xF0, 0x63, 0x73, 0x3F, 0xE7};
static const u8 keys330_3[] = {0xA8, 0xB1, 0x47, 0x77, 0xDC, 0x49, 0x6A, 0x6F, 0x38, 0x4C, 0x4D, 0x96, 0xBD, 0x49, 0xEC, 0x9B};
static const u8 keys330_4[] = {0xEC, 0x3B, 0xD2, 0xC0, 0xFA, 0xC1, 0xEE, 0xB9, 0x9A, 0xBC, 0xFF, 0xA3, 0x89, 0xF2, 0x60, 0x1F};
static const u8 keys360_0[] = {0x3C, 0x2B, 0x51, 0xD4, 0x2D, 0x85, 0x47, 0xDA, 0x2D, 0xCA, 0x18, 0xDF, 0xFE, 0x54, 0x09, 0xED};
static const u8 keys360_1[] = {0x31, 0x1F, 0x98, 0xD5, 0x7B, 0x58, 0x95, 0x45, 0x32, 0xAB, 0x3A, 0xE3, 0x89, 0x32, 0x4B, 0x34};
static const u8 keys370_0[] = {0x26, 0x38, 0x0A, 0xAC, 0xA5, 0xD8, 0x74, 0xD1, 0x32, 0xB7, 0x2A, 0xBF, 0x79, 0x9E, 0x6D, 0xDB};
static const u8 keys370_1[] = {0x53, 0xE7, 0xAB, 0xB9, 0xC6, 0x4A, 0x4B, 0x77, 0x92, 0x17, 0xB5, 0x74, 0x0A, 0xDA, 0xA9, 0xEA};
static const u8 keys370_2[] = {0x71, 0x10, 0xF0, 0xA4, 0x16, 0x14, 0xD5, 0x93, 0x12, 0xFF, 0x74, 0x96, 0xDF, 0x1F, 0xDA, 0x89};
static const u8 keys390_0[] = {0x45, 0xEF, 0x5C, 0x5D, 0xED, 0x81, 0x99, 0x84, 0x12, 0x94, 0x8F, 0xAB, 0xE8, 0x05, 0x6D, 0x7D};
static const u8 keys390_1[] = {0x70, 0x1B, 0x08, 0x25, 0x22, 0xA1, 0x4D, 0x3B, 0x69, 0x21, 0xF9, 0x71, 0x0A, 0xA8, 0x41, 0xA9};
static const u8 keys500_0[] = {0xEB, 0x1B, 0x53, 0x0B, 0x62, 0x49, 0x32, 0x58, 0x1F, 0x83, 0x0A, 0xF4, 0x99, 0x3D, 0x75, 0xD0};
static const u8 keys500_1[] = {0xBA, 0xE2, 0xA3, 0x12, 0x07, 0xFF, 0x04, 0x1B, 0x64, 0xA5, 0x11, 0x85, 0xF7, 0x2F, 0x99, 0x5B};
static const u8 keys500_2[] = {0x2C, 0x8E, 0xAF, 0x1D, 0xFF, 0x79, 0x73, 0x1A, 0xAD, 0x96, 0xAB, 0x09, 0xEA, 0x35, 0x59, 0x8B};
static const u8 keys500_c[] = {0xA3, 0x5D, 0x51, 0xE6, 0x56, 0xC8, 0x01, 0xCA, 0xE3, 0x77, 0xBF, 0xCD, 0xFF, 0x24, 0xDA, 0x4D};
static const u8 keys505_a[] = {0x7B, 0x94, 0x72, 0x27, 0x4C, 0xCC, 0x54, 0x3B, 0xAE, 0xDF, 0x46, 0x37, 0xAC, 0x01, 0x4D, 0x87};
static const u8 keys505_0[] = {0x2E, 0x8E, 0x97, 0xA2, 0x85, 0x42, 0x70, 0x73, 0x18, 0xDA, 0xA0, 0x8A, 0xF8, 0x62, 0xA2, 0xB0};
static const u8 keys505_1[] = {0x58, 0x2A, 0x4C, 0x69, 0x19, 0x7B, 0x83, 0x3D, 0xD2, 0x61, 0x61, 0xFE, 0x14, 0xEE, 0xAA, 0x11};
static const u8 keys555_k1[] = {0x9F, 0xFD, 0x4C, 0x28, 0x20, 0xB1, 0x3E, 0x76, 0x36, 0x4A, 0xAB, 0x1C, 0x54, 0xBC, 0x3B, 0xDC};
static const u8 keys555_k2[] = {0xAB, 0x1A, 0x74, 0x43, 0xF7, 0x4F, 0xE5, 0xFF, 0x04, 0xA5, 0xFC, 0x3B, 0xEC, 0xD4, 0xF8, 0xF0};
static const u8 keys570_5k[] = {0x6D, 0x72, 0xA4, 0xBA, 0x7F, 0xBF, 0xD1, 0xF1, 0xA9, 0xF3, 0xBB, 0x07, 0x1B, 0xC0, 0xB3, 0x66};
static const u8 keys600_1[] = {0xE3, 0x52, 0x39, 0x97, 0x3B, 0x84, 0x41, 0x1C, 0xC3, 0x23, 0xF1, 0xB8, 0xA9, 0x09, 0x4B, 0xF0};
static const u8 keys600_2[] = {0xE1, 0x45, 0x93, 0x2C, 0x53, 0xE2, 0xAB, 0x06, 0x6F, 0xB6, 0x8F, 0x0B, 0x66, 0x91, 0xE7, 0x1E};
static const u8 keys620_0[] = {0xD6, 0xBD, 0xCE, 0x1E, 0x12, 0xAF, 0x9A, 0xE6, 0x69, 0x30, 0xDE, 0xDA, 0x88, 0xB8, 0xFF, 0xFB};
static const u8 keys620_1[] = {0x1D, 0x13, 0xE9, 0x50, 0x04, 0x73, 0x3D, 0xD2, 0xE1, 0xDA, 0xB9, 0xC1, 0xE6, 0x7B, 0x25, 0xA7};
static const u8 keys620_a[] = {0xAC, 0x34, 0xBA, 0xB1, 0x97, 0x8D, 0xAE, 0x6F, 0xBA, 0xE8, 0xB1, 0xD6, 0xDF, 0xDF, 0xF1, 0xA2};
static const u8 keys620_e[] = {0xB1, 0xB3, 0x7F, 0x76, 0xC3, 0xFB, 0x88, 0xE6, 0xF8, 0x60, 0xD3, 0x35, 0x3C, 0xA3, 0x4E, 0xF3};
static const u8 keys620_5[] = {0xF1, 0xBC, 0x17, 0x07, 0xAE, 0xB7, 0xC8, 0x30, 0xD8, 0x34, 0x9D, 0x40, 0x6A, 0x8E, 0xDF, 0x4E};
static const u8 keys620_5k[] = {0x41, 0x8A, 0x35, 0x4F, 0x69, 0x3A, 0xDF, 0x04, 0xFD, 0x39, 0x46, 0xA2, 0x5C, 0x2D, 0xF2, 0x21};
static const u8 keys620_5v[] = {0xF2, 0x8F, 0x75, 0xA7, 0x31, 0x91, 0xCE, 0x9E, 0x75, 0xBD, 0x27, 0x26, 0xB4, 0xB4, 0x0C, 0x32};
static const u8 keys630_k1[] = {0x36, 0xB0, 0xDC, 0xFC, 0x59, 0x2A, 0x95, 0x1D, 0x80, 0x2D, 0x80, 0x3F, 0xCD, 0x30, 0xA0, 0x1B};
static const u8 keys630_k2[] = {0xd4, 0x35, 0x18, 0x02, 0x29, 0x68, 0xfb, 0xa0, 0x6a, 0xa9, 0xa5, 0xed, 0x78, 0xfd, 0x2e, 0x9d};
static const u8 keys630_k3[] = {0x23, 0x8D, 0x3D, 0xAE, 0x41, 0x50, 0xA0, 0xFA, 0xF3, 0x2F, 0x32, 0xCE, 0xC7, 0x27, 0xCD, 0x50};
static const u8 keys630_k4[] = {0xAA, 0xA1, 0xB5, 0x7C, 0x93, 0x5A, 0x95, 0xBD, 0xEF, 0x69, 0x16, 0xFC, 0x2B, 0x92, 0x31, 0xDD};
static const u8 keys630_k5[] = {0x87, 0x37, 0x21, 0xCC, 0x65, 0xAE, 0xAA, 0x5F, 0x40, 0xF6, 0x6F, 0x2A, 0x86, 0xC7, 0xA1, 0xC8};
static const u8 keys630_k6[] = {0x8D, 0xDB, 0xDC, 0x5C, 0xF2, 0x70, 0x2B, 0x40, 0xB2, 0x3D, 0x00, 0x09, 0x61, 0x7C, 0x10, 0x60};
static const u8 keys630_k7[] = {0x77, 0x1C, 0x06, 0x5F, 0x53, 0xEC, 0x3F, 0xFC, 0x22, 0xCE, 0x5A, 0x27, 0xFF, 0x78, 0xA8, 0x48};
static const u8 keys630_k8[] = {0x81, 0xD1, 0x12, 0x89, 0x35, 0xC8, 0xEA, 0x8B, 0xE0, 0x02, 0x2D, 0x2D, 0x6A, 0x18, 0x67, 0xB8};
static const u8 keys636_k1[] = {0x07, 0xE3, 0x08, 0x64, 0x7F, 0x60, 0xA3, 0x36, 0x6A, 0x76, 0x21, 0x44, 0xC9, 0xD7, 0x06, 0x83};
static const u8 keys636_k2[] = {0x91, 0xF2, 0x02, 0x9E, 0x63, 0x32, 0x30, 0xA9, 0x1D, 0xDA, 0x0B, 0xA8, 0xB7, 0x41, 0xA3, 0xCC};
static const u8 keys638_k4[] = {0x98, 0x43, 0xFF, 0x85, 0x68, 0xB2, 0xDB, 0x3B, 0xD4, 0x22, 0xD0, 0x4F, 0xAB, 0x5F, 0x0A, 0x31};
static const u8 keys639_k3[] = {0x01, 0x7B, 0xF0, 0xE9, 0xBE, 0x9A, 0xDD, 0x54, 0x37, 0xEA, 0x0E, 0xC4, 0xD6, 0x4D, 0x8E, 0x9E};
static const u8 keys660_k1[] = {0x76, 0xF2, 0x6C, 0x0A, 0xCA, 0x3A, 0xBA, 0x4E, 0xAC, 0x76, 0xD2, 0x40, 0xF5, 0xC3, 0xBF, 0xF9};
static const u8 keys660_k2[] = {0x7A, 0x3E, 0x55, 0x75, 0xB9, 0x6A, 0xFC, 0x4F, 0x3E, 0xE3, 0xDF, 0xB3, 0x6C, 0xE8, 0x2A, 0x82};
static const u8 keys660_k3[] = {0xFA, 0x79, 0x09, 0x36, 0xE6, 0x19, 0xE8, 0xA4, 0xA9, 0x41, 0x37, 0x18, 0x81, 0x02, 0xE9, 0xB3};
static const u8 keys660_v1[] = {0xBA, 0x76, 0x61, 0x47, 0x8B, 0x55, 0xA8, 0x72, 0x89, 0x15, 0x79, 0x6D, 0xD7, 0x2F, 0x78, 0x0E};
static const u8 keys660_v2[] = {0xF9, 0x4A, 0x6B, 0x96, 0x79, 0x3F, 0xEE, 0x0A, 0x04, 0xC8, 0x8D, 0x7E, 0x5F, 0x38, 0x3A, 0xCF};
static const u8 keys660_v3[] = {0x88, 0xAF, 0x18, 0xE9, 0xC3, 0xAA, 0x6B, 0x56, 0xF7, 0xC5, 0xA8, 0xBF, 0x1A, 0x84, 0xE9, 0xF3};
static const u8 keys660_v4[] = {0xD1, 0xB0, 0xAE, 0xC3, 0x24, 0x36, 0x13, 0x49, 0xD6, 0x49, 0xD7, 0x88, 0xEA, 0xA4, 0x99, 0x86};
static const u8 keys660_v5[] = {0xCB, 0x93, 0x12, 0x38, 0x31, 0xC0, 0x2D, 0x2E, 0x7A, 0x18, 0x5C, 0xAC, 0x92, 0x93, 0xAB, 0x32};
static const u8 keys660_v6[] = {0x92, 0x8C, 0xA4, 0x12, 0xD6, 0x5C, 0x55, 0x31, 0x5B, 0x94, 0x23, 0x9B, 0x62, 0xB3, 0xDB, 0x47};
static const u8 keys660_k4[] = {0xC8, 0xA0, 0x70, 0x98, 0xAE, 0xE6, 0x2B, 0x80, 0xD7, 0x91, 0xE6, 0xCA, 0x4C, 0xA9, 0x78, 0x4E};
static const u8 keys660_k5[] = {0xBF, 0xF8, 0x34, 0x02, 0x84, 0x47, 0xBD, 0x87, 0x1C, 0x52, 0x03, 0x23, 0x79, 0xBB, 0x59, 0x81};
static const u8 keys660_k6[] = {0xD2, 0x83, 0xCC, 0x63, 0xBB, 0x10, 0x15, 0xE7, 0x7B, 0xC0, 0x6D, 0xEE, 0x34, 0x9E, 0x4A, 0xFA};
static const u8 keys660_k7[] = {0xEB, 0xD9, 0x1E, 0x05, 0x3C, 0xAE, 0xAB, 0x62, 0xE3, 0xB7, 0x1F, 0x37, 0xE5, 0xCD, 0x68, 0xC3};
static const u8 keys660_v7[] = {0xC5, 0x9C, 0x77, 0x9C, 0x41, 0x01, 0xE4, 0x85, 0x79, 0xC8, 0x71, 0x63, 0xA5, 0x7D, 0x4F, 0xFB};
static const u8 keys660_v8[] = {0x86, 0xA0, 0x7D, 0x4D, 0xB3, 0x6B, 0xA2, 0xFD, 0xF4, 0x15, 0x85, 0x70, 0x2D, 0x6A, 0x0D, 0x3A};
static const u8 keys660_k8[] = {0x85, 0x93, 0x1F, 0xED, 0x2C, 0x4D, 0xA4, 0x53, 0x59, 0x9C, 0x3F, 0x16, 0xF3, 0x50, 0xDE, 0x46};
static const u8 key_21C0[] = {0x6A, 0x19, 0x71, 0xF3, 0x18, 0xDE, 0xD3, 0xA2, 0x6D, 0x3B, 0xDE, 0xC7, 0xBE, 0x98, 0xE2, 0x4C};
static const u8 key_2250[] = {0x50, 0xCC, 0x03, 0xAC, 0x3F, 0x53, 0x1A, 0xFA, 0x0A, 0xA4, 0x34, 0x23, 0x86, 0x61, 0x7F, 0x97};
static const u8 key_22E0[] = {0x66, 0x0F, 0xCB, 0x3B, 0x30, 0x75, 0xE3, 0x10, 0x0A, 0x95, 0x65, 0xC7, 0x3C, 0x93, 0x87, 0x22};
static const u8 key_2D80[] = {0x40, 0x02, 0xC0, 0xBF, 0x20, 0x02, 0xC0, 0xBF, 0x5C, 0x68, 0x2B, 0x95, 0x5F, 0x40, 0x7B, 0xB8};
static const u8 key_2D90[] = {0x55, 0x19, 0x35, 0x10, 0x48, 0xD8, 0x2E, 0x46, 0xA8, 0xB1, 0x47, 0x77, 0xDC, 0x49, 0x6A, 0x6F};
static const u8 key_2DA8[] = {0x80, 0x02, 0xC0, 0xBF, 0x00, 0x0A, 0xC0, 0xBF, 0x40, 0x03, 0xC0, 0xBF, 0x40, 0x00, 0x00, 0x00};
static const u8 key_2DB8[] = {0x4C, 0x2D, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xB8, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static const u8 key_D91605F0[] = {0xB8, 0x8C, 0x45, 0x8B, 0xB6, 0xE7, 0x6E, 0xB8, 0x51, 0x59, 0xA6, 0x53, 0x7C, 0x5E, 0x86, 0x31};
static const u8 key_D91606F0[] = {0xED, 0x10, 0xE0, 0x36, 0xC4, 0xFE, 0x83, 0xF3, 0x75, 0x70, 0x5E, 0xF6, 0xA4, 0x40, 0x05, 0xF7};
static const u8 key_D91608F0[] = {0x5C, 0x77, 0x0C, 0xBB, 0xB4, 0xC2, 0x4F, 0xA2, 0x7E, 0x3B, 0x4E, 0xB4, 0xB4, 0xC8, 0x70, 0xAF};
static const u8 key_D91609F0[] = {0xD0, 0x36, 0x12, 0x75, 0x80, 0x56, 0x20, 0x43, 0xC4, 0x30, 0x94, 0x3E, 0x1C, 0x75, 0xD1, 0xBF};
static const u8 key_D9160AF0[] = {0x10, 0xA9, 0xAC, 0x16, 0xAE, 0x19, 0xC0, 0x7E, 0x3B, 0x60, 0x77, 0x86, 0x01, 0x6F, 0xF2, 0x63};
static const u8 key_D9160BF0[] = {0x83, 0x83, 0xF1, 0x37, 0x53, 0xD0, 0xBE, 0xFC, 0x8D, 0xA7, 0x32, 0x52, 0x46, 0x0A, 0xC2, 0xC2};
static const u8 key_D91611F0[] = {0x61, 0xB0, 0xC0, 0x58, 0x71, 0x57, 0xD9, 0xFA, 0x74, 0x67, 0x0E, 0x5C, 0x7E, 0x6E, 0x95, 0xB9};
static const u8 key_D91612F0[] = {0x9E, 0x20, 0xE1, 0xCD, 0xD7, 0x88, 0xDE, 0xC0, 0x31, 0x9B, 0x10, 0xAF, 0xC5, 0xB8, 0x73, 0x23};
static const u8 key_D91613F0[] = {0xEB, 0xFF, 0x40, 0xD8, 0xB4, 0x1A, 0xE1, 0x66, 0x91, 0x3B, 0x8F, 0x64, 0xB6, 0xFC, 0xB7, 0x12};
static const u8 key_D91614F0[] = {0xFD, 0xF7, 0xB7, 0x3C, 0x9F, 0xD1, 0x33, 0x95, 0x11, 0xB8, 0xB5, 0xBB, 0x54, 0x23, 0x73, 0x85};
static const u8 key_D91615F0[] = {0xC8, 0x03, 0xE3, 0x44, 0x50, 0xF1, 0xE7, 0x2A, 0x6A, 0x0D, 0xC3, 0x61, 0xB6, 0x8E, 0x5F, 0x51};
static const u8 key_D91616F0[] = {0x53, 0x03, 0xB8, 0x6A, 0x10, 0x19, 0x98, 0x49, 0x1C, 0xAF, 0x30, 0xE4, 0x25, 0x1B, 0x6B, 0x28};
static const u8 key_D91617F0[] = {0x02, 0xFA, 0x48, 0x73, 0x75, 0xAF, 0xAE, 0x0A, 0x67, 0x89, 0x2B, 0x95, 0x4B, 0x09, 0x87, 0xA3};
static const u8 key_D91618F0[] = {0x96, 0x96, 0x7C, 0xC3, 0xF7, 0x12, 0xDA, 0x62, 0x1B, 0xF6, 0x9A, 0x9A, 0x44, 0x44, 0xBC, 0x48};
static const u8 key_D91619F0[] = {0xE0, 0x32, 0xA7, 0x08, 0x6B, 0x2B, 0x29, 0x2C, 0xD1, 0x4D, 0x5B, 0xEE, 0xA8, 0xC8, 0xB4, 0xE9};
static const u8 key_D9161AF0[] = {0x27, 0xE5, 0xA7, 0x49, 0x52, 0xE1, 0x94, 0x67, 0x35, 0x66, 0x91, 0x0C, 0xE8, 0x9A, 0x25, 0x24};
static const u8 key_D91620F0[] = {0x52, 0x1C, 0xB4, 0x5F, 0x40, 0x3B, 0x9A, 0xDD, 0xAC, 0xFC, 0xEA, 0x92, 0xFD, 0xDD, 0xF5, 0x90};
static const u8 key_D91621F0[] = {0xD1, 0x91, 0x2E, 0xA6, 0x21, 0x14, 0x29, 0x62, 0xF6, 0xED, 0xAE, 0xCB, 0xDD, 0xA3, 0xBA, 0xFE};
static const u8 key_D91622F0[] = {0x59, 0x5D, 0x78, 0x4D, 0x21, 0xB2, 0x01, 0x17, 0x6C, 0x9A, 0xB5, 0x1B, 0xDA, 0xB7, 0xF9, 0xE6};
static const u8 key_D91623F0[] = {0xAA, 0x45, 0xEB, 0x4F, 0x62, 0xFB, 0xD1, 0x0D, 0x71, 0xD5, 0x62, 0xD2, 0xF5, 0xBF, 0xA5, 0x2F};
static const u8 key_D91624F0[] = {0x61, 0xB7, 0x26, 0xAF, 0x8B, 0xF1, 0x41, 0x58, 0x83, 0x6A, 0xC4, 0x92, 0x12, 0xCB, 0xB1, 0xE9};
static const u8 key_D91628F0[] = {0x49, 0xA4, 0xFC, 0x66, 0xDC, 0xE7, 0x62, 0x21, 0xDB, 0x18, 0xA7, 0x50, 0xD6, 0xA8, 0xC1, 0xB6};
static const u8 key_D91680F0[] = {0x2C, 0x22, 0x9B, 0x12, 0x36, 0x74, 0x11, 0x67, 0x49, 0xD1, 0xD1, 0x88, 0x92, 0xF6, 0xA1, 0xD8};
static const u8 key_D91681F0[] = {0x52, 0xB6, 0x36, 0x6C, 0x8C, 0x46, 0x7F, 0x7A, 0xCC, 0x11, 0x62, 0x99, 0xC1, 0x99, 0xBE, 0x98};
static const u8 key_2E5E0AF0[] = {0xAC, 0xD7, 0x6D, 0x90, 0x33, 0x37, 0x38, 0x92, 0x31, 0x41, 0xD6, 0x36, 0xAF, 0x9F, 0x17, 0x15};
static const u8 xor_2E5E0AF0[] = {0x69, 0xBA, 0x55, 0x34, 0xF0, 0xC0, 0xD6, 0x71, 0xE3, 0x1F, 0xDB, 0x97, 0xE0, 0x7C, 0xD2, 0x2A};
static const u8 key_2E5E10F0[] = {0x9D, 0x5C, 0x5B, 0xAF, 0x8C, 0xD8, 0x69, 0x7E, 0x51, 0x9F, 0x70, 0x96, 0xE6, 0xD5, 0xC4, 0xE8};
static const u8 xor_2E5E10F0[] = {0x69, 0xBA, 0x55, 0x34, 0xF0, 0xC0, 0xD6, 0x71, 0xE3, 0x1F, 0xDB, 0x97, 0xE0, 0x7C, 0xD2, 0x2A};
static const u8 key_2E5E11F0[] = {0x75, 0xEB, 0xE8, 0x43, 0xF4, 0x87, 0x8F, 0xD0, 0x14, 0x7F, 0x7E, 0x39, 0xAD, 0xAF, 0x04, 0x9D};
static const u8 xor_2E5E11F0[] = {0x69, 0xBA, 0x55, 0x34, 0xF0, 0xC0, 0xD6, 0x71, 0xE3, 0x1F, 0xDB, 0x97, 0xE0, 0x7C, 0xD2, 0x2A};
static const u8 key_2E5E12F0[] = {0x8A, 0x7B, 0xC9, 0xD6, 0x52, 0x58, 0x88, 0xEA, 0x51, 0x83, 0x60, 0xCA, 0x16, 0x79, 0xE2, 0x07};
static const u8 xor_2E5E12F0[] = {0x69, 0xBA, 0x55, 0x34, 0xF0, 0xC0, 0xD6, 0x71, 0xE3, 0x1F, 0xDB, 0x97, 0xE0, 0x7C, 0xD2, 0x2A};
static const u8 key_2E5E13F0[] = {0xFF, 0xA4, 0x68, 0xC3, 0x31, 0xCA, 0xB7, 0x4C, 0xF1, 0x23, 0xFF, 0x01, 0x65, 0x3D, 0x26, 0x36};
static const u8 xor_2E5E13F0[] = {0x69, 0xBA, 0x55, 0x34, 0xF0, 0xC0, 0xD6, 0x71, 0xE3, 0x1F, 0xDB, 0x97, 0xE0, 0x7C, 0xD2, 0x2A};
static const u8 key_2E5E80F0[] = {0x0F, 0x74, 0xAF, 0x43, 0x75, 0xCD, 0xDA, 0x39, 0x81, 0x56, 0xD9, 0x61, 0x3E, 0x16, 0xC8, 0x92};
static const u8 xor_2E5E80F0[] = {0x69, 0xBA, 0x55, 0x34, 0xF0, 0xC0, 0xD6, 0x71, 0xE3, 0x1F, 0xDB, 0x97, 0xE0, 0x7C, 0xD2, 0x2A};
static const u8 key_2E5E90F0[] = {0x67, 0xE4, 0x8F, 0x4C, 0x08, 0xA0, 0x7D, 0xB1, 0x5F, 0x51, 0xA7, 0x72, 0x98, 0xA8, 0x2D, 0x7E};
static const u8 xor_2E5E90F0[] = {0x69, 0xBA, 0x55, 0x34, 0xF0, 0xC0, 0xD6, 0x71, 0xE3, 0x1F, 0xDB, 0x97, 0xE0, 0x7C, 0xD2, 0x2A};
static const u8 key_2FD30BF0[] = {0xD8, 0x58, 0x79, 0xF9, 0xA4, 0x22, 0xAF, 0x86, 0x90, 0xAC, 0xDA, 0x45, 0xCE, 0x60, 0x40, 0x3F};
static const u8 keys02G_E[] = {0x9D, 0x09, 0xFD, 0x20, 0xF3, 0x8F, 0x10, 0x69, 0x0D, 0xB2, 0x6F, 0x00, 0xCC, 0xC5, 0x51, 0x2E};
static const u8 keys03G_E[] = {0x4F, 0x44, 0x5C, 0x62, 0xB3, 0x53, 0xC4, 0x30, 0xFC, 0x3A, 0xA4, 0x5B, 0xEC, 0xFE, 0x51, 0xEA};
static const u8 keys05G_E[] = {0x5D, 0xAA, 0x72, 0xF2, 0x26, 0x60, 0x4D, 0x1C, 0xE7, 0x2D, 0xC8, 0xA3, 0x2F, 0x79, 0xC5, 0x54};
static const u8 oneseg_310[] = {0xC7, 0x27, 0x72, 0x85, 0xAB, 0xA7, 0xF7, 0xF0, 0x4C, 0xC1, 0x86, 0xCC, 0xE3, 0x7F, 0x17, 0xCA};
static const u8 oneseg_300[] = {0x76, 0x40, 0x9E, 0x08, 0xDB, 0x9B, 0x3B, 0xA1, 0x47, 0x8A, 0x96, 0x8E, 0xF3, 0xF7, 0x62, 0x92};
static const u8 oneseg_280[] = {0x23, 0xDC, 0x3B, 0xB5, 0xA9, 0x82, 0xD6, 0xEA, 0x63, 0xA3, 0x6E, 0x2B, 0x2B, 0xE9, 0xE1, 0x54};
static const u8 oneseg_260_271[] = {0x22, 0x43, 0x57, 0x68, 0x2F, 0x41, 0xCE, 0x65, 0x4C, 0xA3, 0x7C, 0xC6, 0xC4, 0xAC, 0xF3, 0x60};
static const u8 oneseg_slim[] = {0x12, 0x57, 0x0D, 0x8A, 0x16, 0x6D, 0x87, 0x06, 0x03, 0x7D, 0xC8, 0x8B, 0x62, 0xA3, 0x32, 0xA9};
static const u8 ms_app_main[] = {0x1E, 0x2E, 0x38, 0x49, 0xDA, 0xD4, 0x16, 0x08, 0x27, 0x2E, 0xF3, 0xBC, 0x37, 0x75, 0x80, 0x93};
static const u8 demokeys_280[] = {0x12, 0x99, 0x70, 0x5E, 0x24, 0x07, 0x6C, 0xD0, 0x2D, 0x06, 0xFE, 0x7E, 0xB3, 0x0C, 0x11, 0x26};
static const u8 demokeys_3XX_1[] = {0x47, 0x05, 0xD5, 0xE3, 0x56, 0x1E, 0x81, 0x9B, 0x09, 0x2F, 0x06, 0xDB, 0x6B, 0x12, 0x92, 0xE0};
static const u8 demokeys_3XX_2[] = {0xF6, 0x62, 0x39, 0x6E, 0x26, 0x22, 0x4D, 0xCA, 0x02, 0x64, 0x16, 0x99, 0x7B, 0x9A, 0xE7, 0xB8};
static const u8 ebootbin_271_new[] = {0xF4, 0xAE, 0xF4, 0xE1, 0x86, 0xDD, 0xD2, 0x9C, 0x7C, 0xC5, 0x42, 0xA6, 0x95, 0xA0, 0x83, 0x88};
static const u8 gameshare_260_271[] = {0xF9, 0x48, 0x38, 0x0C, 0x96, 0x88, 0xA7, 0x74, 0x4F, 0x65, 0xA0, 0x54, 0xC2, 0x76, 0xD9, 0xB8};
static const u8 gameshare_280[] = {0x2D, 0x86, 0x77, 0x3A, 0x56, 0xA4, 0x4F, 0xDD, 0x3C, 0x16, 0x71, 0x93, 0xAA, 0x8E, 0x11, 0x43};
static const u8 gameshare_300[] = {0x78, 0x1A, 0xD2, 0x87, 0x24, 0xBD, 0xA2, 0x96, 0x18, 0x3F, 0x89, 0x36, 0x72, 0x90, 0x92, 0x85};
static const u8 gameshare_310[] = {0xC9, 0x7D, 0x3E, 0x0A, 0x54, 0x81, 0x6E, 0xC7, 0x13, 0x74, 0x99, 0x74, 0x62, 0x18, 0xE7, 0xDD};
static const u8 key_380210F0[] = {0x32, 0x2C, 0xFA, 0x75, 0xE4, 0x7E, 0x93, 0xEB, 0x9F, 0x22, 0x80, 0x85, 0x57, 0x08, 0x98, 0x48};
static const u8 key_380280F0[] = {0x97, 0x09, 0x12, 0xD3, 0xDB, 0x02, 0xBD, 0xD8, 0xE7, 0x74, 0x51, 0xFE, 0xF0, 0xEA, 0x6C, 0x5C};
static const u8 key_380283F0[] = {0x34, 0x20, 0x0C, 0x8E, 0xA1, 0x86, 0x79, 0x84, 0xAF, 0x13, 0xAE, 0x34, 0x77, 0x6F, 0xEA, 0x89};
static const u8 key_407810F0[] = {0xAF, 0xAD, 0xCA, 0xF1, 0x95, 0x59, 0x91, 0xEC, 0x1B, 0x27, 0xD0, 0x4E, 0x8A, 0xF3, 0x3D, 0xE7};
static const u8 drmkeys_6XX_1[] = {0x36, 0xEF, 0x82, 0x4E, 0x74, 0xFB, 0x17, 0x5B, 0x14, 0x14, 0x05, 0xF3, 0xB3, 0x8A, 0x76, 0x18};
static const u8 drmkeys_6XX_2[] = {0x21, 0x52, 0x5D, 0x76, 0xF6, 0x81, 0x0F, 0x15, 0x2F, 0x4A, 0x40, 0x89, 0x63, 0xA0, 0x10, 0x55};
static const u8 pauth_98b83b5d_1[] = {0xB0, 0x24, 0xC8, 0x16, 0x43, 0xE8, 0xF0, 0x1C, 0x8C, 0x30, 0x67, 0x73, 0x3E, 0x96, 0x35, 0xEF};
static const u8 pauth_98b83b5d_xor[] = {0xA9, 0x1E, 0xDD, 0x7B, 0x09, 0xBB, 0x22, 0xB5, 0x9D, 0xA3, 0x30, 0x69, 0x13, 0x6E, 0x0E, 0xD8};
static const u8 pauth_f7aa47f6_1[] = {0xC5, 0xFB, 0x69, 0x03, 0x20, 0x7A, 0xCF, 0xBA, 0x2C, 0x90, 0xF8, 0xB8, 0x4D, 0xD2, 0xF1, 0xDE};
static const u8 pauth_f7aa47f6_2[] = {0x3A, 0x6B, 0x48, 0x96, 0x86, 0xA5, 0xC8, 0x80, 0x69, 0x6C, 0xE6, 0x4B, 0xF6, 0x04, 0x17, 0x44};
static const u8 pauth_f7aa47f6_xor[] = {0xA9, 0x1E, 0xDD, 0x7B, 0x09, 0xBB, 0x22, 0xB5, 0x9D, 0xA3, 0x30, 0x69, 0x13, 0x6E, 0x0E, 0xD8};
// Retail game patch installed by the DRM BB install utility (decrypt mode 18). JPCSP KeyVault.
static const u8 key_89742B04[] = {0xD7, 0xEB, 0xC9, 0x24, 0x7E, 0x23, 0x3D, 0x89, 0x46, 0xE7, 0x2E, 0x47, 0xAD, 0xDB, 0x0D, 0x09};
static const u8 xor_89742B04[] = {0xFF, 0x5E, 0xF1, 0xE9, 0xB1, 0xC9, 0x3E, 0xC5, 0xDB, 0xE0, 0x67, 0x82, 0x95, 0x3A, 0x8E, 0xA5};
// A type 3 module as it ships, inside a DRM BB install package, has its first type 5 step encrypted
// for KIRK command 2, whose key (AES keyslot 3) isn't known. The install re-encrypts it for the console
// with command 2. These are command 2's results (the 0x50 bytes the first step decrypts to) for the
// packages we know of, recovered from installs on a PSP. They're keyed by the first 16 bytes of the
// command 2 header at 0xEC, its encrypted per-module key.
struct Kirk2Result {
u8 header[16];
u8 result[0x50];
};
static const Kirk2Result g_kirk2Results[] = {
// Killzone: Liberation 1.20 + Chapter 5 (UCES00279), KZL.PRX.
{
{0x68, 0x2F, 0x47, 0x0B, 0xE9, 0x4C, 0xE5, 0xE9, 0xA4, 0x39, 0xD7, 0x6F, 0x38, 0x53, 0xCF, 0xDE},
{
0xF6, 0xDC, 0x71, 0xF8, 0x19, 0x1A, 0xDB, 0xC4, 0xFF, 0x5C, 0xCE, 0x3A, 0xAE, 0x8A, 0x04, 0x11,
0xE3, 0x45, 0x91, 0xDA, 0x7F, 0xE3, 0x2E, 0x75, 0xB8, 0x6B, 0xDF, 0x87, 0x55, 0x22, 0xF6, 0x90,
0x96, 0x7B, 0xF4, 0xF6, 0x88, 0x55, 0xE0, 0xF1, 0xB5, 0xD8, 0xDF, 0xF0, 0x9B, 0xB1, 0xBD, 0x60,
0x46, 0xA0, 0x1F, 0x53, 0x64, 0xD9, 0x7C, 0xF7, 0x56, 0x7E, 0x23, 0x8A, 0x8F, 0x8E, 0xFD, 0xF6,
0x1F, 0x28, 0xDD, 0xB9, 0x39, 0xDE, 0x58, 0xD7, 0x48, 0x55, 0x64, 0x99, 0x5A, 0xD2, 0x55, 0x6C,
},
},
// Killzone: Liberation 1.20 + Chapter 5 (UCUS98646), KZL.PRX.
{
{0x03, 0x48, 0xC4, 0x10, 0xCD, 0x29, 0x80, 0x92, 0xE9, 0x6D, 0xB8, 0x14, 0xE2, 0x0E, 0x24, 0x2F},
{
0xDE, 0xE8, 0x0D, 0x22, 0x09, 0xEF, 0x91, 0x45, 0x57, 0xAF, 0x9D, 0xC8, 0xB4, 0xF1, 0xC2, 0x0C,
0x34, 0x14, 0x1A, 0x54, 0x66, 0xA1, 0xB3, 0xF8, 0x8E, 0xCF, 0xE9, 0x15, 0x77, 0x42, 0x3B, 0x38,
0x67, 0xEA, 0xF8, 0xA7, 0x5C, 0x0A, 0xBF, 0x1D, 0xAD, 0x0D, 0xED, 0x83, 0xEF, 0x8A, 0x0B, 0x54,
0xEF, 0xB2, 0xA2, 0x1E, 0x48, 0x3D, 0x92, 0x9D, 0x02, 0xAC, 0x01, 0x35, 0x13, 0xB2, 0xB7, 0xB4,
0x72, 0x50, 0xDC, 0x92, 0x22, 0xCA, 0x80, 0x10, 0xD6, 0x45, 0x9B, 0x84, 0x7D, 0xB9, 0x7B, 0x05,
},
},
};
static const u8 *FindKirk2Result(const u8 *header) {
for (const Kirk2Result &r : g_kirk2Results) {
if (!memcmp(r.header, header, sizeof(r.header))) {
return r.result;
}
}
return nullptr;
}
// PRXDecrypter 144-byte tag keys.
static const u32 g_key0[] = {
0x7b21f3be, 0x299c5e1d, 0x1c9c5e71, 0x96cb4645, 0x3c9b1be0, 0xeb85de3d,
0x4a7f2022, 0xc2206eaa, 0xd50b3265, 0x55770567, 0x3c080840, 0x981d55f2,
0x5fd8f6f3, 0xee8eb0c5, 0x944d8152, 0xf8278651, 0x2705bafa, 0x8420e533,
0x27154ae9, 0x4819aa32, 0x59a3aa40, 0x2cb3cf65, 0xf274466d, 0x3a655605,
0x21b0f88f, 0xc5b18d26, 0x64c19051, 0xd669c94e, 0xe87035f2, 0x9d3a5909,
0x6f4e7102, 0xdca946ce, 0x8416881b, 0xbab097a5, 0x249125c6, 0xb34c0872};
static const u32 g_key2[] = {
0xccfda932, 0x51c06f76, 0x046dcccf, 0x49e1821e, 0x7d3b024c, 0x9dda5865,
0xcc8c9825, 0xd1e97db5, 0x6874d8cb, 0x3471c987, 0x72edb3fc, 0x81c8365d,
0xe161e33a, 0xfc92db59, 0x2009b1ec, 0xb1a94ce4, 0x2f03696b, 0x87e236d8,
0x3b2b8ce9, 0x0305e784, 0xf9710883, 0xb039db39, 0x893bea37, 0xe74d6805,
0x2a5c38bd, 0xb08dc813, 0x15b32375, 0x46be4525, 0x0103fd90, 0xa90e87a2,
0x52aba66a, 0x85bf7b80, 0x45e8ce63, 0x4dd716d3, 0xf5e30d2d, 0xaf3ae456};
static const u32 g_key3[] = {
0xa6c8f5ca, 0x6d67c080, 0x924f4d3a, 0x047ca06a, 0x08640297, 0x4fd4a758,
0xbd685a87, 0x9b2701c2, 0x83b62a35, 0x726b533c, 0xe522fa0c, 0xc24b06b4,
0x459d1cac, 0xa8c5417b, 0x4fea62a2, 0x0615d742, 0x30628d09, 0xc44fab14,
0x69ff715e, 0xd2d8837d, 0xbeed0b8b, 0x1e6e57ae, 0x61e8c402, 0xbe367a06,
0x543f2b5e, 0xdb3ec058, 0xbe852075, 0x1e7e4dcc, 0x1564ea55, 0xec7825b4,
0xc0538cad, 0x70f72c7f, 0x49e8c3d0, 0xeda97ec5, 0xf492b0a4, 0xe05eb02a};
static const u32 g_key44[] = {
0xef80e005, 0x3a54689f, 0x43c99ccd, 0x1b7727be, 0x5cb80038, 0xdd2efe62,
0xf369f92c, 0x160f94c5, 0x29560019, 0xbf3c10c5, 0xf2ce5566, 0xcea2c626,
0xb601816f, 0x64e7481e, 0x0c34debd, 0x98f29cb0, 0x3fc504d7, 0xc8fb39f0,
0x0221b3d8, 0x63f936a2, 0x9a3a4800, 0x6ecc32e3, 0x8e120cfd, 0xb0361623,
0xaee1e689, 0x745502eb, 0xe4a6c61c, 0x74f23eb4, 0xd7fa5813, 0xb01916eb,
0x12328457, 0xd2bc97d2, 0x646425d8, 0x328380a5, 0x43da8ab1, 0x4b122ac9};
static const u32 g_key20[] = {
0x33b50800, 0xf32f5fcd, 0x3c14881f, 0x6e8a2a95, 0x29feefd5, 0x1394eae3,
0xbd6bd443, 0x0821c083, 0xfab379d3, 0xe613e165, 0xf5a754d3, 0x108b2952,
0x0a4b1e15, 0x61eadeba, 0x557565df, 0x3b465301, 0xae54ecc3, 0x61423309,
0x70c9ff19, 0x5b0ae5ec, 0x989df126, 0x9d987a5f, 0x55bc750e, 0xc66eba27,
0x2de988e8, 0xf76600da, 0x0382dccb, 0x5569f5f2, 0x8e431262, 0x288fe3d3,
0x656f2187, 0x37d12e9c, 0x2f539eb4, 0xa492998e, 0xed3958f7, 0x39e96523};
static const u32 g_key3A[] = {
0x67877069, 0x3abd5617, 0xc23ab1dc, 0xab57507d, 0x066a7f40, 0x24def9b9,
0x06f759e4, 0xdcf524b1, 0x13793e5e, 0x0359022d, 0xaae7e1a2, 0x76b9b2fa,
0x9a160340, 0x87822fba, 0x19e28fbb, 0x9e338a02, 0xd8007e9a, 0xea317af1,
0x630671de, 0x0b67ca7c, 0x865192af, 0xea3c3526, 0x2b448c8e, 0x8b599254,
0x4602e9cb, 0x4de16cda, 0xe164d5bb, 0x07ecd88e, 0x99ffe5f8, 0x768800c1,
0x53b091ed, 0x84047434, 0xb426dbbc, 0x36f948bb, 0x46142158, 0x749bb492};
static const u32 g_keyEBOOT1xx[] = {
0x18CB69EF, 0x158E8912, 0xDEF90EBB, 0x4CB0FB23, 0x3687EE18, 0x868D4A6E,
0x19B5C756, 0xEE16551D, 0xE7CB2D6C, 0x9747C660, 0xCE95143F, 0x2956F477,
0x03824ADE, 0x210C9DF1, 0x5029EB24, 0x81DFE69F, 0x39C89B00, 0xB00C8B91,
0xEF2DF9C2, 0xE13A93FC, 0x8B94A4A8, 0x491DD09D, 0x686A400D, 0xCED4C7E4,
0x96C8B7C9, 0x1EAADC28, 0xA4170B84, 0x505D5DDC, 0x5DA6C3CF, 0x0E5DFA2D,
0x6E7919B5, 0xCE5E29C7, 0xAAACDB94, 0x45F70CDD, 0x62A73725, 0xCCE6563D};
static const u32 g_keyEBOOT2xx[] = {
0xDA8E36FA, 0x5DD97447, 0x76C19874, 0x97E57EAF, 0x1CAB09BD, 0x9835BAC6,
0x03D39281, 0x03B205CF, 0x2882E734, 0xE714F663, 0xB96E2775, 0xBD8AAFC7,
0x1DD3EC29, 0xECA4A16C, 0x5F69EC87, 0x85981E92, 0x7CFCAE21, 0xBAE9DD16,
0xE6A97804, 0x2EEE02FC, 0x61DF8A3D, 0xDD310564, 0x9697E149, 0xC2453F3B,
0xF91D8456, 0x39DA6BC8, 0xB3E5FEF5, 0x89C593A3, 0xFB5C8ABC, 0x6C0B7212,
0xE10DD3CB, 0x98D0B2A8, 0x5FD61847, 0xF0DC2357, 0x7701166A, 0x0F5C3B68};
static const u32 g_demokeys_280[] = {
0x2A5282B4, 0x8706DDA5, 0x4C88EC1C, 0xD504708E, 0x72634DD2, 0xDD2E2F60,
0xE3D5FDB5, 0xE050637D, 0x295C69AC, 0x7B61F57D, 0x594412B0, 0x13D925CE,
0x2A6BE8DD, 0xBC9594E6, 0x1F4A8A39, 0xC56B5909, 0x52CFB2F7, 0x03EE089F,
0x5CA57A21, 0xDB64090F, 0x5E9A56F3, 0x13C56633, 0xD9C48D1D, 0xCDA05972,
0xD09E13B2, 0x7DEDD3DF, 0x364387BB, 0xCB207488, 0xBEC14B3F, 0x7C9C0D11,
0x9916ED40, 0x65909519, 0xC55BB1B3, 0xE997E084, 0xB483438B, 0xB8A2D255};
static const u32 g_keyUPDATER[] = {
0xA5603CBF, 0xD7482441, 0xF65764CC, 0x1F90060B, 0x4EA73E45, 0xE551D192,
0xE7B75D8A, 0x465A506E, 0x40FB1022, 0x2C273350, 0x8096DA44, 0x9947198E,
0x278DEE77, 0x745D062E, 0xC148FA45, 0x832582AF, 0x5FDB86DA, 0xCB15C4CE,
0x2524C62F, 0x6C2EC3B1, 0x369BE39E, 0xF7EB1FC4, 0x1E51CE1A, 0xD70536F4,
0xC34D39D8, 0x7418FB13, 0xE3C84DE1, 0xB118F03C, 0xA2018D4E, 0xE6D8770D,
0x5720F390, 0x17F96341, 0x60A4A68F, 0x1327DD28, 0x05944C64, 0x0C2C4C12};
static const u32 g_keyMEIMG250[] = {
0xA381FEBC, 0x99B9D5C9, 0x6C560A8D, 0x30309F95, 0x792646CC, 0x82B64E5E,
0x1A3951AD, 0x0A182EC4, 0xC46131B4, 0x77C50C8A, 0x325F16C6, 0x02D1942E,
0x0AA38AC4, 0x2A940AC6, 0x67034726, 0xE52DB133, 0xD2EF2107, 0x85C81E90,
0xC8D164BA, 0xC38DCE1D, 0x948BA275, 0x0DB84603, 0xE2473637, 0xCD74FCDA,
0x588E3D66, 0x6D28E822, 0x891E548B, 0xF53CF56D, 0x0BBDDB66, 0xC4B286AA,
0x2BEBBC4B, 0xFC261FF4, 0x92B8E705, 0xDCEE6952, 0x5E0442E5, 0x8BEB7F21};
static const u32 g_keyMEIMG260[] = {
0x11BFD698, 0xD7F9B324, 0xDD524927, 0x16215B86, 0x504AC36D, 0x5843B217,
0xE5A0DA47, 0xBB73A1E7, 0x2915DB35, 0x375CFD3A, 0xBB70A905, 0x272BEFCA,
0x2E960791, 0xEA0799BB, 0xB85AE6C8, 0xC9CAF773, 0x250EE641, 0x06E74A9E,
0x5244895D, 0x466755A5, 0x9A84AF53, 0xE1024174, 0xEEBA031E, 0xED80B9CE,
0xBC315F72, 0x5821067F, 0xE8313058, 0xD2D0E706, 0xE6D8933E, 0xD7D17FB4,
0x505096C4, 0xFDA50B3B, 0x4635AE3D, 0xEB489C8A, 0x422D762D, 0x5A8B3231};
static const u32 g_keyDEMOS27X[] = {
0x1ABF102F, 0xD596D071, 0x6FC552B2, 0xD4F2531F, 0xF025CDD9, 0xAF9AAF03,
0xE0CF57CF, 0x255494C4, 0x7003675E, 0x907BC884, 0x002D4EE4, 0x0B687A0D,
0x9E3AA44F, 0xF58FDA81, 0xEC26AC8C, 0x3AC9B49D, 0x3471C037, 0xB0F3834D,
0x10DC4411, 0xA232EA31, 0xE2E5FA6B, 0x45594B03, 0xE43A1C87, 0x31DAD9D1,
0x08CD7003, 0xFA9C2FDF, 0x5A891D25, 0x9B5C1934, 0x22F366E5, 0x5F084A32,
0x695516D5, 0x2245BE9F, 0x4F6DD705, 0xC4B8B8A1, 0xBC13A600, 0x77B7FC3B};
static const u32 g_keyUNK1[] = {
0x33B50800, 0xF32F5FCD, 0x3C14881F, 0x6E8A2A95, 0x29FEEFD5, 0x1394EAE3,
0xBD6BD443, 0x0821C083, 0xFAB379D3, 0xE613E165, 0xF5A754D3, 0x108B2952,
0x0A4B1E15, 0x61EADEBA, 0x557565DF, 0x3B465301, 0xAE54ECC3, 0x61423309,
0x70C9FF19, 0x5B0AE5EC, 0x989DF126, 0x9D987A5F, 0x55BC750E, 0xC66EBA27,
0x2DE988E8, 0xF76600DA, 0x0382DCCB, 0x5569F5F2, 0x8E431262, 0x288FE3D3,
0x656F2187, 0x37D12E9C, 0x2F539EB4, 0xA492998E, 0xED3958F7, 0x39E96523};
static const u32 g_key_GAMESHARE1xx[] = {
0x721B53E8, 0xFC3E31C6, 0xF85BA2A2, 0x3CF0AC72, 0x54EEA7AB, 0x5959BFCB,
0x54B8836B, 0xBC431313, 0x989EF2CF, 0xF0CE36B2, 0x98BA4CF8, 0xE971C931,
0xA0375DC8, 0x08E52FA0, 0xAC0DD426, 0x57E4D601, 0xC56E61C7, 0xEF1AB98A,
0xD1D9F8F4, 0x5FE9A708, 0x3EF09D07, 0xFA0C1A8C, 0xA91EEA5C, 0x58F482C5,
0x2C800302, 0x7EE6F6C3, 0xFF6ABBBB, 0x2110D0D0, 0xD3297A88, 0x980012D3,
0xDC59C87B, 0x7FDC5792, 0xDB3F5DA6, 0xFC23B787, 0x22698ED3, 0xB680E812};
static const u32 g_key_GAMESHARE2xx[] = {
0x94A757C7, 0x9FD39833, 0xF8508371, 0x328B0B29, 0x2CBCB9DA, 0x2918B9C6,
0x944C50BA, 0xF1DCE7D0, 0x640C3966, 0xC90B3D08, 0xF4AD17BA, 0x6CA0F84B,
0xF7767C67, 0xA4D3A55A, 0x4A085C6A, 0x6BB27071, 0xFA8B38FB, 0x3FDB31B8,
0x8B7196F2, 0xDB9BED4A, 0x51625B84, 0x4C1481B4, 0xF684F508, 0x30B44770,
0x93AA8E74, 0x90C579BC, 0x246EC88D, 0x2E051202, 0xC774842E, 0xA185D997,
0x7A2B3ADD, 0xFE835B6D, 0x508F184D, 0xEB4C4F13, 0x0E1993D3, 0xBA96DFD2};
static const u32 g_key_INDEXDAT1xx[] = {
0x76CB00AF, 0x111CE62F, 0xB7B27E36, 0x6D8DE8F9, 0xD54BF16A, 0xD9E90373,
0x7599D982, 0x51F82B0E, 0x636103AD, 0x8E40BC35, 0x2F332C94, 0xF513AAE9,
0xD22AFEE9, 0x04343987, 0xFC5BB80C, 0x12349D89, 0x14A481BB, 0x25ED3AE8,
0x7D500E4F, 0x43D1B757, 0x7B59FDAD, 0x4CFBBF34, 0xC3D17436, 0xC1DA21DB,
0xA34D8C80, 0x962B235D, 0x3E420548, 0x09CF9FFE, 0xD4883F5C, 0xD90E9CB5,
0x00AEF4E9, 0xF0886DE9, 0x62A58A5B, 0x52A55546, 0x971941B5, 0xF5B79FAC};
// The blocks inside the 1.00 updater's DATA.PSAR (a version 1 archive) carry this tag. Stored raw,
// as in 1.50's mesg_led.prx, which keeps its tags and keys in a table: this one follows tag
// 0x06000000 at 0x2028 of the decrypted module.
static const u32 g_keyUPDATER_PSAR_100[] = {
0x8C121584, 0x80D783A8, 0xDB881EEF, 0x239661BC, 0xFC88F32B, 0xDEB53FE5,
0x6BA05A98, 0xDB550ADE, 0x3644F8F2, 0x5594D1EB, 0x933E394A, 0x02E33D7C,
0xF5E78812, 0xEBC1F0F8, 0xC68D1B25, 0x442B1EB8, 0x7E6AB7A5, 0x924639D0,
0x07DE716D, 0x102FB897, 0x53DDBA18, 0x982B07C6, 0x0D748A24, 0xFE5D645C,
0x4367E78E, 0xA1B39693, 0x12ECA8A1, 0x4458FBC4, 0x9C0A55FC, 0xFA37301E,
0x03D32454, 0x31BD92E2, 0xF7EA0323, 0x09DE73E7, 0x7983B33B, 0x0E8517DA};
// The blocks inside an official updater's DATA.PSAR carry this tag. See Core/Util/PSARUnpack.cpp.
static const u32 g_keyUPDATER_PSAR[] = {
0x77B757DE, 0xEE62DD17, 0x5D03787B, 0x59CA8644, 0x93F68D20, 0x21819328,
0x86A74E71, 0x1B2482CA, 0x5F74AE58, 0x568D016C, 0x9A4D8832, 0x2EA24372,
0x820CF484, 0xFCFC06B9, 0x8A5BFB6A, 0xBF9F9CD7, 0x15850D01, 0x39ED5FBA,
0x4CC38393, 0xED3ADEAF, 0x1AA768BF, 0x89BD8A77, 0x46564165, 0x7333DBD9,
0x62E86C81, 0x03299B96, 0x73AFAE5A, 0x40A05320, 0x10664BE8, 0xE5B76A99,
0x29E0DD70, 0xEA602428, 0x2042AE30, 0x946F8D32, 0xA29E5F71, 0x7C0C7FD5};
// Used by sceResmgr_9DC14891, which is how the VSH decrypts flash0:/vsh/etc/index_XXg.dat - the
// XMB item index, and the only thing on the boot path that needs these.
static const u8 keys_9DC14891_1[] = {0x39, 0xF7, 0xDF, 0x19, 0xD7, 0x10, 0xEA, 0x9F, 0x02, 0xDB, 0x3F, 0xB1, 0x10, 0x9F, 0x26, 0x6B};
static const u8 keys_9DC14891_2[] = {0x46, 0x1D, 0xC9, 0xC2, 0x1D, 0x44, 0xA6, 0x68, 0xF2, 0x06, 0x37, 0xBF, 0x62, 0xCD, 0x11, 0x9E};
static const u8 keys_9DC14891_3[] = {0x11, 0x0D, 0x1A, 0x4C, 0x8A, 0x19, 0x17, 0xDC, 0xD0, 0x5A, 0x65, 0x47, 0xA5, 0x03, 0x85, 0x22};
static const u8 keys_9DC14891_26x[] = {0xE4, 0x98, 0x8E, 0x93, 0x5B, 0x94, 0xAF, 0x19, 0xEA, 0x30, 0x6C, 0xEA, 0x6F, 0x1F, 0x11, 0x59};
static const u8 keys_9DC14891_28x[] = {0xC3, 0x1A, 0x78, 0xC5, 0xF5, 0xBE, 0xC6, 0x92, 0xF9, 0xEF, 0x94, 0xEA, 0x51, 0xE5, 0x57, 0x11};
static const u8 keys_9DC14891_30x[] = {0x96, 0x86, 0xDD, 0x78, 0x87, 0xA7, 0x2B, 0xD9, 0xDD, 0xC6, 0x6C, 0x4F, 0x89, 0xFB, 0xD4, 0xD7};
static const u8 keys_9DC14891_31x[] = {0x27, 0xE1, 0x31, 0xF5, 0xF7, 0x9B, 0xE7, 0x88, 0xD6, 0x8D, 0x7C, 0x0D, 0x99, 0x73, 0xA1, 0x8F};
static const u8 keys_9DC14891_35x[] = {0x6B, 0x3F, 0x91, 0x58, 0xED, 0x40, 0x68, 0x54, 0x93, 0xD6, 0x45, 0x3F, 0x2C, 0xD4, 0x23, 0x43};
static const u8 keys_9DC14891_50x_01g[] = {0xF8, 0x15, 0xCC, 0x79, 0x10, 0x89, 0x16, 0xD6, 0x25, 0x11, 0x00, 0xEB, 0x6B, 0xB1, 0x13, 0xE2};
static const u8 keys_9DC14891_50x_02g[] = {0x1A, 0x26, 0xFD, 0x16, 0x32, 0x0E, 0x71, 0xD0, 0xDC, 0xD1, 0x3C, 0xE5, 0x53, 0xD5, 0x44, 0x99};
static const u8 keys_9DC14891_50x_03g[] = {0x20, 0xDC, 0xEF, 0xB8, 0x0A, 0x8D, 0x43, 0x27, 0x68, 0xB9, 0xF9, 0x11, 0x38, 0x94, 0x84, 0x28};
static const u8 keys_9DC14891_60x_01g[] = {0x2F, 0xB5, 0x04, 0xEF, 0xCB, 0xC8, 0xEC, 0x82, 0x31, 0x26, 0xF7, 0x0A, 0x18, 0x6F, 0xAF, 0xC7};
static const u8 keys_9DC14891_60x_02g[] = {0xE5, 0xB6, 0xDC, 0x83, 0x94, 0xD1, 0x76, 0xEA, 0x99, 0x2D, 0x22, 0x16, 0xE8, 0x03, 0xA2, 0x03};
static const u8 keys_9DC14891_60x_03g[] = {0xEF, 0x00, 0x79, 0x32, 0xCE, 0x70, 0x71, 0x21, 0x06, 0x0C, 0xA3, 0xA0, 0x7B, 0xA8, 0x96, 0x53};
static const u8 keys_9DC14891_63x_01g[] = {0x57, 0xB4, 0xA6, 0x5C, 0x75, 0x2D, 0xB9, 0x4D, 0xE1, 0x67, 0xE3, 0x31, 0xBF, 0x4D, 0x70, 0xF8};
static const u8 keys_9DC14891_63x_02g[] = {0x29, 0x20, 0x0B, 0x22, 0xCF, 0x1F, 0xD7, 0x50, 0x64, 0xA7, 0x50, 0x20, 0xEC, 0x22, 0x6F, 0xB8};
static const u8 keys_9DC14891_63x_03g[] = {0x04, 0xB6, 0x9F, 0x92, 0x39, 0xEB, 0xE8, 0xB2, 0xCB, 0x38, 0x29, 0xF6, 0x41, 0x77, 0xFF, 0xAD};
struct TAG_INFO {
u32 tag; // 4 byte value at offset 0xD0 in the PRX file
const u32 *key; // "step1_result" use for XOR step
u8 code;
u8 codeExtra;
// Most keys here are stored already scrambled, so the kirk7 pass the hardware would do is
// skipped. Set this for a key that's stored raw and needs that pass applied first.
bool scrambleKey;
};
static const TAG_INFO g_tagInfo[] =
{
{ 0x00000000, g_key0, 0x42 },
{ 0x02000000, g_key2, 0x45 },
{ 0x03000000, g_key3, 0x46 },
{ 0x4467415d, g_key44, 0x59, 0x59 },
{ 0x207bbf2f, g_key20, 0x5A, 0x5A },
{ 0x3ace4dce, g_key3A, 0x5B, 0x5B },
{ 0x07000000, g_key_INDEXDAT1xx, 0x4A },
{ 0x08000000, g_keyEBOOT1xx, 0x4B },
{ 0xC0CB167C, g_keyEBOOT2xx, 0x5D, 0x5D },
{ 0x7F24BDCD, g_demokeys_280, 0x60, 0x60 },
{ 0x0B000000, g_keyUPDATER, 0x4E },
{ 0x0C000000, g_keyDEMOS27X, 0x4F },
{ 0x0F000000, g_keyMEIMG250, 0x52 },
{ 0x862648D1, g_keyMEIMG260, 0x52, 0x52 },
{ 0x207BBF2F, g_keyUNK1, 0x5A, 0x5A },
{ 0x09000000, g_key_GAMESHARE1xx, 0x4C },
{ 0xBB67C59F, g_key_GAMESHARE2xx, 0x5E, 0x5E },
{ 0x0E000000, g_keyUPDATER_PSAR, 0x51, 0x00, true },
{ 0x06000000, g_keyUPDATER_PSAR_100, 0x49, 0x00, true },
};
static const TAG_INFO *GetTagInfo(u32 tagFind)
{
for (u32 iTag = 0; iTag < sizeof(g_tagInfo)/sizeof(TAG_INFO); iTag++)
if (g_tagInfo[iTag].tag == tagFind)
return &g_tagInfo[iTag];
return NULL; // not found
}
////////// Decryption 2 //////////
struct TAG_INFO2
{
u32 tag; // 4 byte value at offset 0xD0 in the PRX file
const u8 *key; // 16 bytes keys
u8 code; // code for scramble
u8 type;
const u8 *seed;
};
static const TAG_INFO2 g_tagInfo2[] =
{
{ 0x0B2B90F0, keys_9DC14891_1, 0x5C },
{ 0x0B2B91F0, keys_9DC14891_2, 0x5C },
{ 0x0B2B92F0, keys_9DC14891_3, 0x5C },
{ 0x495BE403, keys_9DC14891_26x, 0x5C },
{ 0x0B2B05F0, keys_9DC14891_28x, 0x5C },
{ 0x0B2B06F0, keys_9DC14891_30x, 0x5C },
{ 0x0B2B08F0, keys_9DC14891_31x, 0x5C },
{ 0x0B2B0AF0, keys_9DC14891_35x, 0x5C },
{ 0x0B2B0BF0, keys_9DC14891_50x_01g, 0x5C },
{ 0x0B2B11F0, keys_9DC14891_50x_02g, 0x5C },
{ 0x0B2B1EF0, keys_9DC14891_50x_03g, 0x5C },
{ 0x0B2B0CF0, keys_9DC14891_60x_01g, 0x5C },
{ 0x0B2B12F0, keys_9DC14891_60x_02g, 0x5C },
{ 0x0B2B1FF0, keys_9DC14891_60x_03g, 0x5C },
{ 0x0B2B80F0, keys_9DC14891_63x_01g, 0x5C },
{ 0x0B2B81F0, keys_9DC14891_63x_02g, 0x5C },
{ 0x0B2B82F0, keys_9DC14891_63x_03g, 0x5C },
{ 0x4C9494F0, keys660_k1, 0x43 },
{ 0x4C9495F0, keys660_k2, 0x43 },
{ 0x4C9490F0, keys660_k3, 0x43 },
{ 0x4C9491F0, keys660_k8, 0x43 },
{ 0x4C9493F0, keys660_k4, 0x43 },
{ 0x4C9497F0, keys660_k5, 0x43 },
{ 0x4C9492F0, keys660_k6, 0x43 },
{ 0x4C9496F0, keys660_k7, 0x43 },
{ 0x457B90F0, keys660_v1, 0x5B },
{ 0x457B91F0, keys660_v7, 0x5B },
{ 0x457B92F0, keys660_v6, 0x5B },
{ 0x457B93F0, keys660_v3, 0x5B },
{ 0x380290F0, keys660_v2, 0x5A },
{ 0x380291F0, keys660_v8, 0x5A },
{ 0x380292F0, keys660_v4, 0x5A },
{ 0x380293F0, keys660_v5, 0x5A },
{ 0x4C948CF0, keys639_k3, 0x43 },
{ 0x4C948DF0, keys638_k4, 0x43 },
{ 0x4C948BF0, keys636_k2, 0x43 },
{ 0x4C948AF0, keys636_k1, 0x43 },
{ 0x4C9487F0, keys630_k8, 0x43 },
{ 0x457B83F0, keys630_k7, 0x5B },
{ 0x4C9486F0, keys630_k6, 0x43 },
{ 0x457B82F0, keys630_k5, 0x5B },
{ 0x457B81F0, keys630_k4, 0x5B },
{ 0x4C9485F0, keys630_k3, 0x43 },
{ 0x457B80F0, keys630_k2, 0x5B },
{ 0x4C9484F0, keys630_k1, 0x43 },
{ 0x457B28F0, keys620_e, 0x5B },
{ 0x457B0CF0, keys620_a, 0x5B },
{ 0x380228F0, keys620_5v, 0x5A },
{ 0x4C942AF0, keys620_5k, 0x43 },
{ 0x4C9428F0, keys620_5, 0x43 },
{ 0x4C941DF0, keys620_1, 0x43 },
{ 0x4C941CF0, keys620_0, 0x43 },
{ 0x4C9422F0, keys600_2, 0x43 },
{ 0x4C941EF0, keys600_1, 0x43 },
{ 0x4C9429F0, keys570_5k, 0x43 },
{ 0x4C941BF0, keys555_k2, 0x43 },
{ 0x4C941AF0, keys555_k1, 0x43 },
{ 0x457B0BF0, keys505_a, 0x5B },
{ 0x4C9419F0, keys505_1, 0x43 },
{ 0x4C9418F0, keys505_0, 0x43 },
{ 0x457B1EF0, keys500_c, 0x5B },
{ 0x4C941FF0, keys500_2, 0x43 },
{ 0x4C9417F0, keys500_1, 0x43 },
{ 0x4C9416F0, keys500_0, 0x43 },
{ 0x4C9414F0, keys390_0, 0x43 },
{ 0x4C9415F0, keys390_1, 0x43 },
{ 0x4C9412F0, keys370_0, 0x43 },
{ 0x4C9413F0, keys370_1, 0x43 },
{ 0x457B10F0, keys370_2, 0x5B },
{ 0x4C940DF0, keys360_0, 0x43 },
{ 0x4C9410F0, keys360_1, 0x43 },
{ 0x4C940BF0, keys330_0, 0x43 },
{ 0x457B0AF0, keys330_1, 0x5B },
{ 0x38020AF0, keys330_2, 0x5A },
{ 0x4C940AF0, keys330_3, 0x43 },
{ 0x4C940CF0, keys330_4, 0x43 },
{ 0xcfef09f0, keys310_0, 0x62 },
{ 0x457b08f0, keys310_1, 0x5B },
{ 0x380208F0, keys310_2, 0x5A },
{ 0xcfef08f0, keys310_3, 0x62 },
{ 0xCFEF07F0, keys303_0, 0x62 },
{ 0xCFEF06F0, keys300_0, 0x62 },
{ 0x457B06F0, keys300_1, 0x5B },
{ 0x380206F0, keys300_2, 0x5A },
{ 0xCFEF05F0, keys280_0, 0x62 },
{ 0x457B05F0, keys280_1, 0x5B },
{ 0x380205F0, keys280_2, 0x5A },
{ 0x16D59E03, keys260_0, 0x62 },
{ 0x76202403, keys260_1, 0x5B },
{ 0x0F037303, keys260_2, 0x5A },
{ 0x4C940FF0, key_2DA8, 0x43 },
{ 0x4467415D, key_22E0, 0x59 },
{ 0x00000000, key_21C0, 0x42 },
{ 0x01000000, key_2250, 0x43 },
{ 0x2E5E0AF0, key_2E5E0AF0, 0x48, 5, xor_2E5E0AF0 }, //[32]6.20
{ 0x2E5E10F0, key_2E5E10F0, 0x48, 5, xor_2E5E10F0 }, //[32]5.00 PSP-2000 (Game PSN Update 2 LBP)
{ 0x2E5E11F0, key_2E5E11F0, 0x48, 5, xor_2E5E11F0 }, //[32]6.00
{ 0x2E5E12F0, key_2E5E12F0, 0x48, 5, xor_2E5E12F0 },
{ 0x2E5E13F0, key_2E5E13F0, 0x48, 5, xor_2E5E13F0 },
{ 0x2E5E80F0, key_2E5E80F0, 0x48, 5, xor_2E5E80F0 }, //[32]6.30
{ 0x2E5E90F0, key_2E5E90F0, 0x48, 5, xor_2E5E90F0 },
{ 0x2FD30BF0, key_2FD30BF0, 0x47 },
{ 0xD91605F0, key_D91605F0, 0x5D, 2},
{ 0xD91606F0, key_D91606F0, 0x5D, 2},
{ 0xD91608F0, key_D91608F0, 0x5D, 2},
{ 0xD91609F0, key_D91609F0, 0x5D, 2},
{ 0xD9160AF0, key_D9160AF0, 0x5D, 2},
{ 0xD9160BF0, key_D9160BF0, 0x5D, 2},
{ 0xD91611F0, key_D91611F0, 0x5D, 2},
{ 0xD91612F0, key_D91612F0, 0x5D, 2},
{ 0xD91613F0, key_D91613F0, 0x5D, 2},
{ 0xD91614F0, key_D91614F0, 0x5D, 2},
{ 0xD91615F0, key_D91615F0, 0x5D, 2},
{ 0xD91616F0, key_D91616F0, 0x5D, 2},
{ 0xD91617F0, key_D91617F0, 0x5D, 2},
{ 0xD91618F0, key_D91618F0, 0x5D, 2},
{ 0xD91619F0, key_D91619F0, 0x5D, 2},
{ 0xD9161AF0, key_D9161AF0, 0x5D, 2},
{ 0xD91620F0, key_D91620F0, 0x5D, 2},
{ 0xD91621F0, key_D91621F0, 0x5D, 2},
{ 0xD91622F0, key_D91622F0, 0x5D, 2},
{ 0xD91623F0, key_D91623F0, 0x5D, 2},
{ 0xD91624F0, key_D91624F0, 0x5D, 2},
{ 0xD91628F0, key_D91628F0, 0x5D, 2},
{ 0xD91680F0, key_D91680F0, 0x5D, 6},
{ 0xD91681F0, key_D91681F0, 0x5D, 6},
{ 0xD82310F0, keys02G_E, 0x51 },
{ 0xD8231EF0, keys03G_E, 0x51 },
{ 0xD82328F0, keys05G_E, 0x51 },
{ 0x279D08F0, oneseg_310, 0x61 },
{ 0x279D06F0, oneseg_300, 0x61 },
{ 0x279D05F0, oneseg_280, 0x61 },
{ 0xD66DF703, oneseg_260_271, 0x61 },
{ 0x279D10F0, oneseg_slim, 0x61 },
{ 0x3C2A08F0, ms_app_main, 0x67 },
{ 0xADF305F0, demokeys_280, 0x60 },
{ 0xADF306F0, demokeys_3XX_1, 0x60 },
{ 0xADF308F0, demokeys_3XX_2, 0x60 },
{ 0x8004FD03, ebootbin_271_new, 0x5D, 2 },
{ 0x0A35EA03, gameshare_260_271, 0x5E },
{ 0x7B0505F0, gameshare_280, 0x5E },
{ 0x7B0506F0, gameshare_300, 0x5E },
{ 0x7B0508F0, gameshare_310, 0x5E },
{ 0x380210F0, key_380210F0, 0x5A },
{ 0x380280F0, key_380280F0, 0x5A },
{ 0x380283F0, key_380283F0, 0x5A },
{ 0x407810F0, key_407810F0, 0x6A },
{ 0xE92410F0, drmkeys_6XX_1, 0x40 },
{ 0x692810F0, drmkeys_6XX_2, 0x40 },
{ 0x2FD313F0, pauth_98b83b5d_1, 0x47, 5, pauth_98b83b5d_xor },
{ 0x2FD312F0, pauth_f7aa47f6_1, 0x47, 5, pauth_f7aa47f6_xor },
{ 0x2FD311F0, pauth_f7aa47f6_2, 0x47, 5, pauth_f7aa47f6_xor },
{ 0x89742B04, key_89742B04, 0x65, 3, xor_89742B04 },
};
static const TAG_INFO2 *GetTagInfo2(u32 tagFind)
{
for (u32 iTag = 0; iTag < sizeof(g_tagInfo2) / sizeof(TAG_INFO2); iTag++)
{
if (g_tagInfo2[iTag].tag == tagFind)
{
return &g_tagInfo2[iTag];
}
}
return NULL; // not found
}
static std::array<u8, 0x90> expandSeed(const u8 *seed, int key, const u8 *bonusSeed = nullptr)
{
std::array<u8, 0x90> expandedSeed;
// perform some AES-CTR like encryption of seed
for (auto i = 0u; i < expandedSeed.size(); i += 0x10)
{
memcpy(expandedSeed.data()+i, seed, 0x10);
expandedSeed[i] = i/0x10;
}
kirk7(expandedSeed.data(), expandedSeed.data(), expandedSeed.size(), key);
if (bonusSeed)
{
for (auto i = 0u; i < expandedSeed.size(); ++i)
{
expandedSeed[i] ^= bonusSeed[i % 0x10];
}
}
return expandedSeed;
}
template<typename It>
static void decryptKirkHeader(u8 *outbuf, const u8 *inbuf, It xorbuf, int key)
{
for (auto i = 0; i < 0x40; ++i)
{
outbuf[i] = inbuf[i] ^ *xorbuf++;
}
kirk7(outbuf, outbuf, 0x40, key);
for (auto i = 0; i < 0x40; ++i)
{
outbuf[i] = outbuf[i] ^ *xorbuf++;
}
}
template <typename T>
static void decryptKirkHeaderType0(u8 *outbuf, const u8 *inbuf, T xorbuf, int key)
{
for (auto i = 0; i < 0x70; ++i)
{
outbuf[i] = inbuf[i] ^ xorbuf[i+0x14];
}
kirk7(outbuf, outbuf, 0x70, key);
for (auto i = 0; i < 0x70; ++i)
{
outbuf[i] = outbuf[i] ^ xorbuf[i+0x20];
}
}
struct PRXType0
{
explicit PRXType0(const u8 *prx)
{
memcpy(tag, prx+0xD0, sizeof(tag));
memcpy(sha1, prx+0xD4, sizeof(sha1));
memcpy(unused, prx+0xE8, sizeof(unused));
memcpy(kirkBlock, prx+0x110, 0x40); // key data
memcpy(kirkBlock+0x40, prx+0x80, sizeof(kirkBlock)-0x40);
memcpy(prxHeader, prx, sizeof(prxHeader));
}
u8 tag[4];
u8 sha1[0x14];
u8 unused[0x28];
u8 kirkBlock[0x90];
u8 prxHeader[0x80];
};
static_assert(sizeof(PRXType0) == 0x150, "inconsistent size of PRX Type 0");
struct PRXType1
{
explicit PRXType1(const u8 *prx)
{
memcpy(tag, prx+0xD0, sizeof(tag));
memcpy(sha1, prx+0xD4, sizeof(sha1));
memcpy(unused, prx+0xE8, sizeof(unused));
memcpy(kirkBlock, prx+0x110, 0x40); // key data
memcpy(kirkBlock+0x40, prx+0x80, sizeof(kirkBlock)-0x40);
memcpy(prxHeader, prx, sizeof(prxHeader));
}
void decrypt(int key)
{
kirk7(sha1+0xC, sha1+0xC, 0xA0, key);
}
u8 tag[4];
u8 sha1[0x14];
u8 unused[0x28];
u8 kirkBlock[0x90];
u8 prxHeader[0x80];
};
static_assert(sizeof(PRXType1) == 0x150, "inconsistent size of PRX Type 1");
struct PRXType2
{
explicit PRXType2(const u8 *prx)
{
memcpy(tag, prx+0xD0, sizeof(tag));
memset(empty, 0, sizeof(empty));
memcpy(id, prx+0x140, sizeof(id));
memcpy(sha1, prx+0x12C, sizeof(sha1));
// kirk header is split between 0x80->0xB0 and 0xC0->0xD0
memcpy(kirkHeader, prx+0x80, sizeof(kirkHeader)-0x10);
memcpy(kirkHeader+0x30, prx+0xC0, 0x10);
memcpy(kirkMetadata, prx+0xB0, sizeof(kirkMetadata));
memcpy(prxHeader, prx, sizeof(prxHeader));
}
void decrypt(int key)
{
kirk7(id, id, 0x60, key);
}
u8 tag[4];
u8 empty[0x58];
u8 id[0x10];
u8 sha1[0x14];
u8 kirkHeader[0x40];
u8 kirkMetadata[0x10];
u8 prxHeader[0x80];
};
static_assert(sizeof(PRXType2) == 0x150, "inconsistent size of PRX Type 2");
struct PRXType5
{
explicit PRXType5(const u8 *prx)
{
memcpy(tag, prx+0xD0, sizeof(tag));
memset(empty, 0, sizeof(empty));
memcpy(id, prx+0x140, sizeof(id));
memcpy(sha1, prx+0x12C, sizeof(sha1));
// kirk header is split between 0x80->0xB0 and 0xC0->0xD0
memcpy(kirkHeader, prx+0x80, sizeof(kirkHeader)-0x10);
memcpy(kirkHeader+0x30, prx+0xC0, 0x10);
memcpy(kirkMetadata, prx+0xB0, sizeof(kirkMetadata));
memcpy(prxHeader, prx, sizeof(prxHeader));
}
// cmd3Header is for type 3, a game patch installed on a PSP: the first step goes through KIRK
// command 3 there, keyed to the console, with the header the install put at 0xEC (JPCSP's
// PRX.DecryptPRX). Fails only if that command does. kirk2Result instead is what that step gives
// (see g_kirk2Results).
bool decrypt(int key, const u8 *xor1, const u8 *xor2, KirkState *kirk = nullptr, const u8 *cmd3Header = nullptr, const u8 *kirk2Result = nullptr)
{
// first step is to decrypt kirk header + SHA1
u8 data[0x50];
memcpy(data, kirkHeader, sizeof(kirkHeader));
memcpy(data+sizeof(kirkHeader), sha1, 0x10);
for (auto i = 0; i < 0x50; ++i)
{
if (xor1)
{
data[i] ^= xor1[i % 0x10];
}
if (xor2)
{
data[i] ^= xor2[i % 0x10];
}
}
if (kirk2Result)
{
memcpy(data, kirk2Result, sizeof(data));
}
else if (cmd3Header)
{
u8 block[sizeof(KIRK_CMD1_HEADER) + sizeof(data)]{};
KIRK_CMD1_HEADER *header = reinterpret_cast<KIRK_CMD1_HEADER *>(block);
memcpy(block, cmd3Header, 0x40);
header->mode = KIRK_MODE_CMD3;
header->data_size = sizeof(data);
memcpy(block + sizeof(KIRK_CMD1_HEADER), data, sizeof(data));
if (kirk_CMD3(kirk, data, block, sizeof(block)) != KIRK_OPERATION_SUCCESS)
{
return false;
}
}
else
{
kirk7(data, data, 0x50, key);
}
// copy the result back
memcpy(kirkHeader, data, sizeof(kirkHeader));
memcpy(sha1, data+sizeof(kirkHeader), 0x10);
// second step is a XOR then decrypt id through to kirk header
if (xor1)
{
u8 *p = id;
for (auto i = 0; i < 0x60; ++i)
{
p[i] ^= xor1[i % 0x10];
}
}
kirk7(id, id, 0x60, key);
return true;
}
u8 tag[4];
u8 empty[0x58];
u8 id[0x10];
u8 sha1[0x14];
u8 kirkHeader[0x40];
u8 kirkMetadata[0x10];
u8 prxHeader[0x80];
};
static_assert(sizeof(PRXType5) == 0x150, "inconsistent size of PRX Type 5");
struct PRXType6
{
explicit PRXType6(const u8 *prx)
{
memcpy(tag, prx+0xD0, sizeof(tag));
memset(empty, 0, sizeof(empty));
memcpy(ecdsaSignatureTail, prx+0x10C, sizeof(ecdsaSignatureTail));
memcpy(id, prx+0x140, sizeof(id));
memcpy(sha1, prx+0x12C, sizeof(sha1));
// kirk header is split between 0x80->0xB0 and 0xC0->0xD0
memcpy(kirkHeader, prx+0x80, sizeof(kirkHeader)-0x10);
memcpy(kirkHeader+0x30, prx+0xC0, 0x10);
memcpy(kirkMetadata, prx+0xB0, sizeof(kirkMetadata));
memcpy(prxHeader, prx, sizeof(prxHeader));
}
void decrypt(int key)
{
kirk7(id, id, 0x60, key);
}
u8 tag[4];
u8 empty[0x38];
u8 ecdsaSignatureTail[0x20];
u8 id[0x10];
u8 sha1[0x14];
u8 kirkHeader[0x40];
u8 kirkMetadata[0x10];
u8 prxHeader[0x80];
};
static_assert(sizeof(PRXType6) == 0x150, "inconsistent size of PRX Type 6");
// Same 0x150-byte layout as type 6 - the difference is that a type 9 PRX carries an ECDSA
// signature at 0x104..0x12C where type 6 has nothing, and the signature is *excluded* from the
// hash rather than fed into it. JPCSP zeroes buf2[0x34..0x5C), which is that same range once the
// header has been rearranged, so here the corresponding field is simply left zero.
struct PRXType9
{
explicit PRXType9(const u8 *prx)
{
memcpy(tag, prx+0xD0, sizeof(tag));
memset(empty, 0, sizeof(empty));
// Not copied from prx+0x10C, unlike type 6: it holds the tail of the signature.
memset(ecdsaSignatureTail, 0, sizeof(ecdsaSignatureTail));
memcpy(id, prx+0x140, sizeof(id));
memcpy(sha1, prx+0x12C, sizeof(sha1));
// kirk header is split between 0x80->0xB0 and 0xC0->0xD0
memcpy(kirkHeader, prx+0x80, sizeof(kirkHeader)-0x10);
memcpy(kirkHeader+0x30, prx+0xC0, 0x10);
memcpy(kirkMetadata, prx+0xB0, sizeof(kirkMetadata));
memcpy(prxHeader, prx, sizeof(prxHeader));
}
void decrypt(int key)
{
kirk7(id, id, 0x60, key);
}
u8 tag[4];
u8 empty[0x38];
u8 ecdsaSignatureTail[0x20];
u8 id[0x10];
u8 sha1[0x14];
u8 kirkHeader[0x40];
u8 kirkMetadata[0x10];
u8 prxHeader[0x80];
};
static_assert(sizeof(PRXType9) == 0x150, "inconsistent size of PRX Type 9");
// KIRK CMD1 writes the header plus data_offset plus data_size (rounded up to 16) bytes into
// outbuf, and all three of those come out of the header we just decrypted rather than from the
// caller. The SHA1 check above doesn't bound them - it only covers the header, so it passes just
// as happily for a block that's been cut short as for a whole one. A caller that has to guess how
// long a block is (the PSAR walker does: an updater doesn't record the length of its second
// block, so it tries the sizes real updaters use) then hands us a size that's too small and KIRK
// runs off the end of the buffer. That showed up as an intermittent crash unpacking any official
// updater, since whether the overrun lands on an unmapped page depends on the heap layout.
static bool KirkOutputFits(u32 headerSize, u32 dataOffset, u32 dataSize, u32 size) {
const u64 alignedDataSize = ((u64)dataSize + 15) & ~(u64)15;
return (u64)headerSize + (u64)dataOffset + alignedDataSize <= (u64)size;
}
static bool KirkOutputFits(const KIRK_CMD1_HEADER *header, u32 size) {
return KirkOutputFits(sizeof(KIRK_CMD1_HEADER), header->data_offset, header->data_size, size);
}
static bool KirkOutputFits(const KIRK_CMD1_ECDSA_HEADER *header, u32 size) {
return KirkOutputFits(sizeof(KIRK_CMD1_ECDSA_HEADER), header->data_offset, header->data_size, size);
}
static int pspDecryptType0(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 size) {
DEBUG_LOG(Log::Loader, "Decrypting tag %02X", (u32)*(u32_le *)&inbuf[0xD0]);
const auto decryptSize = *(s32_le*)&inbuf[0xB0];
const auto pti = GetTagInfo((u32)*(u32_le *)&inbuf[0xD0]);
if (!pti)
{
return -1;
}
// no need to expand seed, and no need to decrypt
// normally this would be a kirk7 op, but we have the seed pre-decrypted
std::array<u8, 0x90> xorbuf;
memcpy(xorbuf.data(), reinterpret_cast<const u8 *>(pti->key), xorbuf.size());
if (pti->scrambleKey)
{
// ...except for keys we have in their raw form, which need that pass after all.
kirk7(xorbuf.data(), xorbuf.data(), xorbuf.size(), pti->code);
}
// construct the header format for a type 0 prx
PRXType0 type0(inbuf);
SHA_CTX ctx;
SHAInit(&ctx);
SHAUpdate(&ctx, xorbuf.data(), 0x14);
SHAUpdate(&ctx, type0.unused, sizeof(type0.unused));
SHAUpdate(&ctx, type0.kirkBlock, sizeof(type0.kirkBlock));
SHAUpdate(&ctx, type0.prxHeader, sizeof(type0.prxHeader));
u8 sha1[0x14];
SHAFinal(sha1, &ctx);
if (memcmp(sha1, type0.sha1, sizeof(sha1)) != 0)
{
return -3;
}
constexpr auto offset = sizeof(PSP_Header)-sizeof(KIRK_CMD1_HEADER)-sizeof(type0.prxHeader);
KIRK_CMD1_HEADER *header = reinterpret_cast<KIRK_CMD1_HEADER *>(outbuf+offset);
if (outbuf != inbuf)
{
memcpy(outbuf, inbuf, size);
}
memcpy(header, type0.kirkBlock, sizeof(KIRK_CMD1_HEADER));
memcpy(reinterpret_cast<u8*>(header)+sizeof(KIRK_CMD1_HEADER), type0.prxHeader, sizeof(type0.prxHeader));
decryptKirkHeaderType0(reinterpret_cast<u8*>(header), type0.kirkBlock, xorbuf, pti->code);
if (!KirkOutputFits(header, size))
{
return -5;
}
if (kirk_sceUtilsBufferCopyWithRange(kirk, outbuf, size, reinterpret_cast<u8*>(header), size - offset, KIRK_CMD_DECRYPT_PRIVATE) != 0)
{
return -4;
}
return decryptSize;
}
static int pspDecryptType1(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 size)
{
INFO_LOG(Log::Loader, "Decrypting tag %02X", (u32)*(u32_le *)&inbuf[0xD0]);
const auto decryptSize = *(s32_le*)&inbuf[0xB0];
const auto pti = GetTagInfo((u32)*(u32_le *)&inbuf[0xD0]);
if (!pti)
{
return -1;
}
// no need to expand seed, and no need to decrypt
// normally this would be a kirk7 op, but we have the seed pre-decrypted
std::array<u8, 0x90> xorbuf;
memcpy(xorbuf.data(), reinterpret_cast<const u8 *>(pti->key), xorbuf.size());
// construct the header format for a type 1 prx
PRXType1 type1(inbuf);
type1.decrypt(pti->code);
SHA_CTX ctx;
SHAInit(&ctx);
SHAUpdate(&ctx, xorbuf.data(), 0x14);
SHAUpdate(&ctx, type1.unused, sizeof(type1.unused));
SHAUpdate(&ctx, type1.kirkBlock, sizeof(type1.kirkBlock));
SHAUpdate(&ctx, type1.prxHeader, sizeof(type1.prxHeader));
u8 sha1[0x14];
SHAFinal(sha1, &ctx);
if (memcmp(sha1, type1.sha1, sizeof(sha1)) != 0)
{
return -3;
}
constexpr auto offset = sizeof(PSP_Header)-sizeof(KIRK_CMD1_HEADER)-sizeof(type1.prxHeader);
KIRK_CMD1_HEADER *header = reinterpret_cast<KIRK_CMD1_HEADER *>(outbuf+offset);
if (outbuf != inbuf)
{
memcpy(outbuf, inbuf, size);
}
memcpy(header, type1.kirkBlock, sizeof(KIRK_CMD1_HEADER));
memcpy(reinterpret_cast<u8*>(header)+sizeof(KIRK_CMD1_HEADER), type1.prxHeader, sizeof(type1.prxHeader));
decryptKirkHeaderType0(reinterpret_cast<u8*>(header), type1.kirkBlock, xorbuf, pti->code);
if (!KirkOutputFits(header, size))
{
return -5;
}
if (kirk_sceUtilsBufferCopyWithRange(kirk, outbuf, size, reinterpret_cast<u8*>(header), size - offset, KIRK_CMD_DECRYPT_PRIVATE) != 0)
{
return -4;
}
return decryptSize;
}
static int pspDecryptType2(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 size)
{
INFO_LOG(Log::Loader, "Decrypting tag %02X", (u32)*(u32_le *)&inbuf[0xD0]);
const auto decryptSize = *(s32_le*)&inbuf[0xB0];
const auto pti = GetTagInfo2((u32)*(u32_le *)&inbuf[0xD0]);
if (!pti)
{
return -1;
}
// check if range is non-zero
if (std::any_of(inbuf+0xD4, inbuf+0xD4+0x58, [](u8 x) { return x != 0; }))
{
return -2;
}
// expand the seed into a xor buffer
auto xorbuf = expandSeed(pti->key, pti->code);
// construct the header format for a type 2 prx
PRXType2 type2(inbuf);
type2.decrypt(pti->code);
SHA_CTX ctx;
SHAInit(&ctx);
SHAUpdate(&ctx, type2.tag, sizeof(type2.tag));
SHAUpdate(&ctx, xorbuf.data(), 0x10);
SHAUpdate(&ctx, type2.empty, sizeof(type2.empty));
SHAUpdate(&ctx, type2.id, sizeof(type2.id));
SHAUpdate(&ctx, type2.kirkHeader, sizeof(type2.kirkHeader));
SHAUpdate(&ctx, type2.kirkMetadata, sizeof(type2.kirkMetadata));
SHAUpdate(&ctx, type2.prxHeader, sizeof(type2.prxHeader));
u8 sha1[0x14];
SHAFinal(sha1, &ctx);
if (memcmp(sha1, type2.sha1, sizeof(sha1)) != 0)
{
return -3;
}
constexpr auto offset = sizeof(PSP_Header)-sizeof(KIRK_CMD1_HEADER)-sizeof(type2.prxHeader);
KIRK_CMD1_HEADER *header = reinterpret_cast<KIRK_CMD1_HEADER *>(outbuf+offset);
if (outbuf != inbuf)
{
memcpy(outbuf, inbuf, size);
}
memset(header, 0, sizeof(KIRK_CMD1_HEADER));
memcpy(reinterpret_cast<u8*>(&header->data_size), type2.kirkMetadata, sizeof(type2.kirkMetadata));
memcpy(reinterpret_cast<u8*>(header)+sizeof(KIRK_CMD1_HEADER), type2.prxHeader, sizeof(type2.prxHeader));
decryptKirkHeader(reinterpret_cast<u8*>(header), type2.kirkHeader, xorbuf.cbegin()+0x10, pti->code);
header->mode = 1;
if (!KirkOutputFits(header, size))
{
return -5;
}
if (kirk_sceUtilsBufferCopyWithRange(kirk, outbuf, size, reinterpret_cast<u8*>(header), size - offset, KIRK_CMD_DECRYPT_PRIVATE) != 0)
{
return -4;
}
return decryptSize;
}
// The part of type 5 decryption after its kirk header and SHA1 have been decrypted, which type 3
// shares.
static int pspFinishType5(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 size, PRXType5 &type5, const std::array<u8, 0x90> &xorbuf, int code)
{
const auto decryptSize = *(s32_le*)&inbuf[0xB0];
SHA_CTX ctx;
SHAInit(&ctx);
SHAUpdate(&ctx, type5.tag, sizeof(type5.tag));
SHAUpdate(&ctx, xorbuf.data(), 0x10);
SHAUpdate(&ctx, type5.empty, sizeof(type5.empty));
SHAUpdate(&ctx, type5.id, sizeof(type5.id));
SHAUpdate(&ctx, type5.kirkHeader, sizeof(type5.kirkHeader));
SHAUpdate(&ctx, type5.kirkMetadata, sizeof(type5.kirkMetadata));
SHAUpdate(&ctx, type5.prxHeader, sizeof(type5.prxHeader));
u8 sha1[0x14];
SHAFinal(sha1, &ctx);
if (memcmp(sha1, type5.sha1, sizeof(sha1)) != 0)
{
return -3;
}
constexpr auto offset = sizeof(PSP_Header)-sizeof(KIRK_CMD1_HEADER)-sizeof(type5.prxHeader);
KIRK_CMD1_HEADER *header = reinterpret_cast<KIRK_CMD1_HEADER *>(outbuf+offset);
if (outbuf != inbuf)
{
memcpy(outbuf, inbuf, size);
}
memset(header, 0, sizeof(KIRK_CMD1_HEADER));
memcpy(reinterpret_cast<u8*>(&header->data_size), type5.kirkMetadata, sizeof(type5.kirkMetadata));
memcpy(reinterpret_cast<u8*>(header)+sizeof(KIRK_CMD1_HEADER), type5.prxHeader, sizeof(type5.prxHeader));
decryptKirkHeader(reinterpret_cast<u8*>(header), type5.kirkHeader, xorbuf.cbegin()+0x10, code);
header->mode = 1;
if (!KirkOutputFits(header, size))
{
return -5;
}
if (kirk_sceUtilsBufferCopyWithRange(kirk, outbuf, size, reinterpret_cast<u8*>(header), size - offset, KIRK_CMD_DECRYPT_PRIVATE) != 0)
{
return -4;
}
return decryptSize;
}
static int pspDecryptType5(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 size, const u8 *seed)
{
INFO_LOG(Log::Loader, "Decrypting tag %02X", (u32)*(u32_le *)&inbuf[0xD0]);
const auto pti = GetTagInfo2((u32)*(u32_le *)&inbuf[0xD0]);
if (!pti)
{
return -1;
}
// check if range is non-zero
if (std::any_of(inbuf+0xD4+1, inbuf+0xD4+0x58, [](u8 x) { return x != 0; }))
{
return -2;
}
// expand the seed into a xor buffer
auto xorbuf = expandSeed(pti->key, pti->code, seed);
// The XOR the decrypt_mode implies wins over the tag table's, which is only the fallback -
// same precedence as JPCSP, and it leaves every tag that has a seed of its own alone.
const u8 *xor1 = inbuf[0x7C] == PRX_DECRYPT_MODE_SPRX ? xor_91E0A9AD : pti->seed;
// construct the header format for a type 2 prx
PRXType5 type5(inbuf);
type5.decrypt(pti->code, xor1, seed);
return pspFinishType5(kirk, inbuf, outbuf, size, type5, xorbuf, pti->code);
}
// Decrypt mode 18: a game patch installed through the DRM BB install utility, like the one
// Killzone: Liberation's DLC installs. Type 5 with the first step through KIRK command 3, so it only
// decrypts with the Fuse ID of the PSP that did the install. The secure install ID the game passes
// to sceKernelLoadModuleDNAS is the second XOR. JPCSP's PRX.DecryptPRX, uofw's mesgled.h.
static int pspDecryptType3(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 size, const u8 *secureId)
{
const auto pti = GetTagInfo2((u32)*(u32_le *)&inbuf[0xD0]);
if (!pti || pti->type != 3 || !secureId)
{
return -1;
}
INFO_LOG(Log::Loader, "Decrypting tag %08X", (u32)*(u32_le *)&inbuf[0xD0]);
auto xorbuf = expandSeed(pti->key, pti->code, secureId);
PRXType5 type5(inbuf);
// Either still as the package has it, if we know command 2's result, or installed on a PSP.
const u8 *kirk2Result = FindKirk2Result(inbuf + 0xEC);
if (!type5.decrypt(pti->code, pti->seed, secureId, kirk, inbuf + 0xEC, kirk2Result))
{
return -6;
}
return pspFinishType5(kirk, inbuf, outbuf, size, type5, xorbuf, pti->code);
}
static int pspDecryptType6(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 size)
{
INFO_LOG(Log::Loader, "Decrypting tag %02X", (u32)*(u32_le *)&inbuf[0xD0]);
const auto decryptSize = *(s32_le*)&inbuf[0xB0];
const auto pti = GetTagInfo2((u32)*(u32_le *)&inbuf[0xD0]);
if (!pti)
{
return -1;
}
// check if range is non-zero
if (std::any_of(inbuf+0xD4, inbuf+0xD4+0x38, [](u8 x) { return x != 0; }))
{
return -2;
}
// expand the seed into a xor buffer
auto xorbuf = expandSeed(pti->key, pti->code);
// construct the header format for a type 2 prx
PRXType6 type6(inbuf);
type6.decrypt(pti->code);
SHA_CTX ctx;
SHAInit(&ctx);
SHAUpdate(&ctx, type6.tag, sizeof(type6.tag));
SHAUpdate(&ctx, xorbuf.data(), 0x10);
SHAUpdate(&ctx, type6.empty, sizeof(type6.empty));
SHAUpdate(&ctx, type6.ecdsaSignatureTail, sizeof(type6.ecdsaSignatureTail));
SHAUpdate(&ctx, type6.id, sizeof(type6.id));
SHAUpdate(&ctx, type6.kirkHeader, sizeof(type6.kirkHeader));
SHAUpdate(&ctx, type6.kirkMetadata, sizeof(type6.kirkMetadata));
SHAUpdate(&ctx, type6.prxHeader, sizeof(type6.prxHeader));
u8 sha1[0x14];
SHAFinal(sha1, &ctx);
if (memcmp(sha1, type6.sha1, sizeof(sha1)) != 0)
{
return -3;
}
constexpr auto offset = sizeof(PSP_Header)-sizeof(KIRK_CMD1_ECDSA_HEADER)-sizeof(type6.prxHeader);
KIRK_CMD1_ECDSA_HEADER *header = reinterpret_cast<KIRK_CMD1_ECDSA_HEADER *>(outbuf+offset);
if (outbuf != inbuf)
{
memcpy(outbuf, inbuf, size);
}
memset(header, 0, sizeof(KIRK_CMD1_ECDSA_HEADER));
memcpy(outbuf+offset+0x40, type6.ecdsaSignatureTail, sizeof(type6.ecdsaSignatureTail));
memcpy(reinterpret_cast<u8*>(&header->data_size), type6.kirkMetadata, sizeof(type6.kirkMetadata));
memcpy(reinterpret_cast<u8*>(header)+sizeof(KIRK_CMD1_ECDSA_HEADER), type6.prxHeader, sizeof(type6.prxHeader));
decryptKirkHeader(reinterpret_cast<u8*>(header), type6.kirkHeader, xorbuf.cbegin()+0x10, pti->code);
header->mode = 1;
header->ecdsa_hash = 1;
if (!KirkOutputFits(header, size))
{
return -5;
}
if (kirk_sceUtilsBufferCopyWithRange(kirk, outbuf, size, reinterpret_cast<u8*>(header), size - offset, KIRK_CMD_DECRYPT_PRIVATE) != 0)
{
return -4;
}
return decryptSize;
}
// Used by sceResmgr for flash0:/vsh/etc/index_XXg.dat. Identical to type 6 except for the ECDSA
// signature at 0x104..0x12C - so the "must be empty" check stops short of it, the signature is
// left out of the hash (see PRXType9), and it is not written back over the output header the way
// type 6 writes its tail there.
static int pspDecryptType9(KirkState *kirk, const u8 *inbuf, u8 *outbuf, u32 size)
{
INFO_LOG(Log::Loader, "Decrypting tag %02X", (u32)*(u32_le *)&inbuf[0xD0]);
const auto decryptSize = *(s32_le*)&inbuf[0xB0];
const auto pti = GetTagInfo2((u32)*(u32_le *)&inbuf[0xD0]);
if (!pti)
{
return -1;
}
// Only up to 0x104 - past that is the signature, which is present rather than empty here.
if (std::any_of(inbuf+0xD4, inbuf+0x104, [](u8 x) { return x != 0; }))
{
return -2;
}
// expand the seed into a xor buffer
auto xorbuf = expandSeed(pti->key, pti->code);
PRXType9 type9(inbuf);
type9.decrypt(pti->code);
SHA_CTX ctx;
SHAInit(&ctx);
SHAUpdate(&ctx, type9.tag, sizeof(type9.tag));
SHAUpdate(&ctx, xorbuf.data(), 0x10);
SHAUpdate(&ctx, type9.empty, sizeof(type9.empty));
SHAUpdate(&ctx, type9.ecdsaSignatureTail, sizeof(type9.ecdsaSignatureTail));
SHAUpdate(&ctx, type9.id, sizeof(type9.id));
SHAUpdate(&ctx, type9.kirkHeader, sizeof(type9.kirkHeader));
SHAUpdate(&ctx, type9.kirkMetadata, sizeof(type9.kirkMetadata));
SHAUpdate(&ctx, type9.prxHeader, sizeof(type9.prxHeader));
u8 sha1[0x14];
SHAFinal(sha1, &ctx);
if (memcmp(sha1, type9.sha1, sizeof(sha1)) != 0)
{
return -3;
}
constexpr auto offset = sizeof(PSP_Header)-sizeof(KIRK_CMD1_ECDSA_HEADER)-sizeof(type9.prxHeader);
KIRK_CMD1_ECDSA_HEADER *header = reinterpret_cast<KIRK_CMD1_ECDSA_HEADER *>(outbuf+offset);
if (outbuf != inbuf)
{
memcpy(outbuf, inbuf, size);
}
memset(header, 0, sizeof(KIRK_CMD1_ECDSA_HEADER));
memcpy(reinterpret_cast<u8*>(&header->data_size), type9.kirkMetadata, sizeof(type9.kirkMetadata));
memcpy(reinterpret_cast<u8*>(header)+sizeof(KIRK_CMD1_ECDSA_HEADER), type9.prxHeader, sizeof(type9.prxHeader));
decryptKirkHeader(reinterpret_cast<u8*>(header), type9.kirkHeader, xorbuf.cbegin()+0x10, pti->code);
header->mode = 1;
// Left at 0, unlike type 6/7 which set it: type 9 carries a real ECDSA signature that was
// verified separately, so KIRK is not asked to hash-check this block. JPCSP's equivalent
// branch only writes the mode word and zeroes the rest of that region.
header->ecdsa_hash = 0;
if (!KirkOutputFits(header, size))
{
return -5;
}
if (kirk_sceUtilsBufferCopyWithRange(kirk, outbuf, size, reinterpret_cast<u8*>(header), size - offset, KIRK_CMD_DECRYPT_PRIVATE) != 0)
{
return -4;
}
return decryptSize;
}
int pspDecryptPRX(const u8 *inbuf, u8 *outbuf, u32 size, const u8 *seed, const u32 *fuseId)
{
// Every type below reads the tag at 0xD0, the size at 0xB0 and key data as far as 0x150, and
// writes a KIRK header into outbuf at a fixed offset derived from sizeof(PSP_Header). Without
// this, a PRX declaring a tiny psp_size got read past its end, had a header written past the end
// of the (equally tiny) output buffer, and passed "size - offset" to KIRK as an unsigned
// underflow. Callers must supply at least a whole header on both sides.
if (size < sizeof(PSP_Header)) {
ERROR_LOG(Log::Loader, "PRX too small to decrypt: %d bytes, need at least %d", (int)size, (int)sizeof(PSP_Header));
return -1;
}
KirkState kirk{};
if (fuseId) {
kirk_init2(&kirk, (u8 *)"PrxDecrypter", 12, fuseId[0], fuseId[1]);
} else {
kirk_init(&kirk);
}
// Type 3 tags are only ever type 3, and the others would fail on its header anyway.
const TAG_INFO2 *pti = GetTagInfo2((u32)*(u32_le *)&inbuf[0xD0]);
if (pti && pti->type == 3) {
return pspDecryptType3(&kirk, inbuf, outbuf, size, seed);
}
// this would be significantly better if we had a log of the tags
// and their appropriate prx types
// since we don't know the PRX type we attempt a decrypt using all
auto res = pspDecryptType0(&kirk, inbuf, outbuf, size);
if (res >= 0)
return res;
res = pspDecryptType1(&kirk, inbuf, outbuf, size);
if (res >= 0)
return res;
res = pspDecryptType2(&kirk, inbuf, outbuf, size);
if (res >= 0)
return res;
res = pspDecryptType5(&kirk, inbuf, outbuf, size, seed);
if (res >= 0)
return res;
res = pspDecryptType6(&kirk, inbuf, outbuf, size);
if (res >= 0)
return res;
// Last, because its header check is a subset of type 6's - a genuine type 6 PRX would pass it
// and then fail on the hash, so trying it earlier would shadow the real answer.
return pspDecryptType9(&kirk, inbuf, outbuf, size);
}
bool pspUnwrapDNASPGD(std::vector<u8> &data, const u8 *secureId, const u32 *fuseId)
{
if (data.size() < 0x90 || memcmp(data.data(), "\0PGD", 4) != 0) {
return false;
}
KirkState kirk{};
if (fuseId) {
kirk_init2(&kirk, (u8 *)"PrxDecrypter", 12, fuseId[0], fuseId[1]);
} else {
kirk_init(&kirk);
}
// The PGD of an installed patch uses the flag 1 fixed key; keep 2 (the usual NPDRM one) as a fallback.
u8 header[0x90];
PGD_DESC *pgd = nullptr;
for (int flag : { 1, 2 }) {
memcpy(header, data.data(), sizeof(header));
pgd = pgd_open(&kirk, header, flag, const_cast<u8 *>(secureId));
if (pgd) {
break;
}
}
if (!pgd) {
return false;
}
const u64 blocksSize = (u64)pgd->block_nr * pgd->block_size;
if ((u64)pgd->data_offset + blocksSize > data.size() || pgd->data_size > blocksSize) {
pgd_close(pgd);
return false;
}
std::vector<u8> plain((size_t)blocksSize);
for (u32 block = 0; block < pgd->block_nr; block++) {
memcpy(pgd->block_buf, data.data() + pgd->data_offset + (size_t)block * pgd->block_size, pgd->block_size);
pgd_decrypt_block(&kirk, pgd, block);
memcpy(plain.data() + (size_t)block * pgd->block_size, pgd->block_buf, pgd->block_size);
}
plain.resize(pgd->data_size);
pgd_close(pgd);
data = std::move(plain);
return true;
}
// psheet's fixed version key for the package header (its data segment, firmware 6.61).
static const u8 drmbb_header_key[16] = {0xED, 0xE2, 0x5D, 0x2D, 0xBB, 0xF8, 0x12, 0xE5, 0x3C, 0x5C, 0x59, 0x32, 0xFA, 0xE3, 0xE2, 0x43};
// sceDrmBBCipher type 1 (fixed keys), decrypting in place.
static bool DrmBBDecrypt(KirkState *kirk, u8 *data, u32 size, u32 seed, const u8 *headerKey, const u8 *versionKey) {
CIPHER_KEY ckey;
u8 hkey[16];
memcpy(hkey, headerKey, sizeof(hkey));
bool ok = sceDrmBBCipherInit(kirk, &ckey, 1, 2, hkey, const_cast<u8 *>(versionKey), seed) == 0 &&
sceDrmBBCipherUpdate(kirk, &ckey, data, (int)size) == 0;
sceDrmBBCipherFinal(&ckey);
return ok;
}
bool pspReadDrmBBPackage(const std::vector<u8> &pbp, const u8 *secureIdIn, std::vector<DrmBBFile> &files, u8 *secureIdOut)
{
// Layout from psheet's bbox_access.c functions (get_core_header, get_params, get_string). All
// offsets below are relative to DATA.PSP.
if (pbp.size() < 0x28 || memcmp(pbp.data(), "\0PBP", 4) != 0) {
return false;
}
const u32 base = *(const u32_le *)&pbp[0x20];
const u32 end = *(const u32_le *)&pbp[0x24];
if (end > pbp.size() || base > end || end - base < 0x60) {
return false;
}
const u32 size = end - base;
const u8 *psp = pbp.data() + base;
KirkState kirk{};
kirk_init(&kirk);
// The whole file is MACed with the header key, the last 16 bytes.
{
std::vector<u8> copy(pbp.begin(), pbp.end() - 16);
u8 mac[16];
memcpy(mac, &pbp[pbp.size() - 16], sizeof(mac));
MAC_KEY mkey;
sceDrmBBMacInit(&mkey, 1);
sceDrmBBMacUpdate(&kirk, &mkey, copy.data(), (int)copy.size());
if (sceDrmBBMacFinal2(&kirk, &mkey, mac, const_cast<u8 *>(drmbb_header_key)) != 0) {
return false;
}
}
// 0x50 bytes of header, MACed with the secure install ID. The game passes the same ID to the install.
u8 header[0x50];
u8 mac[16];
memcpy(header, psp, sizeof(header));
memcpy(mac, psp + 0x50, sizeof(mac));
u8 secureId[16];
MAC_KEY mkey;
sceDrmBBMacInit(&mkey, 1);
sceDrmBBMacUpdate(&kirk, &mkey, header, sizeof(header));
if (secureIdIn) {
memcpy(secureId, secureIdIn, sizeof(secureId));
if (sceDrmBBMacFinal2(&kirk, &mkey, mac, secureId) != 0) {
return false;
}
} else if (bbmac_getkey(&kirk, &mkey, mac, secureId) != 0) {
return false;
}
if (secureIdOut) {
memcpy(secureIdOut, secureId, sizeof(secureId));
}
memcpy(header, psp, sizeof(header));
if (!DrmBBDecrypt(&kirk, header + 0x10, 0x40, 0, header, drmbb_header_key)) {
return false;
}
const u8 *paramKey = header + 0x10;
const u8 *fileKey = header + 0x20;
const u32 *h = (const u32 *)(header + 0x30);
if (h[0] != 1 || h[2] != 1) {
return false;
}
const u32 paramOffset = h[4], paramCount = h[5], fileTable = h[6];
// The 16-byte aligned blocks around [offset, offset + len), decrypted with block-numbered seeds.
auto readBlocks = [&](u32 tableOffset, u32 offset, u32 len, const u8 *key, std::vector<u8> &dst) {
const u32 start = offset & ~15;
const u64 alignedEnd = ((u64)offset + len + 15) & ~15ULL;
if ((u64)tableOffset + alignedEnd > size) {
return false;
}
dst.assign(psp + tableOffset + start, psp + tableOffset + alignedEnd);
if (!DrmBBDecrypt(&kirk, dst.data(), (u32)dst.size(), start >> 4, key, secureId)) {
return false;
}
dst.erase(dst.begin(), dst.begin() + (offset - start));
dst.resize(len);
return true;
};
// Parameters are (id, value) pairs. 0x01010100 is the number of files.
std::vector<u8> params;
if (paramCount > 0x100 || !readBlocks(paramOffset, 0, paramCount * 8, paramKey, params)) {
return false;
}
u32 fileCount = 0;
for (u32 i = 0; i < paramCount; i++) {
const u32 *p = (const u32 *)&params[i * 8];
if (p[0] == 0x01010100) {
fileCount = p[1];
}
}
// File entries are 16 bytes: u16 name offset, u16 name length, u32 data offset, u32 size, all
// relative to the table.
if (fileCount > 0x100) {
return false;
}
files.clear();
for (u32 i = 0; i < fileCount; i++) {
std::vector<u8> entry, name;
if (!readBlocks(fileTable, i * 16, 16, fileKey, entry)) {
return false;
}
const u16 nameOffset = *(const u16 *)&entry[0];
const u16 nameLength = *(const u16 *)&entry[2];
const u32 dataOffset = *(const u32 *)&entry[4];
const u32 dataSize = *(const u32 *)&entry[8];
if (!readBlocks(fileTable, nameOffset, nameLength, fileKey, name)) {
return false;
}
DrmBBFile &file = files.emplace_back();
// The name is NUL terminated within its length.
file.name.assign((const char *)name.data(), strnlen((const char *)name.data(), name.size()));
if (!readBlocks(fileTable, dataOffset, dataSize, fileKey, file.data)) {
return false;
}
}
return true;
}
bool pspParseFuseId(std::string_view text, u32 fuseId[2])
{
if (text.size() > 2 && text[0] == '0' && (text[1] == 'x' || text[1] == 'X')) {
text.remove_prefix(2);
}
if (text.size() != 12 && text.size() != 16) {
return false;
}
u64 value = 0;
for (char c : text) {
int digit;
if (c >= '0' && c <= '9') {
digit = c - '0';
} else if (c >= 'a' && c <= 'f') {
digit = c - 'a' + 10;
} else if (c >= 'A' && c <= 'F') {
digit = c - 'A' + 10;
} else {
return false;
}
value = (value << 4) | digit;
}
fuseId[0] = (u32)value;
fuseId[1] = (u32)(value >> 32);
return true;
}