PS4 FPKG Writeup by Flatz
The text below in its entirety is property of scene developer flatz and the original tweet can be viewed here
- Overview
- Fake SELFs
2.1. Kernel code
2.2. Testing - Fake PKGs
3.1. Toolchain
3.2. Modding of Shellcore
3.3. Kernel code - Defines, structures and helper functions
- Package repacking issues
5.1. SFO file parsing
5.2. PlayGo chunk builder
5.3. GP4 project generation
5.4. Some of PKG/PFS stuff - Conclusion
Overview
My original intent was to implement a minimalistic strategy that could lead to running stuff like fselfs and debug pkgs. I don’t like methods that do a lot of patching and involve a lot of manual work, so I prefer to use built-in functionality whenever possible. While the result is not as minimalistic, it is effective. As I’ve said multiple times, this stuff can be ported to any firmware.
So here’s the problems we have:
- Executable files including SELFs/SPRXs/SEXEs/SDLLs are signed and encrypted. While their fake versions are not signed and encrypted, we usually can’t use them on a retail console due to restrictions that are put in place by the auth manager that loads them (a secure module, or SM, that is running by SAMU, a secure co-processor that is embedded into our AMD‘s APU). The (S)ELF loader from the kernel use SAMU calls for everything crypto-related.
- Applications, including games (except system ones) requires everything to be bundled into pkg files. The console uses this system heavily, so we can’t just drop our application in the file system and hope that all things will work normally. So we must use packages too if we don’t want to give ourselves a headache. These packages are also encrypted and signed, and we can’t use debug packages on a retail console. Even debug packages are encrypted and signed unlike executable files. Due to Sony using asymmetric encryption, we don’t have all the private keys to decrypt them properly.
Okay, so how we want to achieve our goal? We’ll start with a method to load custom executable files. Keep in mind that everything here is written for developers and not currently usable for a regular user. If you don’t have much knowledge or the skills on how to implement this payload, you just need to wait until a developer releases it as a ready to use payload, or maybe integrate it into an exploit itself, thus making a fork of it.
Also, please refer to Defines, structures and helper functions section if you see unknown defines, functions, constants, variables or structures, because I’ve excluded some from the code snippets below for readability.
Fake SELFs
Kernel code
Because fake versions of executable files (so called FSELFs) don’t use any encryption/signing, we could redirect SM calls that loads their segments to our own functions in a kernel payload. It’s very trivial because all they really do is call memcpy()
at some points in time. All we need is to find the kernel’s methods that I’ll describe below and hook them with our own code (and redirect them to original functions when needed). Please, keep in mind that this code was made for 4.55+ and MAY need adjusting for 4.05, so you will need to backport it according to 4.05’s functionality!
sceSblAuthMgrIsLoadable2
This function basically checks if a file could be loaded from the specific folder/partition and also sets up the authentication information that is used by the system to check access rights and capabilities. If we need some specific capabilities we may store them inside our own file and feed them into the authentication’s information and for others we just use a default auth’s information.
static inline int sceSblAuthMgrGetSelfAuthInfoFake(struct self_context* ctx, struct self_auth_info* info) {struct self_header* hdr;struct self_fake_auth_info* fake_info;if (ctx->format == SELF_FORMAT_SELF) {hdr = (struct self_header*)ctx->header;fake_info = (struct self_fake_auth_info*)(ctx->header + hdr->header_size + hdr->meta_size - 0x100);if (fake_info->size == sizeof(fake_info->info)) {memcpy(info, &fake_info->info, sizeof(*info));return 0;}return -37;} else {return -35;}}//...static inline int is_fake_self(struct self_context* ctx) {struct self_ex_info* ex_info;int ret;if (ctx && ctx->format == SELF_FORMAT_SELF) {ret = sceSblAuthMgrGetSelfInfo(ctx, &ex_info);if (ret)return 0;return ex_info->ptype == SELF_PTYPE_FAKE;} else {return 0;}}//...static const uint8_t s_auth_info_for_exec[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x20,0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,0x00, 0x40, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,};static const uint8_t s_auth_info_for_dynlib[] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x30, 0x00, 0x30,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,0x00, 0x40, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,};static int build_self_auth_info_fake(struct self_context* ctx, struct self_auth_info* parent_auth_info, struct self_auth_info* auth_info) {struct self_auth_info fake_auth_info;struct self_ex_info* ex_info;struct elf64_ehdr* ehdr = NULL;int ret;if (!ctx || !parent_auth_info || !auth_info) {ret = EINVAL;goto error;}if (!is_fake_self(ctx)) {ret = EINVAL;goto error;}ret = sceSblAuthMgrGetSelfInfo(ctx, &ex_info);if (ret)goto error;ret = sceSblAuthMgrGetElfHeader(ctx, &ehdr);if (ret)goto error;if (!ehdr) {ret = ESRCH;goto error;}ret = sceSblAuthMgrGetSelfAuthInfoFake(ctx, &fake_auth_info);if (ret) {switch (ehdr->type) {case ELF_ET_EXEC:case ELF_ET_SCE_EXEC:case ELF_ET_SCE_EXEC_ASLR:memcpy(&fake_auth_info, s_auth_info_for_exec, sizeof(fake_auth_info));ret = 0;break;case ELF_ET_SCE_DYNAMIC:memcpy(&fake_auth_info, s_auth_info_for_dynlib, sizeof(fake_auth_info));ret = 0;break;default:ret = ENOTSUP;goto error;}fake_auth_info.paid = ex_info->paid;// TODO: overwrite low bits of PAID with title id number}if (auth_info)memcpy(auth_info, &fake_auth_info, sizeof(*auth_info));error:return ret;}//...static int sceSblAuthMgrIsLoadable2_hook(struct self_context* ctx, struct self_auth_info* old_auth_info, int path_id, struct self_auth_info* new_auth_info) {int ret;if (ctx->format == SELF_FORMAT_ELF || is_fake_self(ctx))ret = build_self_auth_info_fake(ctx, old_auth_info, new_auth_info);elseret = sceSblAuthMgrIsLoadable2(ctx, old_auth_info, path_id, new_auth_info);return ret;}
sceSblAuthMgrVerifyHeader
This function parses a file’s header, decrypts it (for finalized files) and set ups a context structure inside SM that is used later for loading purposes. We can’t just omit a context set up because it’s used by further system’s code, so we’ll use a little trick by letting the system to set up a fake context for us (this is a bit of a lie though, it’s a real context that SM will prepare, but we won’t use it as is in kernel). Thankfully, we have a few real SELFs inside static kernel memory (embedded into a memory disk image that is loaded at startup), so we don’t even need to load them from the external file system. So, we need to authenticate, for example,mini-syscore.elf
, and then replace a part of the context’s structure with our own stuff.
static inline int auth_self_header(struct self_context* ctx) {extern const uint8_t* mini_syscore_self_binary; /* TODO: you should point it to its location in a kernel's memory (copy the file using ftp or something like that and try to find it in kernel's memory dump) */struct self_header* hdr;unsigned int old_total_header_size, new_total_header_size;int old_format;uint8_t* tmp;int is_unsigned;int ret;is_unsigned = ctx->format == SELF_FORMAT_ELF || is_fake_self(ctx);if (is_unsigned) {old_format = ctx->format;old_total_header_size = ctx->total_header_size;/* take a header from mini-syscore.elf */hdr = (struct self_header*)mini_syscore_self_binary;new_total_header_size = hdr->header_size + hdr->meta_size;tmp = (uint8_t*)alloc(new_total_header_size);if (!tmp) {ret = ENOMEM;goto error;}/* temporarily swap an our header with a header from a real SELF file */memcpy(tmp, ctx->header, new_total_header_size);memcpy(ctx->header, hdr, new_total_header_size);/* it's now SELF, not ELF or whatever... */ctx->format = SELF_FORMAT_SELF;ctx->total_header_size = new_total_header_size;/* call the original method using a real SELF file */*ret = sceSblAuthMgrSmVerifyHeader(ctx);/* restore everything we did before */memcpy(ctx->header, tmp, new_total_header_size);ctx->format = old_format;ctx->total_header_size = old_total_header_size;dealloc(tmp);} else {ret = sceSblAuthMgrSmVerifyHeader(ctx);}error:return ret;}//...static int sceSblAuthMgrVerifyHeader_hook(struct self_context* ctx) {sceSblAuthMgrSmStart();return auth_self_header(ctx);}
sceSblAuthMgrSmLoadSelfSegment
SELF files consist of segments that are divided into blocks. Fortunately, for fake files we don’t need that at all, so a code for loading them will be trivial. Notice, we’re dealing with stack frames here to find a context’s structure. For 4.05 you may use a different method or maybe a different offset, just look into the original kernel’s method to see if you need to adjust something.
static int sceSblAuthMgrSmLoadSelfSegment__sceSblServiceMailbox_hook(unsigned long service_id, uint8_t* request, void* response) {/* getting a stack frame of a parent function */uint8_t* frame = (uint8_t*)__builtin_frame_address(1); /* TODO: may need to fix *//* finding a pointer to a context's structure */struct self_context* ctx = *(struct self_context**)(frame - 0x100); /* TODO: may need to fix */int is_unsigned = ctx && is_fake_self(ctx);int ret;if (is_unsigned) {*(int*)(response + 0x04) = 0; /* setting error field to zero, thus we have no errors */ret = 0;} else {ret = sceSblServiceMailbox(service_id, request, response);}return ret;}
sceSblAuthMgrSmLoadSelfBlock
Here is where the actual decryption/copying happens. So, as I said above, we just need to copy the block’s data from one buffer to another. However, there is a catch, we have the GPU‘s addresses here, so we need to convert them into CPU addresses to use them inmemcpy()
calls, we can do that by looking through lists of mapped memory ranges and searching for a matched GPU address. Also notice, here we’re using a specific register to get a context’s structure. For 4.05 you may use a different method or maybe a different register, just look into the original kernel’s method to find what needs to be changed.
static int sceSblAuthMgrSmLoadSelfBlock__sceSblServiceMailbox_hook(unsigned long service_id, uint8_t* request, void* response) {register struct self_context* ctx __asm__ ("r14"); /* TODO: may need to fix */vm_offset_t segment_data_gpu_va = *(unsigned long*)(request + 0x08);vm_offset_t cur_data_gpu_va = *(unsigned long*)(request + 0x50);vm_offset_t cur_data2_gpu_va = *(unsigned long*)(request + 0x58);unsigned int data_offset = *(unsigned int*)(request + 0x44);unsigned int data_size = *(unsigned int*)(request + 0x48);vm_offset_t segment_data_cpu_va, cur_data_cpu_va, cur_data2_cpu_va;unsigned int size1;int is_unsigned = ctx && (ctx->format == SELF_FORMAT_ELF || is_fake_self(ctx));int ret;if (is_unsigned) {/* looking into lists of GPU's mapped memory regions */segment_data_cpu_va = sceSblDriverGpuVaToCpuVa(segment_data_gpu_va, NULL);cur_data_cpu_va = sceSblDriverGpuVaToCpuVa(cur_data_gpu_va, NULL);cur_data2_cpu_va = cur_data2_gpu_va ? sceSblDriverGpuVaToCpuVa(cur_data2_gpu_va, NULL) : 0;if (segment_data_cpu_va && cur_data_cpu_va) {if (cur_data2_gpu_va && cur_data2_gpu_va != cur_data_gpu_va && data_offset > 0) {/* data spans two consecutive memory's pages, so we need to copy twice */size1 = PAGE_SIZE - data_offset;memcpy((char*)segment_data_cpu_va, (char*)cur_data_cpu_va + data_offset, size1);memcpy((char*)segment_data_cpu_va + size1, (char*)cur_data2_cpu_va, data_size - size1);} else {memcpy((char*)segment_data_cpu_va, (char*)cur_data_cpu_va + data_offset, data_size);}}*(int*)(request + 0x04) = 0; /* setting error field to zero, thus we have no errors */ret = 0;} else {ret = sceSblServiceMailbox(service_id, request, response);}return ret;}
It’s done, now we’re going to hook them all (keep in mind that you may use any hooking method you want). You need to find slide values for all these functions, just open the kernel’s elf (you could use the one that was leaked some time ago or dump your own), locate corresponding functions and replace their offsets (they are relative to kernel’s base address!).
void install_unsigned_loader(void) {/* TODO: need to change a slide of "call sceSblAuthMgrVerifyHeader" instruction */INSTALL_CALL_HOOK(0x61F976, sceSblAuthMgrVerifyHeader_hook);INSTALL_CALL_HOOK(0x620599, sceSblAuthMgrVerifyHeader_hook);/* TODO: need to change a slide of "call sceSblAuthMgrIsLoadable2" instruction */INSTALL_CALL_HOOK(0x61F24F, sceSblAuthMgrIsLoadable2_hook);/* TODO: need to change a slide of "call sceSblServiceMailbox" instruction that's located inside sceSblAuthMgrSmLoadSelfBlock() */INSTALL_CALL_HOOK(0x6244E1, sceSblAuthMgrSmLoadSelfBlock__sceSblServiceMailbox_hook);/* TODO: need to change a slide of "call sceSblServiceMailbox" instruction that's located inside sceSblAuthMgrSmLoadSelfSegment() */INSTALL_CALL_HOOK(0x6238BA, sceSblAuthMgrSmLoadSelfSegment__sceSblServiceMailbox_hook);}
Testing
When I did this the first time I thought “how am I going to test that code?”. I decided to replace a built-in application which isn’t packed into a package file, but starts from the SELF file directly. The Video Editor (NPXS20103
) application fit this need perfectly, and it’s located at /system_ex/app/NPXS20103/eboot.bin
. I’ve decrypted an original eboot using one of the possible methods and then I needed something to pack a plain ELF file into FSELF. However, there is no such tool provided, so I’ve created a custom script to do that: fake signed .elf maker
There’s another thing that we should take in consideration: system applications use access rights and capabilities and they are shared/checked when dependencies are loaded too, which is done using a self_auth_info
structure that’s filled and checked by SM‘s code. So we need to dump an original structure and use it to workaround possible glitches/crashes or whatever else. It could be done easily just by calling kern_get_self_auth_info(struct thread* td, const char* self_path, enum uio_seg segflg, struct self_auth_info* auth_info)
from kernel code (also it’s possible to do that from userland but this requires a bit of patching, there is even a syscall for it).
An example of such structure that was taken from the original eboot.bin
on 4.55:
0000h: 11 00 00 00 00 00 00 38 00 00 00 00 00 1C 00 400010h: 00 FF 00 00 00 00 00 A5 00 00 00 00 00 00 00 000020h: 00 00 00 00 00 00 00 00 00 00 00 80 00 40 00 400030h: 00 00 00 00 00 00 00 80 01 00 00 00 00 00 00 040040h: 00 40 FF FF 00 00 00 F0 XX XX XX XX XX XX XX XX0050h: XX XX XX XX XX XX XX XX 00 00 00 00 00 00 00 000060h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000070h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000080h: 00 00 00 00 00 00 00 00
And the same dump but taken from 5.00:
0000h: 11 00 00 00 00 00 00 38 00 00 00 00 00 1C 00 400010h: 00 FF 00 00 00 00 00 85 00 00 00 00 00 00 00 000020h: 00 00 00 00 00 00 00 00 00 00 00 80 00 40 00 400030h: 00 00 00 00 00 00 00 80 01 00 00 00 00 00 00 040040h: 00 40 FF FF 00 00 00 F0 XX XX XX XX XX XX XX XX0050h: XX XX XX XX XX XX XX XX 00 00 00 00 00 00 00 000060h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000070h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000080h: 00 00 00 00 00 00 00 00
As you may see only one byte of the structure differs. Also if I remember correctly, bytes under XX here are usually the same for all system applications and takes an important role for loading process (however I need to investigate this more), so we will just use them all. I have no idea if it’s a some key or magic, so just dump it by yourself using the method I’ve provided.
Now you could use my script to make a fake signed elf of this application:
$ make_fself.py --paid 0x3800000000000011 --auth-info '<bytes of self_auth_info structure>' eboot.elf eboot.binloading elf file: eboot.elfsaving fake signed elf file: eboot.binprocessing segment #00...processing segment #01...processing segment #02...processing segment #08...done
Let’s replace original SELF file with our FSELF now. We could do that from userland by remounting /system_ex, thus allowing us write access:
static int mount_large_fs(const char* device, const char* mountpoint, const char* fstype, const char* mode, unsigned int flags) {struct iovec* iov = NULL;int iovlen = 0;int ret;build_iovec(&iov, &iovlen, "fstype", fstype, -1);build_iovec(&iov, &iovlen, "fspath", mountpoint, -1);build_iovec(&iov, &iovlen, "from", device, -1);build_iovec(&iov, &iovlen, "large", "yes", -1);build_iovec(&iov, &iovlen, "timezone", "static", -1);build_iovec(&iov, &iovlen, "async", "", -1);build_iovec(&iov, &iovlen, "ignoreacl", "", -1);if (mode) {build_iovec(&iov, &iovlen, "dirmask", mode, -1);build_iovec(&iov, &iovlen, "mask", mode, -1);}printf(" [I] Mounting %s(%s) to %s...\n", device, fstype, mountpoint);ret = nmount(iov, iovlen, flags);if (ret < 0) {printf(" [E] Failed: %d (errno: %d).", ret, errno);goto error;} else {printf(" [I] Success.");}error:return ret;}//...static int remount_partitions(void) {int ret;//...ret = mount_large_fs("/dev/da0x5.crypt", "/system_ex", "exfatfs", "511", MNT_UPDATE);if (ret)goto error;//...error:return ret;}
After calling remount_partitions()
we could use any method to transfer a new file to the console’s file system (don’t forget to make a backup of the original file).
Now, let’s record any video and try to trim it. This will allow the PS4 to launch the video editor application and, if everything was done properly, you should see that the application works normally (otherwise, you may see a crash).
Using this method you could modify almost any system binaries, and if someone could implement a method to soft reboot the console (if it’s even possible), they could redirect system partitions to their own locations with custom fake signed system binaries, thus achieving some kind of CFW with firmware modifications.
Fake PKGs
Toolchain
As I’ve stated above, all packages are encrypted and signed and use asymmetric encryption algorithms like RSA. This includes an algorithm to generate package keys too. Developers could specify a pseudo-random passcode which is used as a root key to generate other keys, that could be used later to generate more keys and so on…
One of these derived keys is EKPFS, encryption key for PFS. This key is used later to generate final keys to encrypt and sign the inner file system of a package file (called PFS, a Playstation File System). Unfortunately for us, it’s encrypted using RSA + AES algorithms, and though we have the AES key, we don’t have the RSA key which is more important. Encrypted (or put more appropriately, escrowed) EKPFS is called EEKPFS, SAMU decrypts it internally and won’t give us the result, it stays inside SM memory and never leaves it (I also believe that there is an additional AES layer in the middle but it’s not important for what we’re doing). EEKPFS is encrypted, and final keys that are generated from the decrypted EKPFS are set directly into key slots of the crypto c-oprocessor (CCP) and used later in CCP requests for doing decryption and verification. This is the main problem that makes this method a bit complicated, because we need to know this key and it’s impossible at the moment, so we need to make our own key and encrypt any content using it, thus we need to modify the toolchain and set a custom key there, then modify the kernel to use this key too (bypassing SM calls) for our own content.
To achieve our goal we need to modify the Orbis Publishing Tools, it’s a part of leaked SDK. It consists of several tools but we need only two executables from it (well, maybe three if you’re planning to use the GUI tool): orbis-pub-cmd.exe
and ext/sc.exe
(and optionally orbis-pub-prx.dll
). The first one is our main tool and the second one is used for crypto operations (holds keys, etc.). We’ll start with ext/sc.exe
.
There is an encrypted XML file inside sc.exe
that holds all needed keys, dictionaries, error codes and other stuff. We need to decrypt it, place our own key there and reencrypt it. We can pen a hex editor, for example, 010 editor with PE template, and look at the beginning of .data section where is our blob stored, copy these bytes until you find 16 zero bytes. The last 0x20 bytes of data is our IV/key (within that order!) for AES 128, so cut it from the data and decrypt the data with CBC mode, you should get the XML document, just don’t forget to trim trailing zero bytes (they are used as padding to get the correct AES block). If you calculate SHA256 hash over the resulting XML file you will notice that it’s the same as IV/key, take that into consideration.
You could also use this script to decrypt XML (place encrypted blob with iv/key into xml.enc.orig
): sc xml decrypter
I’ve already calculated an RSA 2048 key pair that I suggest to use for maintaining compatibility between different payloads and tools:
Public key for custom packages
Private key for custom packages
The public key exponent is 65537
.
You could use OpenSSL to dump all components: openssl rsa -in ypkg_private.pem -text -noout
: modulus
as s_ypkg_n
, publicExponent
as s_ypkg_e
, privateExponent
as s_ypkg_d
, prime1
as s_ypkg_p
, prime2
as s_ypkg_q
, exponent1
as s_ypkg_dmp1
, exponent2
as s_ypkg_dmq1
, and coefficient
as s_ypkg_iqmp
. Notice that in the code the byte order is reversed). Then we replace a key inside <mount-image>...</mount-image>
in the XML file with our modulus
(you should remove the leading zero byte):
<mount-image> 0xc6 0xcf 0x71 0xe7 0xe5 0x9a 0xf0 0xd1 0x2a 0x2c 0x45 0x8b 0xf9 0x2a 0x0e 0xc1 0x43 0x05 0x8b 0xc3 0x71 0x17 0x80 0x1d 0xcd 0x49 0x7d 0xde 0x35 0x9d 0x25 0x9b 0xa0 0xd7 0xa0 0xf2 0x7d 0x6c 0x08 0x7e 0xaa 0x55 0x02 0x68 0x2b 0x23 0xc6 0x44 0xb8 0x44 0x18 0xeb 0x56 0xcf 0x16 0xa2 0x48 0x03 0xc9 0xe7 0x4f 0x87 0xeb 0x3d 0x30 0xc3 0x15 0x88 0xbf 0x20 0xe7 0x9d 0xff 0x77 0x0c 0xde 0x1d 0x24 0x1e 0x63 0xa9 0x4f 0x8a 0xbf 0x5b 0xbe 0x60 0x19 0x68 0x33 0x3b 0xfc 0xed 0x9f 0x47 0x4e 0x5f 0xf8 0xea 0xcb 0x3d 0x00 0xbd 0x67 0x01 0xf9 0x2c 0x6d 0xc6 0xac 0x13 0x64 0xe7 0x67 0x14 0xf3 0xdc 0x52 0x69 0x6a 0xb9 0x83 0x2c 0x42 0x30 0x13 0x1b 0xb2 0xd8 0xa5 0x02 0x0d 0x79 0xed 0x96 0xb1 0x0d 0xf8 0xcc 0x0c 0xdf 0x81 0x95 0x4f 0x03 0x58 0x09 0x57 0x0e 0x80 0x69 0x2e 0xfe 0xff 0x52 0x77 0xea 0x75 0x28 0xa8 0xfb 0xc9 0xbe 0xbf 0x9f 0xbb 0xb7 0x79 0x8e 0x18 0x05 0xe1 0x80 0xbd 0x50 0x34 0x94 0x81 0xd3 0x53 0xc2 0x69 0xa2 0xd2 0x4c 0xcf 0x6c 0xf4 0x57 0x2c 0x10 0x4a 0x3f 0xfb 0x22 0xfd 0x8b 0x97 0xe2 0xc9 0x5b 0xa6 0x2b 0xcd 0xd6 0x1b 0x6b 0xdb 0x68 0x7f 0x4b 0xc2 0xa0 0x50 0x34 0xc0 0x05 0xe5 0x8d 0xef 0x24 0x67 0xff 0x93 0x40 0xcf 0x2d 0x62 0xa2 0xa0 0x50 0xb1 0xf1 0x3a 0xa8 0x3d 0xfd 0x80 0xd1 0xf9 0xb8 0x05 0x22 0xaf 0xc8 0x35 0x45 0x90 0x58 0x8e 0xe3 0x3a 0x7c 0xbd 0x3e 0x27 </mount-image>
Make sure that you have the same file size as original file. Now we need to calculate a SHA256 hash over a new file, add trailing zero bytes for padding and encrypt the data using IV/key based on the hash that we just calculated. Here we will append a new IV/key to the encrypted data and insert a new blob into sc.exe
.
You could also use this script to encrypt XML (place modified file without padding into xml.dec.new
): sc xml encrypter
It would also be nice to have support of generating packages that contains FSELFs (created with a tool above, for example), instead of dealing with raw ELFs. Sadly, publishing tools (and orbis-pub-cmd.exe
in particular) won’t allow packing signed elfs (even fake signed), but there is an exception: sce_sys/about/right.sprx
, it’s a retail SELF file that is embedded into the tool itself and it’s included into a package file automatically, so the tool is making an exception for this file. So we can reuse this feature for our FSELFs by redirecting a code path that prints this error to a code path that is used to handle right.sprx
. To do this we need to patch sc,exe
, and optionally, orbis-pub-prx.dll
. Without these patches toolchain will print this error:
[Error]Format of the elf file is not valid. (eboot.bin, already converted from elf file to self file)
So, let’s open the executable file in IDA, click on Strings, and search for this string: already converted from elf file to self
, remember this location, we’ll go back here later. In the same function we need to find a reference to sce_sys/about/right.sprx
, where it’s used within the strncmp()
function, and where the incoming file name is a match. It will jump to some location, this location is our target (in my case, it’s loc_454B16
).
push 100hmov esi, eaxpush offset aSce_sysAboutRi ; "sce_sys/about/right.sprx"push esicall ebp ; __imp_strncmpadd esp, 0Chtest eax, eaxjz loc_454B16
Now we’re going back (to the push
string instruction), we need to use IDA‘s assembler functionality to patch a binary using a code snippet: jmp loc_454B16
:
Original (for my case!):
push offset aAlreadyConvert ; "already converted from elf file to self"...jmp loc_45502E
Patched one (for my case!):
jmp unk_5E63FC-1918E6hjmp loc_45502E
Don’t mind the weird mutated jmp
instruction, it’s an issue with IDA‘s disassembler and won’t affect functionality. Now we can build package files successfully even with FSELFs inside.
But don’t close the binary yet. We need to extract a debug/fake RIF key from it, as it will be used later in kernel code. You should use binary search in IDA to find a RIF‘s magic value, 52 49 46 00
:
mov [esp+44Ch+Dst], 464952hmov [esp+44Ch+var_3D6], cxcall ds:__imp_strncpy
Skip a few lines of code from this location until you find a place where some function is called and there are a bunch of immediate byte values before this call, this is where the debug/fake RIF key is copied, just save it to somewhere (be sure that you have a proper byte order).
Modding of Shellcore
Shellcore patches are needed to allow us to install debug and fake packages. Keep in mind that fake packages are not debug packages because we changed the key and it doesn’t match to the debug key which we don’t know as I already stated above. So, on any developer consoles, they won’t work without our patches. Similarily, debug packages won’t work on a retail console for the same reason. These patches are also needed to place a debug RIF inside our fake package files to a proper common location on the hard drive, otherwise we won’t be able to run homebrew.
To do these patches you may use a kernel payload to patch the SceShellCore
process (or just ptrace()
stuff). It should find theSceShellCore
process and do patches there using, for example, the proc_rwmem()
function. I won’t go into details of this in this write-up because it’s trivial.
static int do_shellcore_patches(void) {struct proc* p = NULL;struct proc_vm_map_entry* entries = NULL;uint8_t* text_seg_base = NULL;size_t num_entries;size_t i, n;int ret = 0;/* XXX: all offsets below are belongs to functions that parses .pkg files */static uint32_t call_ofs_for__xor__eax_eax__3nop[] = {0x11A0DB, // call sceKernelIsGenuineCEX (0x1486BB for 4.55)0x66EA3B, // call sceKernelIsGenuineCEX (0x6E523B for 4.55)0x7F554B, // call sceKernelIsGenuineCEX (0x852C6B for 4.55)0x11A107, // call nidf_libSceDipsw_0xD21CE9E2F639A83C (0x1486E7 for 4.55)0x66EA67, // call nidf_libSceDipsw_0xD21CE9E2F639A83C (0x6E5267 for 4.55)0x7F5577, // call nidf_libSceDipsw_0xD21CE9E2F639A83C (0x852C97 for 4.55)};p = proc_find_by_name("SceShellCore");if (!p) {//printf("Unable to find shellcore process.\n");ret = ENOENT;goto error;}ret = proc_get_vm_map(p, &entries, &num_entries);if (ret) {//printf("proc_get_vm_map(%p) failed.\n", p);goto error;}for (i = 0; i < num_entries; ++i) {if (entries[i].prot == (PROT_READ | PROT_EXEC)) {text_seg_base = (uint8_t*)entries[i].start;break;}}if (!text_seg_base) {//printf("Unable to find text segment base of shellcore.\n");ret = ESRCH;goto error;}//// Enable installing of debug packages.//for (i = 0; i < COUNT_OF(call_ofs_for__xor__eax_eax__3nop); ++i) {ret = proc_write_mem(p, text_seg_base + call_ofs_for__xor__eax_eax__3nop[i], 5, "\x31\xC0\x90\x90\x90", &n);if (ret) {//printf("proc_write_mem(%p) failed.\n", p);goto error;}}/* XXX: this offset corresponds to "fake\0" string in the Shellcore's memory */ret = proc_write_mem(p, text_seg_base + 0xC980EE /* 0x40F28 for 4.55 */, strlen("free") + 1, "free", &n);if (ret) {//printf("proc_write_mem(%p) failed.\n", p);goto error;}//printf("Shellcore process has been patched.\n");error:if (entries)dealloc(entries);return ret;}
Please take note that here I’ve found offsets for 4.05 already, as well as for 4.55 (look at comments)!
Now we may need to test if they work. I’ve found a few text files inside a system partition that contains links to corresponding debug/retail packages of one well known application, the OMSK client (NPXS29005
). So we may use a debug package of it to test the patches that we just applied. If the pkg is successfully installed (we won’t be able to run it yet though), then everything is okay. Here are these links:
Kernel code
The idea is simple: first we try to decrypt package normally and if this step fails and we have fake package, then we try to decrypt it manually using custom key. Then we’re doing the same things as Orbis Publishing Tools
does for keys generation, and with these keys we can decrypt PFS image inside package file. But because the system use slot keys mechanism to handle these crypto operations, we need to omit it and tell the system that we have plain keys instead of slot keys, we feed these keys into crypto requests and then everything is done by the system. Also I should note that fake rif is not really used here because it’s a dummy file but we need to handle it too.
#define MAX_FAKE_KEYS 32struct fake_key_desc {uint8_t key[0x20];int occupied;};/* we maintain a list of known fake keys to be able to find them and do some changes in crypto requests */static struct fake_key_desc s_fake_keys[MAX_FAKE_KEYS];static struct sx s_fake_keys_lock;/* we mark our key using some pattern that we can check later */static const uint8_t s_fake_key_seed[0x10] = {0x46, 0x41, 0x4B, 0x45, 0x46, 0x41, 0x4B, 0x45, 0x46, 0x41, 0x4B, 0x45, 0x46, 0x41, 0x4B, 0x45,};static struct fake_key_desc* get_free_fake_key_slot(void) {struct fake_key_desc* slot = NULL;size_t i;sx_xlock(&s_fake_keys_lock);{for (i = 0; i < COUNT_OF(s_fake_keys); ++i) {if (!s_fake_keys[i].occupied) {s_fake_keys[i].occupied = 1;slot = s_fake_keys + i;break;}}}sx_xunlock(&s_fake_keys_lock);return slot;}static struct fake_key_desc* is_fake_pfs_key(uint8_t* key) {struct fake_key_desc* slot = NULL;size_t i;sx_xlock(&s_fake_keys_lock);{for (i = 0; i < COUNT_OF(s_fake_keys); ++i) {if (!s_fake_keys[i].occupied)continue;if (memcmp(s_fake_keys[i].key, key, sizeof(s_fake_keys[i].key)) == 0) {slot = s_fake_keys + i;break;}}}sx_xunlock(&s_fake_keys_lock);return slot;}static void debug_pfs_cleanup(void* arg) {sx_destroy(&s_fake_keys_lock);}//.../* these components are belongs to the key I've generated for our own content */static const uint8_t s_ypkg_n[0x100] = {/* TODO: paste a corresponding key here as described in a write-up, don't forget to remove a leading zero byte and correct byte order */};static const unsigned int s_ypkg_e = UINT32_C(0x10001);static const uint8_t s_ypkg_d[0x100] = {/* TODO: paste a corresponding key here as described in a write-up, be careful to use correct byte order */};static const uint8_t s_ypkg_p[0x80] = {/* TODO: paste a corresponding key here as described in a write-up, be careful to use correct byte order */};static const uint8_t s_ypkg_q[0x80] = {/* TODO: paste a corresponding key here as described in a write-up, be careful to use correct byte order */};static const uint8_t s_ypkg_dmp1[0x80] = {/* TODO: paste a corresponding key here as described in a write-up, be careful to use correct byte order */};static const uint8_t s_ypkg_dmq1[0x80] = {/* TODO: paste a corresponding key here as described in a write-up, be careful to use correct byte order */};static const uint8_t s_ypkg_iqmp[0x80] = {/* TODO: paste a corresponding key here as described in a write-up, be careful to use correct byte order */};/* a common function to generate a final key for PFS */static inline void pfs_gen_crypto_key(uint8_t* ekpfs, uint8_t seed[PFS_SEED_SIZE], unsigned int index, uint8_t key[PFS_FINAL_KEY_SIZE]) {struct thread* td = curthread();uint8_t d[4 + PFS_SEED_SIZE];memset(d, 0, sizeof(d));/* an index tells which key we should generate */*(uint32_t*)d = LE32(index);memcpy(d + sizeof(uint32_t), seed, PFS_SEED_SIZE);fpu_kern_enter(td, fpu_kern_ctx, 0);{Sha256Hmac(key, d, sizeof(d), ekpfs, EKPFS_SIZE);}fpu_kern_leave(td, fpu_kern_ctx);}/* an encryption key generator based on EKPFS and PFS header seed */static inline void pfs_generate_enc_key(uint8_t* ekpfs, uint8_t seed[PFS_SEED_SIZE], uint8_t key[PFS_FINAL_KEY_SIZE]) {pfs_gen_crypto_key(ekpfs, seed, 1, key);}/* asigning key generator based on EKPFS and PFS header seed */static inline void pfs_generate_sign_key(uint8_t* ekpfs, uint8_t seed[PFS_SEED_SIZE], uint8_t key[PFS_FINAL_KEY_SIZE]) {pfs_gen_crypto_key(ekpfs, seed, 2, key);}static int sceSblPfsKeymgrIoctl__sceSblPfsKeymgrGenEKpfsForGDGPAC__hook(pfs_key_blob_t* blob) {struct thread* td = curthread();struct rsa_buffer in_data;struct rsa_buffer out_data;struct rsa_key key;uint8_t dec_data[EEKPFS_SIZE];struct fake_key_desc* fake_key_slot;int ret;/* try to decrypt EEKPFS normally */ret = sceSblPfsKeymgrGenEKpfsForGDGPAC(blob);if (ret) {/* if this key is for debug/fake content, we could try to decrypt it manually */if (!blob->finalized) {memset(&in_data, 0, sizeof(in_data));{in_data.ptr = blob->eekpfs;in_data.size = sizeof(blob->eekpfs);}memset(&out_data, 0, sizeof(out_data));{out_data.ptr = dec_data;out_data.size = sizeof(dec_data);}memset(&key, 0, sizeof(key));{/* here we feed a custom key to the algorithm */key.p = (uint8_t*)s_ypkg_p;key.q = (uint8_t*)s_ypkg_q;key.dmp1 = (uint8_t*)s_ypkg_dmp1;key.dmq1 = (uint8_t*)s_ypkg_dmq1;key.iqmp = (uint8_t*)s_ypkg_iqmp;}fpu_kern_enter(td, fpu_kern_ctx, 0);{/* RSA PKCS1v15 */ret = RsaesPkcs1v15Dec2048CRT(&out_data, &in_data, &key);}fpu_kern_leave(td, fpu_kern_ctx);if (ret == 0) { /* got EKPFS key? */memcpy(blob->ekpfs, dec_data, sizeof(blob->ekpfs));/* add it to our key list */fake_key_slot = get_free_fake_key_slot();if (fake_key_slot)memcpy(fake_key_slot->key, blob->ekpfs, sizeof(fake_key_slot->key));}}}return ret;}static int pfs_sbl_init__sceSblPfsSetKey__hook(unsigned int* ekh, unsigned int* skh, uint8_t* key, uint8_t* iv, int mode, int unused, uint8_t disc_flag) {struct sbl_key_rbtree_entry* key_entry;int is_fake_key;int ret;ret = sceSblPfsSetKey(ekh, skh, key, iv, mode, unused, disc_flag);/* check if it's a key that we have decrypted manually */is_fake_key = is_fake_pfs_key(key) != NULL;key_entry = sceSblKeymgrGetKey(*ekh); /* find a corresponding key entry */if (key_entry) {if (is_fake_key) {/* generate an encryption key */pfs_generate_enc_key(key, iv, key_entry->desc.pfs.key);memcpy(key_entry->desc.pfs.seed, s_fake_key_seed, sizeof(s_fake_key_seed));}}key_entry = sceSblKeymgrGetKey(*skh); /* find a corresponding key entry */if (key_entry) {if (is_fake_key) {/* generate a signing key */pfs_generate_sign_key(key, iv, key_entry->desc.pfs.key);memcpy(key_entry->desc.pfs.seed, s_fake_key_seed, sizeof(s_fake_key_seed));}}return ret;}//...static int npdrm_decrypt_debug_rif(unsigned int type, uint8_t* data) {static const uint8_t rif_debug_key[0x10] = { /* TODO: place here a debug/fake RIF key */ };struct thread* td = curthread();int ret;fpu_kern_enter(td, fpu_kern_ctx, 0);{/* decrypt fake rif manually using a key from publishing tools */ret = AesCbcCfb128Decrypt(data + RIF_DIGEST_SIZE, data + RIF_DIGEST_SIZE, RIF_DATA_SIZE, rif_debug_key, sizeof(rif_debug_key) * 8, data);if (ret)ret = SCE_SBL_ERROR_NPDRM_ENOTSUP;}fpu_kern_leave(td, fpu_kern_ctx);return ret;}static int npdrm_decrypt_isolated_rif__sceSblKeymgrSmCallfunc__hook(union keymgr_payload* payload) {/* it's SM request, thus we have the GPU address here, so we need to convert it to the CPU address */union keymgr_request* request = (union keymgr_request*)sceSblDriverGpuVaToCpuVa(payload->data, NULL);int ret;/* try to decrypt rif normally */ret = sceSblKeymgrSmCallfunc(payload);/* and if it fails then we check if it's fake rif and try to decrypt it by ourselves */if ((ret != 0 || payload->status != 0) && request) {if (request->decrypt_rif.type == 0x200) { /* fake? */ret = npdrm_decrypt_debug_rif(request->decrypt_rif.type, request->decrypt_rif.data);payload->status = ret;ret = 0;}}return ret;}//...static int ccp_msg_populate_key(unsigned int key_handle, uint8_t* key, int reverse) {struct sbl_key_rbtree_entry* key_entry;uint8_t* in_key;int i;int status = 0;/* searching for a key entry */key_entry = sceSblKeymgrGetKey(key_handle);if (key_entry) {/* we have found one, now checking if it's our key */if (memcmp(key_entry->desc.pfs.seed, s_fake_key_seed, sizeof(key_entry->desc.pfs.seed)) == 0) {/* currently we have a crypto request that use a key slot which should be already in CCP, but because we did everything manually, we don't have this key slot, so we need to remove using of key slot and place a plain key here */in_key = key_entry->desc.pfs.key;if (reverse) { /* reverse bytes of a key if it's needed */for (i = 0; i < 0x20; ++i)key[0x20 - i - 1] = in_key[i];} else { /* copy a key as is */memcpy(key, in_key, 0x20);}status = 1;}}return status;}static int ccp_msg_populate_key_if_needed(struct ccp_msg* msg) {unsigned int cmd = msg->op.common.cmd;unsigned int type = CCP_OP(cmd);uint8_t* buf;int status = 0;/* skip messages that use plain keys and key slots */if (!(cmd & CCP_USE_KEY_HANDLE))goto skip;buf = (uint8_t*)&msg->op;/* we only need to handle xts/hmac crypto operations */switch (type) {case CCP_OP_XTS:status = ccp_msg_populate_key(*(uint32_t*)(buf + 0x28), buf + 0x28, 1); /* xts key have a reversed byte order */break;case CCP_OP_HMAC:status = ccp_msg_populate_key(*(uint32_t*)(buf + 0x40), buf + 0x40, 0); /* hmac key have a normal byte order */break;default:goto skip;}/* if key was successfully populated, then remove the flag which tells CCP to use a key slot */if (status)msg->op.common.cmd &= ~CCP_USE_KEY_HANDLE;skip:return status;}static int pfs_crypto__sceSblServiceCryptAsync__hook(struct ccp_req* request) {struct ccp_msg* msg;int ret;TAILQ_FOREACH(msg, &request->msgs, next) {/* handle each message in crypto request */ccp_msg_populate_key_if_needed(msg);}/* run a crypto function normally */ret = sceSblServiceCryptAsync(request);return ret;}//...#define pfs_generate_icv_sub__sceSblServiceCryptAsync__hook pfs_crypto__sceSblServiceCryptAsync__hook#define pfs_generate_icv_async_sub__sceSblServiceCryptAsync__hook pfs_crypto__sceSblServiceCryptAsync__hook#define pfs_dec_sub__sceSblServiceCryptAsync__hook pfs_crypto__sceSblServiceCryptAsync__hook#define pfs_dec_icv_sub__sceSblServiceCryptAsync__hook pfs_crypto__sceSblServiceCryptAsync__hook#define pfs_dec_icv_async_sub__sceSblServiceCryptAsync__hook pfs_crypto__sceSblServiceCryptAsync__hook#define pfs_enc_sub__sceSblServiceCryptAsync__hook pfs_crypto__sceSblServiceCryptAsync__hook#define pfs_icv_enc_sub__sceSblServiceCryptAsync__hook pfs_crypto__sceSblServiceCryptAsync__hookstatic void do_debug_pfs_patches(void) {memset(s_fake_keys, 0, sizeof(s_fake_keys));sx_init(&s_fake_keys_lock, "fake_keys_lock");EVENTHANDLER_REGISTER(shutdown_pre_sync, &debug_pfs_cleanup, NULL, 0);/* TODO: need to change a slide of "call sceSblKeymgrSmCallfunc" instruction inside npdrm_decrypt_isolated_rif() */INSTALL_CALL_HOOK(0x62DF00, npdrm_decrypt_isolated_rif__sceSblKeymgrSmCallfunc__hook);/* TODO: need to change slides of "call sceSblPfsKeymgrGenEKpfsForGDGPAC" instructions inside sceSblPfsKeymgrIoctl() */INSTALL_CALL_HOOK(0x607045, sceSblPfsKeymgrIoctl__sceSblPfsKeymgrGenEKpfsForGDGPAC__hook);INSTALL_CALL_HOOK(0x6070E1, sceSblPfsKeymgrIoctl__sceSblPfsKeymgrGenEKpfsForGDGPAC__hook);/* TODO: need to change slides of "call sceSblPfsSetKey" instructions inside pfs_sbl_init_sub() and pfs_sbl_init() */INSTALL_CALL_HOOK(0x69DB4A, pfs_sbl_init__sceSblPfsSetKey__hook);INSTALL_CALL_HOOK(0x69DBD8, pfs_sbl_init__sceSblPfsSetKey__hook);/* TODO: need to change slides of "call sceSblServiceCryptAsync" instructions inside corresponding pfs_*_sub() functions (easy to find by looking at string references that contains function names) */INSTALL_CALL_HOOK(0x69DDE4, pfs_generate_icv_sub__sceSblServiceCryptAsync__hook); // hmacINSTALL_CALL_HOOK(0x69E28C, pfs_generate_icv_async_sub__sceSblServiceCryptAsync__hook); // hmacINSTALL_CALL_HOOK(0x69E4E8, pfs_dec_sub__sceSblServiceCryptAsync__hook); // xtsINSTALL_CALL_HOOK(0x69E85D, pfs_dec_icv_sub__sceSblServiceCryptAsync__hook); // hmac, xtsINSTALL_CALL_HOOK(0x69EC7E, pfs_dec_icv_async_sub__sceSblServiceCryptAsync__hook); // hmac, xtsINSTALL_CALL_HOOK(0x69EF0D, pfs_enc_sub__sceSblServiceCryptAsync__hook); // xtsINSTALL_CALL_HOOK(0x69F252, pfs_icv_enc_sub__sceSblServiceCryptAsync__hook); // hmac, xts}
You may also need these kernel’s patches:
// Disable RSA signature check for PFS.kernel_text_base + 0x69F4E0: 55 48 89 E5 -> 31 C0 C3 90// Enable debug RIFs.kernel_text_base + 0x62D30D: E8 0E 04 00 00 EB 38 3D -> B8 01 00 00 00 EB 38 3D
And again, you will need to find slide values for everything above because it’s for 4.55.
Defines, structures and helper functions
Some defines/structures and functions are just taken from FreeBSD9 source code, so please refer there too. This section only describes those which are related to PS4 directly.
I use a few special macros (based on this nice code snippet: Sparse, offset-based C/C++ structs) to build a partially known structure with fields at specific offsets, so you don’t need to insert unknown members between known fields and add proper padding as it’s all handled for you.
#define JOIN_HELPER(x, y) x##y#define JOIN(x, y) JOIN_HELPER(x, y)//...#define TYPE_PAD(size) char JOIN(_pad_, __COUNTER__)[size]#define TYPE_VARIADIC_BEGIN(name) name { union {#define TYPE_BEGIN(name, size) name { union { TYPE_PAD(size)#define TYPE_END(...) }; } __VA_ARGS__#define TYPE_FIELD(field, offset) struct { TYPE_PAD(offset); field; }#define TYPE_CHECK_SIZE(name, size) \_Static_assert(sizeof(name) == (size), "Size of " #name " != " #size)#define TYPE_CHECK_FIELD_OFFSET(name, member, offset) \_Static_assert(offsetof(name, member) == (offset), "Offset of " #name "." #member " != " #offset)#define TYPE_CHECK_FIELD_SIZE(name, member, size) \_Static_assert(sizeof(((name*)0)->member) == (size), "Size of " #name "." #member " != " #size)
Okay, so let’s summarize all needed structures (if I forgot some, just ping me):
#define PAGE_SIZE 0x4000//...struct fpu_kern_ctx;/* TODO: you should point it to its location in a kernel's memory (grab this variable from some function that does crypto operations, for example, sceSblSsGenerateSealedKey()) */struct fpu_kern_ctx* fpu_kern_ctx;static inline struct thread* curthread(void) {struct thread* td;__asm__ __volatile__ ("mov %%gs:0, %0": "=r"(td));return td;}//...#define SCE_SBL_ERROR_NPDRM_ENOTSUP 0x800F0A25//...#define ELF_IDENT_SIZE 0x10#define ELF_EHDR_EXT_SIZE 0x1000#define ELF_IDENT_MAG0 0#define ELF_IDENT_MAG1 1#define ELF_IDENT_MAG2 2#define ELF_IDENT_MAG3 3#define ELF_IDENT_CLASS 4#define ELF_IDENT_DATA 5#define ELF_CLASS_64 2#define ELF_DATA_LSB 1#define ELF_TYPE_NONE 0#define ELF_TYPE_EXEC 2#define ELF_MACHINE_X86_64 0x3E#define ELF_PHDR_TYPE_NULL 0x0#define ELF_PHDR_TYPE_LOAD 0x1#define ELF_PHDR_TYPE_SCE_DYNLIBDATA 0x61000000#define ELF_PHDR_TYPE_SCE_RELRO 0x61000010#define ELF_PHDR_TYPE_SCE_COMMENT 0x6FFFFF00#define ELF_PHDR_TYPE_SCE_VERSION 0x6FFFFF01#define ELF_PHDR_FLAG_X 0x1#define ELF_PHDR_FLAG_W 0x2#define ELF_PHDR_FLAG_R 0x4#define ELF_ET_EXEC 0x2#define ELF_ET_SCE_EXEC 0xFE00#define ELF_ET_SCE_EXEC_ASLR 0xFE10#define ELF_ET_SCE_DYNAMIC 0xFE18typedef uint16_t elf64_half_t;typedef uint32_t elf64_word_t;typedef uint64_t elf64_xword_t;typedef uint64_t elf64_off_t;typedef uint64_t elf64_addr_t;struct elf64_ehdr {uint8_t ident[ELF_IDENT_SIZE];elf64_half_t type;elf64_half_t machine;elf64_word_t version;elf64_addr_t entry;elf64_off_t phoff;elf64_off_t shoff;elf64_word_t flags;elf64_half_t ehsize;elf64_half_t phentsize;elf64_half_t phnum;elf64_half_t shentsize;elf64_half_t shnum;elf64_half_t shstrndx;};struct elf64_phdr {elf64_word_t type;elf64_word_t flags;elf64_off_t offset;elf64_addr_t vaddr;elf64_addr_t paddr;elf64_xword_t filesz;elf64_xword_t memsz;elf64_xword_t align;};struct elf64_shdr {elf64_word_t name;elf64_word_t type;elf64_xword_t flags;elf64_addr_t addr;elf64_off_t offset;elf64_xword_t size;elf64_word_t link;elf64_word_t info;elf64_xword_t addralign;elf64_xword_t entsize;};//...#define SELF_DIGEST_SIZE 0x20#define SELF_CONTENT_ID_SIZE 0x13#define SELF_RANDOM_PAD_SIZE 0x0D#define SELF_MAX_HEADER_SIZE 0x4000enum self_format {SELF_FORMAT_NONE,SELF_FORMAT_ELF,SELF_FORMAT_SELF,};#define SIZEOF_SELF_PAGER 0x100 // XXX: random, don't use directly without fixing itTYPE_BEGIN(struct self_pager, SIZEOF_SELF_PAGER);TYPE_FIELD(struct mtx lock, 0x00);TYPE_END();#define SIZEOF_SELF_INFO 0x100 // XXX: random, don't use directly without fixing itTYPE_BEGIN(struct self_info, SIZEOF_SELF_INFO);TYPE_FIELD(struct vnode* vp, 0x20);TYPE_FIELD(struct self_pager* pager, 0x28);TYPE_FIELD(uint8_t* header, 0x38);TYPE_FIELD(int ctx_id, 0x40);TYPE_END();#define SIZEOF_SELF_CONTEXT 0x60 // sceSblAuthMgrAuthHeader:bzero(sbl_authmgr_context, 0x60)TYPE_BEGIN(struct self_context, SIZEOF_SELF_CONTEXT);TYPE_FIELD(enum self_format format, 0x00);TYPE_FIELD(int elf_auth_type, 0x04); /* auth id is based on that */TYPE_FIELD(unsigned int total_header_size, 0x08);TYPE_FIELD(int ctx_id, 0x1C);TYPE_FIELD(uint64_t svc_id, 0x20);TYPE_FIELD(int buf_id, 0x30);TYPE_FIELD(uint8_t* header, 0x38);TYPE_FIELD(struct mtx lock, 0x40);TYPE_END();#define SIZEOF_SELF_HEADER 0x20TYPE_BEGIN(struct self_header, SIZEOF_SELF_HEADER);TYPE_FIELD(uint32_t magic, 0x00);#define SELF_MAGIC 0x1D3D154F#define ELF_MAGIC 0x464C457FTYPE_FIELD(uint8_t version, 0x04);TYPE_FIELD(uint8_t mode, 0x05);TYPE_FIELD(uint8_t endian, 0x06);TYPE_FIELD(uint8_t attr, 0x07);TYPE_FIELD(uint32_t key_type, 0x08);TYPE_FIELD(uint16_t header_size, 0x0C);TYPE_FIELD(uint16_t meta_size, 0x0E);TYPE_FIELD(uint64_t file_size, 0x10);TYPE_FIELD(uint16_t num_entries, 0x18);TYPE_FIELD(uint16_t flags, 0x1A);TYPE_END();#define SIZEOF_SELF_ENTRY 0x20TYPE_BEGIN(struct self_entry, SIZEOF_SELF_ENTRY);TYPE_FIELD(uint64_t props, 0x00);TYPE_FIELD(uint64_t offset, 0x08);TYPE_FIELD(uint64_t file_size, 0x10);TYPE_FIELD(uint64_t memory_size, 0x18);TYPE_END();#define SIZEOF_SELF_EX_INFO 0x40TYPE_BEGIN(struct self_ex_info, SIZEOF_SELF_EX_INFO);TYPE_FIELD(uint64_t paid, 0x00);TYPE_FIELD(uint64_t ptype, 0x08);#define SELF_PTYPE_FAKE 0x1TYPE_FIELD(uint64_t app_version, 0x10);TYPE_FIELD(uint64_t fw_version, 0x18);TYPE_FIELD(uint8_t digest[SELF_DIGEST_SIZE], 0x20);TYPE_END();#define SIZEOF_SELF_AUTH_INFO 0x88 // sceSblAuthMgrIsLoadable2:bzero(auth_info, 0x88)TYPE_BEGIN(struct self_auth_info, SIZEOF_SELF_AUTH_INFO);TYPE_FIELD(uint64_t paid, 0x00);TYPE_FIELD(uint64_t caps[4], 0x08);TYPE_FIELD(uint64_t attrs[4], 0x28);TYPE_FIELD(uint8_t unk[0x40], 0x48);TYPE_END();#define SIZEOF_SELF_FAKE_AUTH_INFO (sizeof(uint64_t) + SIZEOF_SELF_AUTH_INFO)TYPE_BEGIN(struct self_fake_auth_info, SIZEOF_SELF_FAKE_AUTH_INFO);TYPE_FIELD(uint64_t size, 0x00);TYPE_FIELD(struct self_auth_info info, 0x08);TYPE_END();int sceSblAuthMgrGetSelfInfo(struct self_context* ctx, struct self_ex_info** info);int sceSblAuthMgrIsLoadable2(struct self_context* ctx, struct self_auth_info* old_auth_info, int path_id, struct self_auth_info* new_auth_info);void sceSblAuthMgrSmStart(void);int sceSblAuthMgrSmVerifyHeader(struct self_context* ctx);static inline int sceSblAuthMgrGetElfHeader(struct self_context* ctx, struct elf64_ehdr** ehdr) {struct self_header* self_hdr;struct elf64_ehdr* elf_hdr;size_t pdata_size;if (ctx->format == SELF_FORMAT_ELF) {elf_hdr = (struct elf64_ehdr*)ctx->header;if (ehdr)*ehdr = elf_hdr;return 0;} else if (ctx->format == SELF_FORMAT_SELF) {self_hdr = (struct self_header*)ctx->header;pdata_size = self_hdr->header_size - sizeof(struct self_entry) * self_hdr->num_entries - sizeof(struct self_header);if (pdata_size >= sizeof(struct elf64_ehdr) && (pdata_size & 0xF) == 0) {elf_hdr = (struct elf64_ehdr*)((uint8_t*)self_hdr + sizeof(struct self_header) + sizeof(struct self_entry) * self_hdr->num_entries);if (ehdr)*ehdr = elf_hdr;return 0;}return -37;}return -35;}int kern_get_self_auth_info(struct thread* td, const char* path, enum uio_seg pathseg, struct self_auth_info* info);//...#define CONTENT_KEY_SEED_SIZE 0x10#define SELF_KEY_SEED_SIZE 0x10#define EEKC_SIZE 0x20struct ekc {uint8_t content_key_seed[CONTENT_KEY_SEED_SIZE];uint8_t self_key_seed[SELF_KEY_SEED_SIZE];};#define SIZEOF_SBL_KEY_DESC 0x7C // sceSblKeymgrSetKeyunion sbl_key_desc {struct {uint16_t cmd;uint16_t pad;uint8_t key[0x20];uint8_t seed[0x10];} pfs;//...uint8_t raw[SIZEOF_SBL_KEY_DESC];};TYPE_CHECK_SIZE(union sbl_key_desc, SIZEOF_SBL_KEY_DESC);#define SIZEOF_SBL_KEY_RBTREE_ENTRY 0xA8 // sceSblKeymgrSetKey#define TYPE_SBL_KEY_RBTREE_ENTRY_DESC_OFFSET 0x04#define TYPE_SBL_KEY_RBTREE_ENTRY_LOCKED_OFFSET 0x80TYPE_BEGIN(struct sbl_key_rbtree_entry, SIZEOF_SBL_KEY_RBTREE_ENTRY);TYPE_FIELD(uint32_t handle, 0x00);TYPE_FIELD(union sbl_key_desc desc, TYPE_SBL_KEY_RBTREE_ENTRY_DESC_OFFSET);TYPE_FIELD(uint32_t locked, TYPE_SBL_KEY_RBTREE_ENTRY_LOCKED_OFFSET);TYPE_FIELD(struct sbl_key_rbtree_entry* left, 0x88);TYPE_FIELD(struct sbl_key_rbtree_entry* right, 0x90);TYPE_FIELD(struct sbl_key_rbtree_entry* parent, 0x98);TYPE_FIELD(uint32_t set, 0xA0);TYPE_END();#define RIF_DIGEST_SIZE 0x10#define RIF_DATA_SIZE 0x90#define RIF_KEY_TABLE_SIZE 0x230#define RIF_MAX_KEY_SIZE 0x20#define RIF_PAYLOAD_SIZE (RIF_DIGEST_SIZE + RIF_DATA_SIZE)#define SIZEOF_ACTDAT 0x200TYPE_BEGIN(struct actdat, SIZEOF_ACTDAT);TYPE_FIELD(uint32_t magic, 0x00);TYPE_FIELD(uint16_t version_major, 0x04);TYPE_FIELD(uint16_t version_minor, 0x06);TYPE_FIELD(uint64_t account_id, 0x08);TYPE_FIELD(uint64_t start_time, 0x10);TYPE_FIELD(uint64_t end_time, 0x18);TYPE_FIELD(uint64_t flags, 0x20);TYPE_FIELD(uint32_t unk3, 0x28);TYPE_FIELD(uint32_t unk4, 0x2C);TYPE_FIELD(uint8_t open_psid_hash[0x20], 0x60);TYPE_FIELD(uint8_t static_per_console_data_1[0x20], 0x80);TYPE_FIELD(uint8_t digest[0x10], 0xA0);TYPE_FIELD(uint8_t key_table[0x20], 0xB0);TYPE_FIELD(uint8_t static_per_console_data_2[0x10], 0xD0);TYPE_FIELD(uint8_t static_per_console_data_3[0x20], 0xE0);TYPE_FIELD(uint8_t signature[0x100], 0x100);TYPE_END();#define SIZEOF_RIF 0x400TYPE_BEGIN(struct rif, SIZEOF_RIF);TYPE_FIELD(uint32_t magic, 0x00);TYPE_FIELD(uint16_t version_major, 0x04);TYPE_FIELD(uint16_t version_minor, 0x06);TYPE_FIELD(uint64_t account_id, 0x08);TYPE_FIELD(uint64_t start_time, 0x10);TYPE_FIELD(uint64_t end_time, 0x18);TYPE_FIELD(char content_id[0x30], 0x20);TYPE_FIELD(uint16_t format, 0x50);TYPE_FIELD(uint16_t drm_type, 0x52);TYPE_FIELD(uint16_t content_type, 0x54);TYPE_FIELD(uint16_t sku_flag, 0x56);TYPE_FIELD(uint64_t content_flags, 0x58);TYPE_FIELD(uint32_t iro_tag, 0x60);TYPE_FIELD(uint32_t ekc_version, 0x64);TYPE_FIELD(uint16_t unk3, 0x6A);TYPE_FIELD(uint16_t unk4, 0x6C);TYPE_FIELD(uint8_t digest[0x10], 0x260);TYPE_FIELD(uint8_t data[RIF_DATA_SIZE], 0x270);TYPE_FIELD(uint8_t signature[0x100], 0x300);TYPE_END();union keymgr_payload {struct {uint32_t cmd;uint32_t status;uint64_t data;};uint8_t buf[0x80];};union keymgr_request {struct {uint32_t type;uint8_t key[RIF_MAX_KEY_SIZE];uint8_t data[RIF_DIGEST_SIZE + RIF_DATA_SIZE];} decrypt_rif;};union keymgr_response {struct {uint32_t type;uint8_t key[RIF_MAX_KEY_SIZE];uint8_t data[RIF_DIGEST_SIZE + RIF_DATA_SIZE];} decrypt_rif;};/* TODO: you should point it to its location in a kernel's memory (see a code of sceSblKeymgrSetKey(), just before a loop there is something like this: mov rdx, cs:xxx go there, it's your target */struct sbl_key_rbtree_entry** sbl_keymgr_key_rbtree;static inline struct sbl_key_rbtree_entry* sceSblKeymgrGetKey(unsigned int handle) {struct sbl_key_rbtree_entry* entry = *sbl_keymgr_key_rbtree;while (entry) {if (entry->handle < handle)entry = entry->right;else if (entry->handle > handle)entry = entry->left;else if (entry->handle == handle)return entry;}return NULL;}int sceSblKeymgrSmCallfunc(union keymgr_payload* payload);//...#define EKPFS_SIZE 0x20#define EEKPFS_SIZE 0x100#define PFS_SEED_SIZE 0x10#define PFS_FINAL_KEY_SIZE 0x20#define SIZEOF_PFS_KEY_BLOB 0x158struct pfs_key_blob {uint8_t ekpfs[EKPFS_SIZE];uint8_t eekpfs[EEKPFS_SIZE];struct ekc eekc;uint32_t key_ver;uint32_t pubkey_ver;uint32_t type;uint32_t finalized;uint32_t is_disc;uint32_t pad;};typedef struct pfs_key_blob pfs_key_blob_t;TYPE_CHECK_SIZE(pfs_key_blob_t, SIZEOF_PFS_KEY_BLOB);int sceSblPfsKeymgrGenEKpfsForGDGPAC(struct pfs_key_blob* key_blob);int sceSblPfsSetKey(uint32_t* ekh, uint32_t* skh, uint8_t* key, uint8_t* iv, int type, int unused, uint8_t is_disc);int sceSblPfsClearKey(uint32_t ekh, uint32_t skh);//...#define CCP_MAX_PAYLOAD_SIZE 0x88#define CCP_OP(cmd) (cmd >> 24)#define CCP_OP_XTS 2#define CCP_OP_HMAC 9#define CCP_USE_KEY_HANDLE (1 << 20)struct ccp_link {void* p;};struct ccp_msg {union ccp_op op;uint32_t index;uint32_t result;TAILQ_ENTRY(ccp_msg) next;uint64_t message_id;LIST_ENTRY(ccp_link) links;};struct ccp_req {TAILQ_HEAD(, ccp_msg) msgs;void (*cb)(void* arg, int result);void* arg;uint64_t message_id;LIST_ENTRY(ccp_link) links;};union ccp_op {struct {uint32_t cmd;uint32_t status;} common;//...uint8_t buf[CCP_MAX_PAYLOAD_SIZE];};//...#define SBL_MSG_SERVICE_MAILBOX_MAX_SIZE 0x80int sceSblServiceMailbox(unsigned long service_id, uint8_t request[SBL_MSG_SERVICE_MAILBOX_MAX_SIZE], uint8_t response);int sceSblServiceCrypt(struct ccp_req* request);int sceSblServiceCryptAsync(struct ccp_req* request);//...struct sbl_mapped_page_group;#define SIZEOF_SBL_MAP_LIST_ENTRY 0x50 // sceSblDriverMapPagesTYPE_BEGIN(struct sbl_map_list_entry, SIZEOF_SBL_MAP_LIST_ENTRY);TYPE_FIELD(struct sbl_map_list_entry* next, 0x00);TYPE_FIELD(struct sbl_map_list_entry* prev, 0x08);TYPE_FIELD(unsigned long cpu_va, 0x10);TYPE_FIELD(unsigned int num_page_groups, 0x18);TYPE_FIELD(unsigned long gpu_va, 0x20);TYPE_FIELD(struct sbl_mapped_page_group* page_groups, 0x28);TYPE_FIELD(unsigned int num_pages, 0x30);TYPE_FIELD(unsigned long flags, 0x38);TYPE_FIELD(struct proc* proc, 0x40);TYPE_FIELD(void* vm_page, 0x48);TYPE_END();static inline struct sbl_map_list_entry* sceSblDriverFindMappedPageListByGpuVa(vm_offset_t gpu_va) {struct sbl_map_list_entry* entry;if (!gpu_va)return NULL;entry = *sbl_driver_mapped_pages;while (entry) {if (entry->gpu_va == gpu_va)return entry;entry = entry->next;}return NULL;}/* TODO: you should point it to its location in a kernel's memory (see a code of sceSblDriverGvmInitialize(), just before a call to mtx_init() there is something like this: mov cs:xxx, 0 go there, it's your target */struct sbl_map_list_entry** sbl_driver_mapped_pages;static inline struct sbl_map_list_entry* sceSblDriverFindMappedPageListByCpuVa(vm_offset_t cpu_va) {struct sbl_map_list_entry* entry;if (!cpu_va)return NULL;entry = *sbl_driver_mapped_pages;while (entry) {if (entry->cpu_va == cpu_va)return entry;entry = entry->next;}return NULL;}static inline vm_offset_t sceSblDriverGpuVaToCpuVa(vm_offset_t gpu_va, size_t* num_page_groups) {struct sbl_map_list_entry* entry = sceSblDriverFindMappedPageListByGpuVa(gpu_va);if (!entry)return 0;if (num_page_groups)*num_page_groups = entry->num_page_groups;return entry->cpu_va;}//...struct rsa_buffer {uint8_t* ptr;size_t size;};#define SIZEOF_RSA_KEY 0x48TYPE_BEGIN(struct rsa_key, SIZEOF_RSA_KEY);TYPE_FIELD(uint8_t* p, 0x20);TYPE_FIELD(uint8_t* q, 0x28);TYPE_FIELD(uint8_t* dmp1, 0x30);TYPE_FIELD(uint8_t* dmq1, 0x38);TYPE_FIELD(uint8_t* iqmp, 0x40);TYPE_END();int AesCbcCfb128Encrypt(uint8_t* out, const uint8_t* in, size_t data_size, const uint8_t* key, int key_size, uint8_t* iv);int AesCbcCfb128Decrypt(uint8_t* out, const uint8_t* in, size_t data_size, const uint8_t* key, int key_size, uint8_t* iv);void Sha256Hash(uint8_t hash[0x20], const uint8_t* data, size_t data_size);void Sha256Hmac(uint8_t hash[0x20], const uint8_t* data, size_t data_size, const uint8_t* key, int key_size);int RsaesPkcs1v15Enc2048(struct rsa_buffer* out, struct rsa_buffer* in, struct rsa_key* key);int RsaesPkcs1v15Dec2048CRT(struct rsa_buffer* out, struct rsa_buffer* in, struct rsa_key* key);//...void build_iovec(struct iovec** iov, int* iovlen, const char* name, const void* val, size_t len) {int i;if (*iovlen < 0)return;i = *iovlen;*iov = realloc(*iov, sizeof **iov * (i + 2));if (*iov == NULL) {*iovlen = -1;return;}(*iov)[i].iov_base = strdup(name);(*iov)[i].iov_len = strlen(name) + 1;++i;(*iov)[i].iov_base = (void*)val;if (len == (size_t)-1) {if (val != NULL)len = strlen(val) + 1;elselen = 0;}(*iov)[i].iov_len = (int)len;*iovlen = ++i;}//...struct proc* proc_find_by_name(const char* name) {struct proc* p;if (!name)return NULL;sx_slock(allproc_lock);FOREACH_PROC_IN_SYSTEM(p) {PROC_LOCK(p);if (strncmp(p->p_comm, name, sizeof(p->p_comm)) == 0) {PROC_UNLOCK(p);goto done;}PROC_UNLOCK(p);}p = NULL;done:sx_sunlock(allproc_lock);return p;}int proc_get_vm_map(struct proc* p, struct proc_vm_map_entry** entries, size_t* num_entries) {struct vmspace* vm;struct proc_vm_map_entry* info = NULL;vm_map_t map;vm_map_entry_t entry;size_t n, i;int ret;if (!p) {ret = EINVAL;goto error;}if (!entries) {ret = EINVAL;goto error;}if (!num_entries) {ret = EINVAL;goto error;}PROC_LOCK(p);if (p->p_flag & P_WEXIT) {PROC_UNLOCK(p);ret = ESRCH;goto error;}_PHOLD(p);PROC_UNLOCK(p);vm = vmspace_acquire_ref(p);if (!vm) {PRELE(p);ret = ESRCH;goto error;}map = &vm->vm_map;vm_map_lock_read(map);for (entry = map->header.next, n = 0; entry != &map->header; entry = entry->next) {if (entry->eflags & MAP_ENTRY_IS_SUB_MAP)continue;++n;}if (n == 0)goto done;info = (struct proc_vm_map_entry*)alloc(n * sizeof(*info));if (!info) {vm_map_unlock_read(map);vmspace_free(vm);PRELE(p);ret = ENOMEM;goto error;}memset(info, 0, n * sizeof(*info));for (entry = map->header.next, i = 0; entry != &map->header; entry = entry->next) {if (entry->eflags & MAP_ENTRY_IS_SUB_MAP)continue;info[i].start = entry->start;info[i].end = entry->end;info[i].offset = entry->offset;info[i].prot = 0;if (entry->protection & VM_PROT_READ)info[i].prot |= PROT_READ;if (entry->protection & VM_PROT_WRITE)info[i].prot |= PROT_WRITE;if (entry->protection & VM_PROT_EXECUTE)info[i].prot |= PROT_EXEC;++i;}done:vm_map_unlock_read(map);vmspace_free(vm);PRELE(p);*num_entries = n;*entries = info;info = NULL;ret = 0;error:if (info)dealloc(info);return ret;}int proc_rw_mem(struct proc* p, void* ptr, size_t size, void* data, size_t* n, int write) {struct thread* td = curthread();struct iovec iov;struct uio uio;int ret;if (!p) {ret = EINVAL;goto error;}if (size == 0) {if (n)*n = 0;ret = 0;goto error;}memset(&iov, 0, sizeof(iov));iov.iov_base = (caddr_t)data;iov.iov_len = size;memset(&uio, 0, sizeof(uio));uio.uio_iov = &iov;uio.uio_iovcnt = 1;uio.uio_offset = (off_t)ptr;uio.uio_resid = (ssize_t)size;uio.uio_segflg = UIO_SYSSPACE;uio.uio_rw = write ? UIO_WRITE : UIO_READ;uio.uio_td = td;ret = proc_rwmem(p, &uio);if (n)*n = (size_t)((ssize_t)size - uio.uio_resid);error:return ret;}//...static inline int proc_write_mem(struct proc* p, void* ptr, size_t size, void* data, size_t* n) {return proc_rw_mem(p, ptr, size, data, n, 1);}
Package repacking issues
Sony introduced the PlayGo system into the PS4, which allows games to use chunk read. This allows convenient features for the user. For example, if you want to play the multi-player aspect of a game, you don’t need to wait for the single-player aspect to download/install, or you don’t need to waste time by looking at the screen whilst game tries to load chinese language if you’re not living in China.
Developers may specify which files belong to what chunks, and which chunks should be loaded by one or more scenarios. This information is used by the Orbis Publishing Tools to build a package where files are stored in a specific order. And game could use an API to control PlayGo system (for example, which scenario should be loaded at some specific event, etc). Some information about used scenarios in a game are stored inside a package file, also there is some information about which languages are used in specific stuff (however, there is a loss of some information about them during a build process).
So, this is a problem for those of us who want to repack an original package. We MUST know this information or we MUST be able to recover this information somehow if we want to rebuild a package that will work. Otherwise we may see glitches, crashes or some other weird bugs, but some games could just work normally if they don’t rely on this. I believe this is the main reason why a lot of games are not working (or working with some troubles) using the previous PlayRoom-based method, because PlayRoom uses a simple file layout. So, to workaround it we need to make some guesses based on package file’s structure and it works, you could even do it semi-automatically (this is what my tool does). But to do this properly you need to get the inner PFS image (called pfs_image.dat
). This is basically for games that use complex scenarios, and don’t use just one chunk and one default scenario. I have seen a lot of games that uses only one chunk/one scenario and they will just work as is without the need of doing something else, so just repack them and they will work fine (one of such game is DriveClub). But I want to discuss other games that brings these problems I’m talking about.
Here I should note that Orbis OS does handling of package decryption and decompression (yes, packages may be also compressed if there is a need for it, developers could specify that too) transparently, and OS will mount game contents into a specific directory inside the file system and also «stores» an inner PFS image in another directory. But as I’ve mentioned already, it’s a decrypted and decompressed image file. However, if you want to get information about chunk placements inside a package file, you need to know their real offsets there, such offsets that are used by Orbis OS internally when it does decryption/decompression of package file to locate file data. Later, using these offsets you may recover chunks information and build a proper layout of package file. But you can’t get these real offsets if you’re operating on an inner PFS file that was already decompressed for us by the system. So, decryption IS okay but decompression IS bad, thus you need a way to get a decrypted raw (compressed) pfs_image.dat
file. It could be doable with custom kernel code that uses some buil-tin PFS functions from the kernel, but you also need to write custom code that will skip decompression. I think it could also be doable by logging file offsets to the original .pkg file when you try to load all files from it, then you may use this information to arrange chunks properly. Both methods should be made as PS4 applications and most likely, will require some kernel payloads.
I’m not going to implement that tool because, naturally speaking, it’s a game’s dumper that does a slightly more than just copy pfs_image.dat
whilst some game is running, so I don’t want legal troubles, but it’s not a rocket science, so someone with the knowledge and skills could implement that. However, I could help by releasing a part of code from my tool that does SFO file parsing, PlayGo chunk builder and finally, GP4 project generation. Someone may use it to implement a similar application.
SFO file parsing
System file object (SFO) is used to store some parameters related to the application/game that are used by the system, so this code is used to parse it.
sfo.h
#pragma once#include "common.h"enum sfo_value_format {SFO_FORMAT_STRING_SPECIAL = 0x004,SFO_FORMAT_STRING = 0x204,SFO_FORMAT_UINT32 = 0x404,};struct sfo_entry {char* key;size_t size;size_t area;void* value;enum sfo_value_format format;struct sfo_entry* next;struct sfo_entry* prev;};struct sfo {struct sfo_entry* entries;};struct sfo* sfo_alloc(void);void sfo_free(struct sfo* sfo);int sfo_load_from_memory(struct sfo* sfo, const void* data, size_t data_size);struct sfo_entry* sfo_find_entry(struct sfo* sfo, const char* key);void sfo_dump_entries(struct sfo* sfo);
sfo.c
#include "sfo.h"#include "util.h"#include <utlist.h>#define SFO_MAGIC "\0PSF"#define SFO_HEADER_SIZE 0x14#define SFO_TABLE_ENTRY_SIZE 0x10TYPE_BEGIN(struct sfo_header, SFO_HEADER_SIZE);TYPE_FIELD(char magic[4], 0x00);TYPE_FIELD(uint32_t version, 0x04);TYPE_FIELD(uint32_t key_table_offset, 0x08);TYPE_FIELD(uint32_t value_table_offset, 0x0C);TYPE_FIELD(uint32_t entry_count, 0x10);TYPE_END();CT_SIZE_ASSERT(struct sfo_header, SFO_HEADER_SIZE);TYPE_BEGIN(struct sfo_table_entry, SFO_TABLE_ENTRY_SIZE);TYPE_FIELD(uint16_t key_offset, 0x00);TYPE_FIELD(uint16_t format, 0x02);TYPE_FIELD(uint32_t size, 0x04);TYPE_FIELD(uint32_t max_size, 0x08);TYPE_FIELD(uint32_t value_offset, 0x0C);TYPE_END();CT_SIZE_ASSERT(struct sfo_table_entry, SFO_TABLE_ENTRY_SIZE);struct sfo* sfo_alloc(void) {struct sfo* sfo = NULL;sfo = (struct sfo*)malloc(sizeof(*sfo));if (!sfo)goto error;memset(sfo, 0, sizeof(*sfo));return sfo;error:if (sfo)free(sfo);return NULL;}void sfo_free(struct sfo* sfo) {struct sfo_entry* entry;struct sfo_entry* tmp;if (!sfo)return;DL_FOREACH_SAFE(sfo->entries, entry, tmp) {DL_DELETE(sfo->entries, entry);if (entry->key)free(entry->key);if (entry->value)free(entry->value);free(entry);}free(sfo);}int sfo_load_from_memory(struct sfo* sfo, const void* data, size_t data_size) {struct sfo_header* hdr;struct sfo_table_entry* entry_table;struct sfo_table_entry* entry;struct sfo_entry* entries = NULL;struct sfo_entry* new_entry = NULL;const char* key_table;const uint8_t* value_table;size_t entry_count, i;int status = 0;assert(sfo != NULL);assert(data != NULL);if (data_size < sizeof(*hdr)) {warning("Insufficient data.");goto error;}hdr = (struct sfo_header*)data;if (memcmp(hdr->magic, SFO_MAGIC, sizeof(hdr->magic)) != 0) {warning("Invalid system file object format.");goto error;}entry_table = (struct sfo_table_entry*)(data + sizeof(*hdr));entry_count = LE32(hdr->entry_count);if (data_size < sizeof(*hdr) + entry_count * sizeof(*entry_table)) {warning("Insufficient data.");goto error;}key_table = (const char*)data + LE32(hdr->key_table_offset);value_table = (const uint8_t*)data + LE32(hdr->value_table_offset);for (i = 0; i < entry_count; ++i) {entry = entry_table + i;new_entry = (struct sfo_entry*)malloc(sizeof(*new_entry));if (!new_entry) {warning("Unable to allocate memory for entry.");goto error;}memset(new_entry, 0, sizeof(*new_entry));new_entry->format = (enum sfo_value_format)LE16(entry->format);new_entry->size = LE32(entry->size);new_entry->area = LE32(entry->max_size);if (new_entry->area < new_entry->size) {warning("Unexpected entry sizes.");goto error;}new_entry->key = strdup(key_table + LE16(entry->key_offset));if (!new_entry->key) {warning("Unable to allocate memory for entry key.");goto error;}new_entry->value = (uint8_t*)malloc(new_entry->area);if (!new_entry->value) {warning("Unable to allocate memory for entry value.");goto error;}memset(new_entry->value, 0, new_entry->area);memcpy(new_entry->value, value_table + LE16(entry->value_offset), new_entry->size);DL_APPEND(entries, new_entry);}new_entry = NULL;sfo->entries = entries;status = 1;error:if (new_entry) {if (new_entry->key)free(new_entry->key);if (new_entry->value)free(new_entry->value);free(new_entry);}return status;}struct sfo_entry* sfo_find_entry(struct sfo* sfo, const char* key) {struct sfo_entry* entry;assert(sfo != NULL);assert(key != NULL);DL_FOREACH(sfo->entries, entry) {if (strcmp(entry->key, key) == 0)return entry;}return NULL;}void sfo_dump_entries(struct sfo* sfo) {struct sfo_entry* entry;FILE* fp = stdout;size_t index = 0;assert(sfo != NULL);DL_FOREACH(sfo->entries, entry) {fprintf(fp, "Entry: %s (#%" PRIuMAX "):", entry->key, (uintmax_t)index);if (entry->value)fprintf_hex(fp, entry->value, entry->size, 2);elsefprintf(fp, " no value\n");++index;}}
PlayGo chunk builder
Extracting and recovering chunks (and other) information about files stored inside package file.
playgo.h
#pragma once#include "common.h"#include <utstring.h>#define PLAYGO_CONTENT_ID_SIZE 0x30#define PLAYGO_MAX_IMAGES 2#define PLAYGO_MAX_DISCS 2#define PLAYGO_MAX_LAYERS_FOR_DISC1 2#define PLAYGO_MAX_LAYERS_FOR_DISC2 1#define PLAYGO_MAX_CHUNKS 1000#define PLAYGO_MAX_MCHUNKS 8000#define PLAYGO_MAX_SCENARIOS 32#define PLAYGO_MAX_LANGUAGES 64#define PLAYGO_SCENARIO_TYPE_SP 1#define PLAYGO_SCENARIO_TYPE_MP 2#define PLAYGO_SCENARIO_TYPE_USER_00 (16 + 1)#define PLAYGO_ALL_LANGUAGES_MASK UINT64_C(0xFFFFFFFFFFFFFFFF)struct playgo {uint16_t version_major;uint16_t version_minor;uint32_t file_size;uint32_t sdk_version;uint16_t attrib;uint16_t image_count;uint16_t chunk_count;uint16_t mchunk_count;uint16_t scenario_count;uint16_t disc_count;uint16_t layer_bmp;uint16_t default_scenario_id;char content_id[PLAYGO_CONTENT_ID_SIZE + 1];struct playgo_scenario_attr_desc* scenario_attrs;struct playgo_chunk_attr_desc* chunk_attrs;struct playgo_mchunk_attr_desc* mchunk_attrs;struct playgo_mchunk_attr_desc* inner_mchunk_attrs;};struct playgo_lang_desc {unsigned int code;const char* name;const char* iso1;const char* iso2;};enum {PLAYGO_LANG_JAPANESE = 0,PLAYGO_LANG_ENGLISH_US = 1,PLAYGO_LANG_FRENCH = 2,PLAYGO_LANG_SPANISH = 3,PLAYGO_LANG_GERMAN = 4,PLAYGO_LANG_ITALIAN = 5,PLAYGO_LANG_DUTCH = 6,PLAYGO_LANG_PORTUGUESE_PT = 7,PLAYGO_LANG_RUSSIAN = 8,PLAYGO_LANG_KOREAN = 9,PLAYGO_LANG_CHINESE_TRADITIONAL = 10,PLAYGO_LANG_CHINESE_SIMPLIFIED = 11,PLAYGO_LANG_FINNISH = 12,PLAYGO_LANG_SWEDISH = 13,PLAYGO_LANG_DANISH = 14,PLAYGO_LANG_NORWEGIAN = 15,PLAYGO_LANG_POLISH = 16,PLAYGO_LANG_PORTUGUESE_BR = 17,PLAYGO_LANG_ENGLISH_UK = 18,PLAYGO_LANG_TURKISH = 19,PLAYGO_LANG_SPANISH_LA = 20,PLAYGO_LANG_ARABIC = 21,PLAYGO_LANG_FRENCH_CA = 22,PLAYGO_LANG_USER_00 = 48,PLAYGO_LANG_USER_01 = 49,PLAYGO_LANG_USER_02 = 50,PLAYGO_LANG_USER_03 = 51,PLAYGO_LANG_USER_04 = 52,PLAYGO_LANG_USER_05 = 53,PLAYGO_LANG_USER_06 = 54,PLAYGO_LANG_USER_07 = 55,PLAYGO_LANG_USER_08 = 56,PLAYGO_LANG_USER_09 = 57,PLAYGO_LANG_USER_10 = 58,PLAYGO_LANG_USER_11 = 59,PLAYGO_LANG_USER_12 = 60,PLAYGO_LANG_USER_13 = 61,PLAYGO_LANG_USER_14 = 62,};enum {PLAYGO_LOCUS_NOT_DOWNLOADED = 0,PLAYGO_LOCUS_LOCAL_SLOW = 2,PLAYGO_LOCUS_LOCAL_FAST = 3,};enum {PLAYGO_INSTALL_SPEED_SUSPENDED = 0,PLAYGO_INSTALL_SPEED_TRICKLE = 1,PLAYGO_INSTALL_SPEED_FULL = 2,};struct playgo_scenario_attr_desc {unsigned int type;unsigned int initial_chunk_count;unsigned int chunk_count;char* label;uint16_t* chunks;};struct playgo_chunk_attr_desc {unsigned int flag;unsigned int disc_no;unsigned int layer_no;unsigned int image_no;unsigned int req_locus;unsigned int mchunk_count;uint64_t language_mask;char* label;uint16_t* mchunks;};struct playgo_mchunk_attr_desc {uint64_t offset;uint64_t size;unsigned int image_no;};struct playgo* playgo_alloc(void);void playgo_free(struct playgo* plgo);int playgo_load_from_memory(struct playgo* plgo, const void* data, size_t data_size);int playgo_get_chunks(struct playgo* plgo, uint64_t offset, uint64_t size, uint16_t** chunks, size_t* nchunks);int playgo_get_languages(struct playgo* plgo, UT_string* supported_langs_str, UT_string* def_lang_str, int* use_all_langs);int playgo_get_chunk_languages(struct playgo* plgo, size_t index, UT_string* langs_str);void playgo_dump(struct playgo* plgo);const struct playgo_lang_desc* playgo_get_lang_by_code(unsigned int code);
playgo.c
#include "playgo.h"#include "util.h"#include <utarray.h>#define PLAYGO_MAGIC "plgo"#define PLAYGO_HEADER_SIZE 0x100#define PLAYGO_SCENARIO_ATTRIBUTE_ENTRY_SIZE 0x20#define PLAYGO_CHUNK_ATTRIBUTE_ENTRY_SIZE 0x20#define PLAYGO_MCHUNK_ATTRIBUTE_ENTRY_SIZE 0x10static const struct playgo_lang_desc s_languages[] = {{ PLAYGO_LANG_JAPANESE, "Japanese", "ja", NULL },{ PLAYGO_LANG_ENGLISH_US, "English", "en-US", "en" },{ PLAYGO_LANG_FRENCH, "French", "fr", NULL },{ PLAYGO_LANG_SPANISH, "Spanish", "es-ES", "es" },{ PLAYGO_LANG_GERMAN, "German", "de", NULL },{ PLAYGO_LANG_ITALIAN, "Italian", "it", NULL },{ PLAYGO_LANG_DUTCH, "Dutch", "nl", NULL },{ PLAYGO_LANG_PORTUGUESE_PT, "Portuguese", "pt-PT", "pt" },{ PLAYGO_LANG_RUSSIAN, "Russian", "ru", NULL },{ PLAYGO_LANG_KOREAN, "Korean", "ko", NULL },{ PLAYGO_LANG_CHINESE_TRADITIONAL, "Trad.Chinese", "zh-Hant", NULL },{ PLAYGO_LANG_CHINESE_SIMPLIFIED, "Simp.Chinese", "zh-Hans", NULL },{ PLAYGO_LANG_FINNISH, "Finnish", "fi", NULL },{ PLAYGO_LANG_SWEDISH, "Swedish", "sv", NULL },{ PLAYGO_LANG_DANISH, "Danish", "da", NULL },{ PLAYGO_LANG_NORWEGIAN, "Norwegian", "no", NULL },{ PLAYGO_LANG_POLISH, "Polish", "pl", NULL },{ PLAYGO_LANG_PORTUGUESE_BR, "Braz.Portuguese", "pt-BR", NULL },{ PLAYGO_LANG_ENGLISH_UK, "UK English", "en-GB", NULL },{ PLAYGO_LANG_TURKISH, "Turkish", "tr", NULL },{ PLAYGO_LANG_SPANISH_LA, "Latin American Spanish", "es-LA", NULL },{ PLAYGO_LANG_ARABIC, "Arabic", "ar", NULL },{ PLAYGO_LANG_FRENCH_CA, "Canadian French", "fr-CA", NULL },{ PLAYGO_LANG_USER_00, "User-defined Language #0", "user00", NULL },{ PLAYGO_LANG_USER_01, "User-defined Language #1", "user01", NULL },{ PLAYGO_LANG_USER_02, "User-defined Language #2", "user02", NULL },{ PLAYGO_LANG_USER_03, "User-defined Language #3", "user03", NULL },{ PLAYGO_LANG_USER_04, "User-defined Language #4", "user04", NULL },{ PLAYGO_LANG_USER_05, "User-defined Language #5", "user05", NULL },{ PLAYGO_LANG_USER_06, "User-defined Language #6", "user06", NULL },{ PLAYGO_LANG_USER_07, "User-defined Language #7", "user07", NULL },{ PLAYGO_LANG_USER_08, "User-defined Language #8", "user08", NULL },{ PLAYGO_LANG_USER_09, "User-defined Language #9", "user09", NULL },{ PLAYGO_LANG_USER_10, "User-defined Language #10", "user10", NULL },{ PLAYGO_LANG_USER_11, "User-defined Language #11", "user11", NULL },{ PLAYGO_LANG_USER_12, "User-defined Language #12", "user12", NULL },{ PLAYGO_LANG_USER_13, "User-defined Language #13", "user13", NULL },{ PLAYGO_LANG_USER_14, "User-defined Language #14", "user14", NULL },};TYPE_BEGIN(struct playgo_scenario_attr_entry, PLAYGO_SCENARIO_ATTRIBUTE_ENTRY_SIZE);TYPE_FIELD(uint8_t type, 0x00);TYPE_FIELD(uint16_t initial_chunk_count, 0x14);TYPE_FIELD(uint16_t chunk_count, 0x16);TYPE_FIELD(uint32_t chunks_offset, 0x18);TYPE_FIELD(uint32_t label_offset, 0x1C);TYPE_END();CT_SIZE_ASSERT(struct playgo_scenario_attr_entry, PLAYGO_SCENARIO_ATTRIBUTE_ENTRY_SIZE);TYPE_BEGIN(struct playgo_chunk_attr_entry, PLAYGO_CHUNK_ATTRIBUTE_ENTRY_SIZE);TYPE_FIELD(uint8_t flag, 0x00);TYPE_FIELD(uint8_t image_disc_layer_no, 0x01);TYPE_FIELD(uint8_t req_locus, 0x02);TYPE_FIELD(uint16_t mchunk_count, 0x0E);TYPE_FIELD(uint64_t language_mask, 0x10);TYPE_FIELD(uint32_t mchunks_offset, 0x18);TYPE_FIELD(uint32_t label_offset, 0x1C);TYPE_END();CT_SIZE_ASSERT(struct playgo_chunk_attr_entry, PLAYGO_CHUNK_ATTRIBUTE_ENTRY_SIZE);union playgo_chunk_loc {struct {uint64_t offset: 48;uint64_t: 8;uint64_t image_no: 4;uint64_t: 4;};uint64_t raw;};union playgo_chunk_size {struct {uint64_t size: 48;uint64_t: 16;};uint64_t raw;};TYPE_BEGIN(struct playgo_mchunk_attr_entry, PLAYGO_MCHUNK_ATTRIBUTE_ENTRY_SIZE);TYPE_FIELD(union playgo_chunk_loc loc, 0x00);TYPE_FIELD(union playgo_chunk_size size, 0x08);TYPE_END();CT_SIZE_ASSERT(struct playgo_mchunk_attr_entry, PLAYGO_MCHUNK_ATTRIBUTE_ENTRY_SIZE);TYPE_BEGIN(struct playgo_header, PLAYGO_HEADER_SIZE);TYPE_FIELD(char magic[4], 0x00);TYPE_FIELD(uint16_t version_major, 0x04);TYPE_FIELD(uint16_t version_minor, 0x06);TYPE_FIELD(uint16_t image_count, 0x08); /* [0;1] */TYPE_FIELD(uint16_t chunk_count, 0x0A); /* [0;1000] */TYPE_FIELD(uint16_t mchunk_count, 0x0C); /* [0;8000] */TYPE_FIELD(uint16_t scenario_count, 0x0E); /* [0;32] */TYPE_FIELD(uint32_t file_size, 0x10);TYPE_FIELD(uint16_t default_scenario_id, 0x14);TYPE_FIELD(uint16_t attrib, 0x16);TYPE_FIELD(uint32_t sdk_version, 0x18);TYPE_FIELD(uint16_t disc_count, 0x1C); /* [0;2] (if equals to 0 then disc count = 1) */TYPE_FIELD(uint16_t layer_bmp, 0x1E);TYPE_FIELD(uint8_t reserved[0x20], 0x20);TYPE_FIELD(char content_id[PLAYGO_CONTENT_ID_SIZE], 0x40);/* chunk attributes */TYPE_FIELD(uint32_t chunk_attrs_offset, 0xC0);TYPE_FIELD(uint32_t chunk_attrs_size, 0xC4); /* [0;32000] *//* chunk mchunks */TYPE_FIELD(uint32_t chunk_mchunks_offset, 0xC8);TYPE_FIELD(uint32_t chunk_mchunks_size, 0xCC);/* chunk labels */TYPE_FIELD(uint32_t chunk_labels_offset, 0xD0);TYPE_FIELD(uint32_t chunk_labels_size, 0xD4); /* [0;16000] *//* mchunk attributes */TYPE_FIELD(uint32_t mchunk_attrs_offset, 0xD8);TYPE_FIELD(uint32_t mchunk_attrs_size, 0xDC); /* [0;12800] *//* scenario attributes */TYPE_FIELD(uint32_t scenario_attrs_offset, 0xE0);TYPE_FIELD(uint32_t scenario_attrs_size, 0xE4); /* [0;1024] *//* scenarios chunks */TYPE_FIELD(uint32_t scenario_chunks_offset, 0xE8);TYPE_FIELD(uint32_t scenario_chunks_size, 0xEC);/* scenario labels */TYPE_FIELD(uint32_t scenario_labels_offset, 0xF0);TYPE_FIELD(uint32_t scenario_labels_size, 0xF4);/* inner mchunk attributes */TYPE_FIELD(uint32_t inner_mchunk_attrs_offset, 0xF8);TYPE_FIELD(uint32_t inner_mchunk_attrs_size, 0xFC); /* [0;12800] */TYPE_END();CT_SIZE_ASSERT(struct playgo_header, PLAYGO_HEADER_SIZE);static const UT_icd ut_uint16_icd = { sizeof(uint16_t), NULL, NULL, NULL };static void playgo_cleanup(struct playgo* plgo);static int parse_scenario_attr(struct playgo* plgo, size_t index, const struct playgo_scenario_attr_entry* entry, const uint16_t* chunks, size_t chunks_size, const char* labels, size_t labels_size);static int parse_chunk_attr(struct playgo* plgo, size_t index, const struct playgo_chunk_attr_entry* entry, const uint16_t* mchunks, size_t mchunks_size, const char* labels, size_t labels_size);static int parse_mchunk_attr(struct playgo* plgo, size_t index, const struct playgo_mchunk_attr_entry* entry, int is_inner);static inline int has_language(uint64_t language_mask, unsigned int code);struct playgo* playgo_alloc(void) {struct playgo* plgo = NULL;plgo = (struct playgo*)malloc(sizeof(*plgo));if (!plgo)goto error;memset(plgo, 0, sizeof(*plgo));return plgo;error:if (plgo)free(plgo);return NULL;}void playgo_free(struct playgo* plgo) {if (!plgo)return;playgo_cleanup(plgo);free(plgo);}int playgo_load_from_memory(struct playgo* plgo, const void* data, size_t data_size) {const uint8_t* data_raw;struct playgo_header* hdr;const struct playgo_scenario_attr_entry* scenario_attrs;const uint16_t* scenario_chunks;const char* scenario_labels;const struct playgo_chunk_attr_entry* chunk_attrs;const uint16_t* chunk_mchunks;const char* chunk_labels;const struct playgo_mchunk_attr_entry* mchunk_attrs;const struct playgo_mchunk_attr_entry* inner_mchunk_attrs;uint32_t chunk_attrs_offset, chunk_attrs_size;uint32_t chunk_mchunks_offset, chunk_mchunks_size;uint32_t chunk_labels_offset, chunk_labels_size;uint32_t mchunk_attrs_offset, mchunk_attrs_size;uint32_t scenario_attrs_offset, scenario_attrs_size;uint32_t scenario_chunks_offset, scenario_chunks_size;uint32_t scenario_labels_offset, scenario_labels_size;uint32_t inner_mchunk_attrs_offset, inner_mchunk_attrs_size;size_t i;assert(plgo != NULL);assert(data != NULL);if (data_size < sizeof(*hdr)) {warning("Insufficient data.");goto error;}data_raw = (const uint8_t*)data;hdr = (struct playgo_header*)data_raw;if (memcmp(hdr->magic, PLAYGO_MAGIC, sizeof(hdr->magic)) != 0) {warning("Invalid playgo file format.");goto error;}plgo->version_major = LE16(hdr->version_major);plgo->version_minor = LE16(hdr->version_minor);plgo->file_size = LE32(hdr->file_size);plgo->sdk_version = LE32(hdr->sdk_version);plgo->attrib = LE16(hdr->attrib);plgo->image_count = LE16(hdr->image_count);if (plgo->image_count > PLAYGO_MAX_IMAGES) {warning("Too much images in playgo file.");goto error;}plgo->chunk_count = LE16(hdr->chunk_count);if (plgo->chunk_count > PLAYGO_MAX_CHUNKS) {warning("Too much chunks in playgo file.");goto error;}plgo->mchunk_count = LE16(hdr->mchunk_count);if (plgo->mchunk_count > PLAYGO_MAX_MCHUNKS) {warning("Too much mchunks in playgo file.");goto error;}plgo->scenario_count = LE16(hdr->scenario_count);if (plgo->scenario_count > PLAYGO_MAX_SCENARIOS) {warning("Too much scenarios in playgo file.");goto error;}plgo->disc_count = LE16(hdr->disc_count);if (plgo->disc_count == 0)plgo->disc_count = 1;if (plgo->disc_count > PLAYGO_MAX_DISCS) {warning("Too much discs in playgo file.");goto error;}plgo->layer_bmp = LE16(hdr->layer_bmp);plgo->default_scenario_id = LE16(hdr->default_scenario_id);if (plgo->default_scenario_id >= plgo->scenario_count) {warning("Invalid default scenario id.");goto error;}memcpy(plgo->content_id, hdr->content_id, sizeof(plgo->content_id) - 1);plgo->content_id[sizeof(plgo->content_id) - 1] = '\0';chunk_attrs_offset = LE32(hdr->chunk_attrs_offset);chunk_attrs_size = LE32(hdr->chunk_attrs_size);if (chunk_attrs_offset + chunk_attrs_size >= plgo->file_size) {warning("Invalid chunk attributes offset or size.");goto error;}chunk_mchunks_offset = LE32(hdr->chunk_mchunks_offset);chunk_mchunks_size = LE32(hdr->chunk_mchunks_size);if (chunk_mchunks_offset + chunk_mchunks_size >= plgo->file_size) {warning("Invalid chunk mchunks offset or size.");goto error;}chunk_labels_offset = LE32(hdr->chunk_labels_offset);chunk_labels_size = LE32(hdr->chunk_labels_size);if (chunk_labels_offset + chunk_labels_size >= plgo->file_size) {warning("Invalid chunk labels offset or size.");goto error;}mchunk_attrs_offset = LE32(hdr->mchunk_attrs_offset);mchunk_attrs_size = LE32(hdr->mchunk_attrs_size);if (mchunk_attrs_offset + mchunk_attrs_size >= plgo->file_size) {warning("Invalid mchunk attributes offset or size.");goto error;}scenario_attrs_offset = LE32(hdr->scenario_attrs_offset);scenario_attrs_size = LE32(hdr->scenario_attrs_size);if (scenario_attrs_offset + scenario_attrs_size >= plgo->file_size) {warning("Invalid scenario attributes offset or size.");goto error;}scenario_chunks_offset = LE32(hdr->scenario_chunks_offset);scenario_chunks_size = LE32(hdr->scenario_chunks_size);if (scenario_chunks_offset + scenario_chunks_size >= plgo->file_size) {warning("Invalid scenario chunks offset or size.");goto error;}scenario_labels_offset = LE32(hdr->scenario_labels_offset);scenario_labels_size = LE32(hdr->scenario_labels_size);if (scenario_labels_offset + scenario_labels_size >= plgo->file_size) {warning("Invalid scenario labels offset or size.");goto error;}inner_mchunk_attrs_offset = LE32(hdr->inner_mchunk_attrs_offset);inner_mchunk_attrs_size = LE32(hdr->inner_mchunk_attrs_size);if (inner_mchunk_attrs_offset + inner_mchunk_attrs_size >= plgo->file_size) {warning("Invalid inner mchunks offset or size.");goto error;}scenario_attrs = (const struct playgo_scenario_attr_entry*)(data_raw + scenario_attrs_offset);scenario_chunks = (const uint16_t*)(data_raw + scenario_chunks_offset);scenario_labels = (const char*)(data_raw + scenario_labels_offset);chunk_attrs = (const struct playgo_chunk_attr_entry*)(data_raw + chunk_attrs_offset);chunk_mchunks = (const uint16_t*)(data_raw + chunk_mchunks_offset);chunk_labels = (const char*)(data_raw + chunk_labels_offset);mchunk_attrs = (const struct playgo_mchunk_attr_entry*)(data_raw + mchunk_attrs_offset);inner_mchunk_attrs = (const struct playgo_mchunk_attr_entry*)(data_raw + inner_mchunk_attrs_offset);plgo->scenario_attrs = (struct playgo_scenario_attr_desc*)malloc(sizeof(*plgo->scenario_attrs) * plgo->scenario_count);if (!plgo->scenario_attrs) {warning("Unable to allocate memory for scenario attributes.");goto error;}memset(plgo->scenario_attrs, 0, sizeof(*plgo->scenario_attrs) * plgo->scenario_count);{for (i = 0; i < plgo->scenario_count; ++i) {if (!parse_scenario_attr(plgo, i, scenario_attrs + i, scenario_chunks, scenario_chunks_size, scenario_labels, scenario_labels_size)) {warning("Invalid scenario attribute entry #%" PRIuMAX, (uintmax_t)i);goto error;}}}plgo->chunk_attrs = (struct playgo_chunk_attr_desc*)malloc(sizeof(*plgo->chunk_attrs) * plgo->chunk_count);if (!plgo->chunk_attrs) {warning("Unable to allocate memory for chunk attributes.");goto error;}memset(plgo->chunk_attrs, 0, sizeof(*plgo->chunk_attrs) * plgo->chunk_count);{for (i = 0; i < plgo->chunk_count; ++i) {if (!parse_chunk_attr(plgo, i, chunk_attrs + i, chunk_mchunks, chunk_mchunks_size, chunk_labels, chunk_labels_size)) {warning("Invalid chunk attribute entry #%" PRIuMAX, (uintmax_t)i);goto error;}}}plgo->mchunk_attrs = (struct playgo_mchunk_attr_desc*)malloc(sizeof(*plgo->mchunk_attrs) * plgo->mchunk_count);if (!plgo->mchunk_attrs) {warning("Unable to allocate memory for mchunk attributes.");goto error;}memset(plgo->mchunk_attrs, 0, sizeof(*plgo->mchunk_attrs) * plgo->mchunk_count);{for (i = 0; i < plgo->mchunk_count; ++i) {if (!parse_mchunk_attr(plgo, i, mchunk_attrs + i, 0)) {warning("Invalid mchunk attribute entry #%" PRIuMAX, (uintmax_t)i);goto error;}}}plgo->inner_mchunk_attrs = (struct playgo_mchunk_attr_desc*)malloc(sizeof(*plgo->inner_mchunk_attrs) * plgo->mchunk_count);if (!plgo->inner_mchunk_attrs) {warning("Unable to allocate memory for inner mchunk attributes.");goto error;}memset(plgo->inner_mchunk_attrs, 0, sizeof(*plgo->inner_mchunk_attrs) * plgo->mchunk_count);{for (i = 0; i < plgo->mchunk_count; ++i) {if (!parse_mchunk_attr(plgo, i, inner_mchunk_attrs + i, 1)) {warning("Invalid inner mchunk attribute entry #%" PRIuMAX, (uintmax_t)i);goto error;}}}return 1;error:playgo_cleanup(plgo);return 0;}int playgo_get_languages(struct playgo* plgo, UT_string* supported_langs_str, UT_string* def_lang_str, int* use_all_langs) {struct playgo_chunk_attr_desc* chunk_attr;const struct playgo_lang_desc* lang;unsigned int lang_counts[PLAYGO_MAX_LANGUAGES];unsigned int has_default_lang, first, count;uint32_t i;uint64_t j;int status = 0;assert(plgo != NULL);assert(supported_langs_str != NULL);assert(def_lang_str != NULL);utstring_clear(supported_langs_str);utstring_clear(def_lang_str);memset(lang_counts, 0, sizeof(lang_counts));for (i = 0, has_default_lang = 0, first = 1; i < plgo->chunk_count; ++i) {chunk_attr = plgo->chunk_attrs + i;if (chunk_attr->language_mask == PLAYGO_ALL_LANGUAGES_MASK)has_default_lang = 1;else {count = popcnt_64(chunk_attr->language_mask);if (count >= PLAYGO_LANG_USER_00)continue;for (j = 0; j < COUNT_OF(s_languages); ++j) {lang = s_languages + j;if (has_language(chunk_attr->language_mask, lang->code))lang_counts[lang->code]++;}}}if (has_default_lang) {lang = playgo_get_lang_by_code(PLAYGO_LANG_ENGLISH_US);assert(lang != NULL);utstring_printf(supported_langs_str, "%s%s", first ? "" : " ", lang->iso2 ? lang->iso2 : lang->iso1);utstring_printf(def_lang_str, "%s", "en"); // XXX: always use English as default languagefirst = 0;}for (i = 0, count = 0; i < COUNT_OF(lang_counts); ++i) {lang = playgo_get_lang_by_code(i);if (!lang || lang_counts[i] == 0)continue;utstring_printf(supported_langs_str, "%s%s", first ? "" : " ", lang->iso2 ? lang->iso2 : lang->iso1);++count;first = 0;}if (use_all_langs)*use_all_langs = 0;if (count == 1 && strcmp(utstring_body(supported_langs_str), "en") == 0) {utstring_clear(supported_langs_str);if (use_all_langs)*use_all_langs = 1;}status = 1;error:return status;}int playgo_get_chunk_languages(struct playgo* plgo, size_t index, UT_string* langs_str) {struct playgo_chunk_attr_desc* chunk_attr;const struct playgo_lang_desc* lang;unsigned int first, count;uint64_t i;int status = 0;assert(plgo != NULL);assert(index < plgo->chunk_count);assert(langs_str != NULL);utstring_clear(langs_str);chunk_attr = plgo->chunk_attrs + index;if (chunk_attr->language_mask != PLAYGO_ALL_LANGUAGES_MASK) {count = popcnt_64(chunk_attr->language_mask);if (count < PLAYGO_LANG_USER_00) {for (i = 0, first = 1; i < COUNT_OF(s_languages); ++i) {lang = s_languages + i;if (has_language(chunk_attr->language_mask, lang->code)) {utstring_printf(langs_str, "%s%s", first ? "" : " ", lang->iso2 ? lang->iso2 : lang->iso1);first = 0;}}}}status = 1;error:return status;}void playgo_dump(struct playgo* plgo) {struct playgo_scenario_attr_desc* scenario_attr;struct playgo_chunk_attr_desc* chunk_attr;struct playgo_mchunk_attr_desc* mchunk_attr;char type_str[32];char req_locus_str[32];uint64_t total_mchunk_size, total_inner_mchunk_size;unsigned int idx;size_t i, j;assert(plgo != NULL);info("Playgo:\n"" Version: 0x%04X.0x%04X\n"" File size: 0x%X\n"" Attributes: 0x%04X\n"" Image count: %u\n"" Disc count: %u\n"" Scenario count: %u\n"" Chunk count: %u\n"" Mchunk count: %u\n"" Layer bmp: 0x%04X\n"" SDK version: 0x%08X\n"" Default scenario id: %u\n"" Content id: %s\n",plgo->version_major, plgo->version_minor,plgo->file_size,plgo->attrib,plgo->image_count, plgo->disc_count, plgo->scenario_count, plgo->chunk_count, plgo->mchunk_count,plgo->layer_bmp,plgo->sdk_version,plgo->default_scenario_id,plgo->content_id);if (plgo->scenario_count > 0) {info(" Scenarios:");for (i = 0; i < plgo->scenario_count; ++i) {scenario_attr = plgo->scenario_attrs + i;if (scenario_attr->type == PLAYGO_SCENARIO_TYPE_SP)strncpy(type_str, "sp", sizeof(type_str));else if (scenario_attr->type == PLAYGO_SCENARIO_TYPE_MP)strncpy(type_str, "mp", sizeof(type_str));else if (scenario_attr->type >= PLAYGO_SCENARIO_TYPE_USER_00)snprintf(type_str, sizeof(type_str), "User-defined Scenario #%u", scenario_attr->type - PLAYGO_SCENARIO_TYPE_USER_00 + 1);elsesnprintf(type_str, sizeof(type_str), "%u", scenario_attr->type);info(" Scenario #%02" PRIuMAX ":\n"" Label: %s\n"" Type: %s\n"" Initial chunk count: %u\n"" Chunk count: %u",(uintmax_t)i,scenario_attr->label,type_str,scenario_attr->initial_chunk_count,scenario_attr->chunk_count);#if 1if (scenario_attr->chunk_count > 0) {info(" Chunk list:");for (j = 0; j < scenario_attr->chunk_count; ++j)info(" %u", scenario_attr->chunks[j]);}#endifinfo("");}}if (plgo->chunk_count > 0) {info(" Chunks:");for (i = 0; i < plgo->chunk_count; ++i) {chunk_attr = plgo->chunk_attrs + i;if (chunk_attr->req_locus == PLAYGO_LOCUS_NOT_DOWNLOADED)snprintf(req_locus_str, sizeof(req_locus_str), "not downloaded (%u)", chunk_attr->req_locus);else if (chunk_attr->req_locus == PLAYGO_LOCUS_LOCAL_SLOW)snprintf(req_locus_str, sizeof(req_locus_str), "slow (%u)", chunk_attr->req_locus);else if (chunk_attr->req_locus == PLAYGO_LOCUS_LOCAL_FAST)snprintf(req_locus_str, sizeof(req_locus_str), "fast (%u)", chunk_attr->req_locus);elsesnprintf(req_locus_str, sizeof(req_locus_str), "%u", chunk_attr->req_locus);info(" Chunk #%04" PRIuMAX ":\n"" Flag: 0x%02X\n"" Label: %s\n"" Disc: %u\n"" Layer: %u\n"" Image: %u\n"" Req locus: %s\n"" Mchunk count: %u\n"" Language mask: 0x%016" PRIX64,(uintmax_t)i,chunk_attr->flag,chunk_attr->label,chunk_attr->disc_no,chunk_attr->layer_no,chunk_attr->image_no,req_locus_str,chunk_attr->mchunk_count,chunk_attr->language_mask);for (j = 0, total_mchunk_size = 0, total_inner_mchunk_size = 0; j < chunk_attr->mchunk_count; ++j) {idx = chunk_attr->mchunks[j];mchunk_attr = plgo->mchunk_attrs + idx;total_mchunk_size += mchunk_attr->size;mchunk_attr = plgo->inner_mchunk_attrs + idx;total_inner_mchunk_size += mchunk_attr->size;}info(" Total mchunk size: 0x%" PRIX64, total_mchunk_size);info(" Total inner mchunk size: 0x%" PRIX64, total_inner_mchunk_size);#if 1if (chunk_attr->mchunk_count > 0) {info(" Mchunk list:");for (j = 0; j < chunk_attr->mchunk_count; ++j)info(" %u", chunk_attr->mchunks[j]);}#endifinfo("");}}if (plgo->mchunk_count > 0) {info(" Mchunks:");for (i = 0; i < plgo->mchunk_count; ++i) {mchunk_attr = plgo->mchunk_attrs + i;info(" Mchunk #%04" PRIuMAX ":\n"" Image: %u\n"" Offset: 0x%" PRIX64 "\n"" Size: 0x%" PRIX64,(uintmax_t)i,mchunk_attr->image_no,mchunk_attr->offset,mchunk_attr->size);info("");}}if (plgo->mchunk_count > 0) {info(" Inner mchunks:");for (i = 0; i < plgo->mchunk_count; ++i) {mchunk_attr = plgo->inner_mchunk_attrs + i;info(" Inner mchunk #%04" PRIuMAX ":\n"" Offset: 0x%" PRIX64 "\n"" Size: 0x%" PRIX64,(uintmax_t)i,mchunk_attr->offset,mchunk_attr->size);info("");}}}const struct playgo_lang_desc* playgo_get_lang_by_code(unsigned int code) {const struct playgo_lang_desc* lang;size_t i;for (i = 0; i < COUNT_OF(s_languages); ++i) {lang = s_languages + i;if (lang->code == code)return lang;}return NULL;}int playgo_get_chunks(struct playgo* plgo, uint64_t offset, uint64_t size, uint16_t** chunks, size_t* nchunks) {UT_array* list = NULL;uint16_t* src_chunks;uint16_t* dst_chunks;size_t count;struct playgo_chunk_attr_desc* chunk_attr;struct playgo_mchunk_attr_desc* mchunk_attr;uint16_t idx;size_t i, j;int status = 0;assert(plgo != NULL);/* TODO: check if size is needed */UNUSED(size);if (plgo->chunk_count == 0 || plgo->mchunk_count == 0) {count = 0;goto done;}utarray_new(list, &ut_uint16_icd);if (!list) {warning("Unable to allocate memory for chunks.");goto error;}for (i = 0; i < plgo->chunk_count; ++i) {chunk_attr = plgo->chunk_attrs + i;for (j = 0; j < chunk_attr->mchunk_count; ++j) {mchunk_attr = plgo->mchunk_attrs + chunk_attr->mchunks[j];if (offset >= mchunk_attr->offset && offset < (mchunk_attr->offset + mchunk_attr->size)) {idx = (uint16_t)i;utarray_push_back(list, &idx);}}}done:count = list ? utarray_len(list) : 0;if (chunks) {if (list) {src_chunks = (uint16_t*)utarray_front(list);if (src_chunks) {dst_chunks = (uint16_t*)malloc(sizeof(*dst_chunks) * count);if (!dst_chunks) {warning("Unable to allocate memory for chunks.");goto error;}memcpy(dst_chunks, src_chunks, sizeof(*dst_chunks) * count);*chunks = dst_chunks;} else {goto no_chunks;}} else {no_chunks:*chunks = NULL;count = 0;}}if (nchunks)*nchunks = count;status = 1;error:if (list)utarray_free(list);return status;}static void playgo_cleanup(struct playgo* plgo) {struct playgo_scenario_attr_desc* scenario_attr;struct playgo_chunk_attr_desc* chunk_attr;size_t i;assert(plgo != NULL);if (plgo->scenario_attrs) {for (i = 0; i < plgo->scenario_count; ++i) {scenario_attr = plgo->scenario_attrs + i;if (scenario_attr->chunks) {free(scenario_attr->chunks);scenario_attr->chunks = NULL;}if (scenario_attr->label) {free(scenario_attr->label);scenario_attr->label = NULL;}}free(plgo->scenario_attrs);plgo->scenario_attrs = NULL;}if (plgo->chunk_attrs) {for (i = 0; i < plgo->chunk_count; ++i) {chunk_attr = plgo->chunk_attrs + i;if (chunk_attr->mchunks) {free(chunk_attr->mchunks);chunk_attr->mchunks = NULL;}if (chunk_attr->label) {free(chunk_attr->label);chunk_attr->label = NULL;}}free(plgo->chunk_attrs);plgo->chunk_attrs = NULL;}if (plgo->mchunk_attrs) {free(plgo->mchunk_attrs);plgo->mchunk_attrs = NULL;}if (plgo->inner_mchunk_attrs) {free(plgo->inner_mchunk_attrs);plgo->inner_mchunk_attrs = NULL;}memset(plgo, 0, sizeof(*plgo));}static int parse_scenario_attr(struct playgo* plgo, size_t index, const struct playgo_scenario_attr_entry* entry, const uint16_t* chunks, size_t chunks_size, const char* labels, size_t labels_size) {struct playgo_scenario_attr_desc* desc;const uint16_t* chunks_src;uint16_t* chunks_dst;uint32_t chunks_offset;uint32_t label_offset;size_t i;int status = 0;assert(plgo != NULL);assert(index < plgo->scenario_count);assert(entry != NULL);assert(labels != NULL);chunks_offset = LE32(entry->chunks_offset);if (chunks_offset >= chunks_size) {warning("Invalid scenario chunks offset.");goto error;}label_offset = LE32(entry->label_offset);if (label_offset >= labels_size) {warning("Invalid scenario label offset.");goto error;}desc = plgo->scenario_attrs + index;{desc->type = LE32(entry->type);desc->initial_chunk_count = LE16(entry->initial_chunk_count);desc->chunk_count = LE16(entry->chunk_count);desc->chunks = (uint16_t*)malloc(sizeof(*desc->chunks) * desc->chunk_count);if (!desc->chunks) {warning("Unable to allocate memory for chunks.");goto error;}memset(desc->chunks, 0, sizeof(*desc->chunks) * desc->chunk_count);{chunks_src = chunks + chunks_offset / sizeof(*chunks);chunks_dst = desc->chunks;for (i = 0; i < desc->chunk_count; ++i)*chunks_dst++ = LE16(*chunks_src++);}desc->label = strdup(labels + label_offset);if (!desc->label) {warning("Unable to allocate memory for scenario label.");goto error;}}status = 1;error:return status;}static int parse_chunk_attr(struct playgo* plgo, size_t index, const struct playgo_chunk_attr_entry* entry, const uint16_t* mchunks, size_t mchunks_size, const char* labels, size_t labels_size) {struct playgo_chunk_attr_desc* desc;const uint16_t* mchunks_src;uint16_t* mchunks_dst;uint32_t mchunks_offset;uint32_t label_offset;size_t i;int status = 0;assert(plgo != NULL);assert(index < plgo->chunk_count);assert(entry != NULL);assert(labels != NULL);#if 0if ((entry->info2 & 0x8) != 0)goto error;#endif// XXX: EP0082-CUSA01435_00-LIFEISSTRANGE001 have mchunks_offset=mchunks_size=0x2, why?mchunks_offset = LE32(entry->mchunks_offset);if (mchunks_offset > mchunks_size) {warning("Invalid chunk mchunks offset.");goto error;}label_offset = LE32(entry->label_offset);if (label_offset >= labels_size) {warning("Invalid chunk label offset.");goto error;}desc = plgo->chunk_attrs + index;{desc->flag = entry->flag;desc->image_no = (entry->image_disc_layer_no >> 4) & 0xF;desc->disc_no = (entry->image_disc_layer_no >> 2) & 0x3;desc->layer_no = entry->image_disc_layer_no & 0x3;desc->req_locus = entry->req_locus;desc->language_mask = LE64(entry->language_mask);desc->mchunk_count = LE16(entry->mchunk_count);if (desc->mchunk_count > PLAYGO_MAX_MCHUNKS) {warning("Too much mchunks in chunk.");goto error;}desc->mchunks = (uint16_t*)malloc(sizeof(*desc->mchunks) * desc->mchunk_count);if (!desc->mchunks) {warning("Unable to allocate memory for mchunks.");goto error;}memset(desc->mchunks, 0, sizeof(*desc->mchunks) * desc->mchunk_count);{mchunks_src = mchunks + mchunks_offset / sizeof(*mchunks);mchunks_dst = desc->mchunks;for (i = 0; i < desc->mchunk_count; ++i)*mchunks_dst++ = LE16(*mchunks_src++);}desc->label = strdup(labels + label_offset);if (!desc->label) {warning("Unable to allocate memory for chunk label.");goto error;}}status = 1;error:return status;}static int parse_mchunk_attr(struct playgo* plgo, size_t index, const struct playgo_mchunk_attr_entry* entry, int is_inner) {struct playgo_mchunk_attr_desc* desc;union playgo_chunk_loc loc;union playgo_chunk_size size;int status = 0;assert(plgo != NULL);assert(index < plgo->mchunk_count);assert(entry != NULL);desc = is_inner ? (plgo->inner_mchunk_attrs + index) : (plgo->mchunk_attrs + index);{loc.raw = LE64(entry->loc.raw);size.raw = LE64(entry->size.raw);desc->image_no = is_inner ? 0 : loc.image_no;desc->offset = loc.offset;desc->size = size.size;}status = 1;error:return status;}static inline int has_language(uint64_t language_mask, unsigned int code) {uint64_t bit;uint64_t mask;bit = UINT64_C(1) << (PLAYGO_MAX_LANGUAGES - code - 1);mask = (language_mask & bit);return !!mask;}
GP4 project generation
This code is used to generate .gp4 project file from package file by doing some guesses and using its structure format.
gp4.h
#pragma once#include "common.h"struct pkg;int pkg_generate_gp4_project(struct pkg* pkg, const char* file_path, const char* output_directory);
gp4.c
#include "gp4.h"#include "pkg.h"#include "sfo.h"#include "playgo.h"#include "util.h"#include <utstring.h>#include <uthash.h>#include <time.h>/* TODO: implement Master Data ID (mdid) */struct dir_tree_entry {char* name;struct dir_tree_entry* children;UT_hash_handle hh;};struct enum_entries_for_gp4_cb_args {struct playgo* plgo;UT_string* files_xml;const char* output_directory;int is_sc_entry;int has_playgo_manifest;};struct build_dir_tree_from_entries_cb_args {struct dir_tree_entry* root_dir_entry;};static int is_skipped_sc_entry(struct pkg_entry_desc* desc) {static enum pkg_entry_id skipped_entry_ids[] = {PKG_ENTRY_ID__LICENSE_DAT,PKG_ENTRY_ID__LICENSE_INFO,PKG_ENTRY_ID__SELFINFO_DAT,PKG_ENTRY_ID__IMAGEINFO_DAT,PKG_ENTRY_ID__TARGET_DELTAINFO_DAT,PKG_ENTRY_ID__ORIGIN_DELTAINFO_DAT,PKG_ENTRY_ID__PSRESERVED_DAT,PKG_ENTRY_ID__PLAYGO_CHUNK_DAT,PKG_ENTRY_ID__PLAYGO_CHUNK_SHA,PKG_ENTRY_ID__PLAYGO_MANIFEST_XML,PKG_ENTRY_ID__PUBTOOLINFO_DAT,PKG_ENTRY_ID__APP__PLAYGO_CHUNK_DAT,PKG_ENTRY_ID__APP__PLAYGO_CHUNK_SHA,PKG_ENTRY_ID__APP__PLAYGO_MANIFEST_XML,};static const char* skipped_entry_names[] = { "" };size_t i;assert(desc != NULL);if (desc->id < PKG_SC_ENTRY_ID_START)return 1;if (desc->id >= PKG_ENTRY_ID__ICON0_DDS && desc->id <= PKG_ENTRY_ID__ICON0_30_DDS)return 1;if (desc->id == PKG_ENTRY_ID__PIC0_DDS || (desc->id >= PKG_ENTRY_ID__PIC1_DDS && desc->id <= PKG_ENTRY_ID__PIC1_30_DDS))return 1;for (i = 0; i < COUNT_OF(skipped_entry_ids); ++i) {if (desc->id == skipped_entry_ids[i])return 1;}for (i = 0; i < COUNT_OF(skipped_entry_names); ++i) {if (strcmp(desc->name, skipped_entry_names[i]) == 0)return 1;}return 0;}static int is_skipped_pfs_entry(const char* path) {static const char* skipped_paths[] = {"sce_sys/about","sce_sys/about/right.sprx","sce_sys/keystone","sce_discmap.plt","sce_discmap_patch.plt",};size_t i;assert(path != NULL);for (i = 0; i < COUNT_OF(skipped_paths); ++i) {if (strcmp(path, skipped_paths[i]) == 0)return 1;}return 0;}static enum cb_result enum_sc_entries_for_gp4_cb(void* arg, struct pkg* pkg, struct pkg_entry_desc* desc) {struct enum_entries_for_gp4_cb_args* args = (struct enum_entries_for_gp4_cb_args*)arg;char target_path[PATH_MAX], orig_path[PATH_MAX];struct pkg_table_entry* sc_entry;assert(args != NULL);assert(args->files_xml != NULL);assert(args->output_directory != NULL);assert(pkg != NULL);assert(desc != NULL);UNUSED(pkg);if (is_skipped_sc_entry(desc))goto done;snprintf(target_path, sizeof(target_path), "sce_sys/%s", desc->name);snprintf(orig_path, sizeof(orig_path), "%sSc0/%s", args->output_directory, desc->name);path_slashes_to_backslashes(orig_path);utstring_printf(args->files_xml," <file targ_path=\"%s\" orig_path=\"%s\"/>\n",target_path, orig_path);if (strcmp(desc->name, PKG_ENTRY_NAME__PARAM_SFO) == 0) {sc_entry = pkg_find_entry(pkg, PKG_ENTRY_ID__SHAREPARAM_JSON);if (!sc_entry) {warning("Share parameters file is not found, adding it anyway...");snprintf(target_path, sizeof(target_path), "sce_sys/%s", PKG_ENTRY_NAME__SHAREPARAM_JSON);snprintf(orig_path, sizeof(orig_path), "%sSc0/%s", args->output_directory, PKG_ENTRY_NAME__SHAREPARAM_JSON);path_slashes_to_backslashes(orig_path);utstring_printf(args->files_xml," <file targ_path=\"%s\" orig_path=\"%s\"/>\n",target_path, orig_path);}}done:return CB_RESULT_CONTINUE;}static int chunk_index_cmp(const void* a, const void* b) {uint16_t idx1 = *(const uint16_t*)a;uint16_t idx2 = *(const uint16_t*)b;if (idx1 > idx2)return 1;else if (idx1 < idx2)return -1;elsereturn 0;}static enum cb_result enum_pfs_entries_for_gp4_cb(void* arg, struct pfs* pfs, pfs_ino ino, enum pfs_entry_type type, const char* path, uint64_t size, uint32_t flags) {struct enum_entries_for_gp4_cb_args* args = (struct enum_entries_for_gp4_cb_args*)arg;struct pfs_file_context* file = NULL;char target_path[PATH_MAX], orig_path[PATH_MAX];UT_string* params_xml = NULL;uint64_t outer_offset, size_to_read;uint16_t* chunks = NULL;uint16_t cur_chunk, start_chunk = (uint16_t)-1, prev_chunk = (uint16_t)-1;struct playgo_chunk_attr_desc* chunk_attr;unsigned int disc_no, layer_no;size_t chunk_count;size_t i;int compressed;assert(args != NULL);assert(args->files_xml != NULL);assert(args->output_directory != NULL);assert(pfs != NULL);assert(path != NULL);UNUSED(ino);UNUSED(size);UNUSED(flags);if (type != PFS_ENTRY_FILE)goto done;if (starts_with(path, "/"))++path;if (is_skipped_pfs_entry(path))goto done;file = pfs_get_file(pfs, ino);if (!file) {warning("Unable to get context for file '%s'.", path);goto done;}strncpy(target_path, path, sizeof(target_path));snprintf(orig_path, sizeof(orig_path), "%sImage0/%s", args->output_directory, path);path_slashes_to_backslashes(orig_path);if (!pfs_file_get_offset_size(file, 0, size, NULL, &size_to_read, &compressed)) {warning("Unable to get read size for file '%s'.", path);goto done;}if (!pfs_file_get_outer_location(file, 0, &outer_offset)) {warning("Unable to get location for file '%s'.", path);goto done;}utstring_new(params_xml);if (compressed && size > size_to_read)utstring_printf(params_xml, " pfs_compression=\"enable\"");if (args->plgo) {if (!playgo_get_chunks(args->plgo, outer_offset, size_to_read, &chunks, &chunk_count)) {warning("Unable to get chunks information for file '%s'.", path);goto done;}if (chunk_count > 0 && !(chunk_count == 1 && chunks[0] == 0)) {qsort(chunks, chunk_count, sizeof(*chunks), &chunk_index_cmp);for (i = 0, disc_no = layer_no = UINT_MAX; i < chunk_count; ++i) {chunk_attr = args->plgo->chunk_attrs + chunks[i];disc_no = MIN(disc_no, chunk_attr->disc_no);layer_no = MIN(layer_no, chunk_attr->layer_no);}if (disc_no == UINT_MAX)disc_no = 0;if (layer_no == UINT_MAX)layer_no = 0;if (disc_no > 0)utstring_printf(params_xml, " disc_no=\"%u\"", disc_no);if (layer_no > 0)utstring_printf(params_xml, " layer_no=\"%u\"", layer_no);utstring_printf(params_xml, " chunks=\"");for (i = 0; i < chunk_count; ++i) {cur_chunk = chunks[i];if (i == 0) {start_chunk = cur_chunk;utstring_printf(params_xml, "%u", cur_chunk);} else {if (cur_chunk == prev_chunk || cur_chunk == (prev_chunk + 1)) { /* consecutive? */if ((i + 1) == chunk_count) /* last one? */utstring_printf(params_xml, "-%u", cur_chunk);} else {if ((prev_chunk - start_chunk) >= 1) /* gap found? */utstring_printf(params_xml, "-%u", prev_chunk);utstring_printf(params_xml, " %u", cur_chunk);start_chunk = cur_chunk;}}prev_chunk = cur_chunk;}utstring_printf(params_xml, "\"");}}utstring_printf(args->files_xml," <file targ_path=\"%s\" orig_path=\"%s\"%s/>\n",target_path, orig_path, utstring_body(params_xml));/* TODO: add image_no support *//* TODO: need proper file ordering (based on chunk order?) */if (params_xml)utstring_free(params_xml);done:if (chunks)free(chunks);if (file)pfs_free_file(file);return CB_RESULT_CONTINUE;}static struct dir_tree_entry* get_dir_tree_entry(struct dir_tree_entry* parent, const char* name) {struct dir_tree_entry* entry = NULL;assert(parent != NULL);assert(name != NULL);HASH_FIND_STR(parent->children, name, entry);if (!entry) {entry = (struct dir_tree_entry*)malloc(sizeof(*entry));if (!entry)error("Unable to allocate memory for directory tree entry.");memset(entry, 0, sizeof(*entry));{entry->name = strdup(name);}HASH_ADD_STR(parent->children, name, entry);}return entry;}static void build_dir_tree(struct dir_tree_entry* root_dir_entry, const char* path) {struct dir_tree_entry* entry;char* part;char* sep, old;size_t len;assert(root_dir_entry != NULL);assert(path != NULL);for (entry = root_dir_entry, part = strdup(path); *part != '\0'; ) {sep = (char*)path_get_separator(part);if (*sep != '\0')len = sep - part;elselen = strlen(part);old = part[len], part[len] = '\0';entry = get_dir_tree_entry(entry, part);part[len] = old;part = (char*)path_skip_separator(sep);}}static void cleanup_dir_tree(struct dir_tree_entry* root_dir_entry) {struct dir_tree_entry* entry;struct dir_tree_entry* tmp;assert(root_dir_entry != NULL);if (root_dir_entry->children) {HASH_ITER(hh, root_dir_entry->children, entry, tmp) {HASH_DEL(root_dir_entry->children, entry);cleanup_dir_tree(entry);}}if (root_dir_entry->name)free(root_dir_entry->name);free(root_dir_entry);}static enum cb_result build_dir_tree_from_sc_entries_cb(void* arg, struct pkg* pkg, struct pkg_entry_desc* desc) {struct build_dir_tree_from_entries_cb_args* args = (struct build_dir_tree_from_entries_cb_args*)arg;enum cb_result cb_result = CB_RESULT_CONTINUE;assert(args != NULL);assert(pkg != NULL);assert(desc != NULL);UNUSED(pkg);if (desc->id < PKG_SC_ENTRY_ID_START)goto done;build_dir_tree(args->root_dir_entry, "sce_sys");cb_result = CB_RESULT_STOP; /* no need to add sce_sys again */done:return cb_result;}static enum cb_result build_dir_tree_from_pfs_entries_cb(void* arg, struct pfs* pfs, pfs_ino ino, enum pfs_entry_type type, const char* path, uint64_t size, uint32_t flags) {struct build_dir_tree_from_entries_cb_args* args = (struct build_dir_tree_from_entries_cb_args*)arg;assert(args != NULL);assert(pfs != NULL);assert(path != NULL);UNUSED(pfs);UNUSED(ino);UNUSED(size);UNUSED(flags);if (type != PFS_ENTRY_DIRECTORY)goto done;if (starts_with(path, "/"))++path;if (is_skipped_pfs_entry(path))goto done;build_dir_tree(args->root_dir_entry, path);done:return CB_RESULT_CONTINUE;}static inline void print_dir_tree_indent(UT_string* dirs_xml, size_t depth) {static const char* indent = " ";size_t i;assert(dirs_xml != NULL);for (i = 0; i < depth; ++i)utstring_printf(dirs_xml, "%s", indent);}static void print_dir_tree(UT_string* dirs_xml, struct dir_tree_entry* root_dir_entry, size_t depth, const char* start_indent) {struct dir_tree_entry* entry;struct dir_tree_entry* tmp;assert(dirs_xml != NULL);assert(root_dir_entry != NULL);if (root_dir_entry->children) {print_dir_tree_indent(dirs_xml, depth);if (depth > 0)utstring_printf(dirs_xml, "%s<dir targ_name=\"%s\">\n", start_indent, root_dir_entry->name);elseutstring_printf(dirs_xml, "%s<rootdir>\n", start_indent);{HASH_ITER(hh, root_dir_entry->children, entry, tmp) {HASH_DEL(root_dir_entry->children, entry);print_dir_tree(dirs_xml, entry, depth + 1, start_indent);}}print_dir_tree_indent(dirs_xml, depth);if (depth > 0)utstring_printf(dirs_xml, "%s</dir>\n", start_indent);elseutstring_printf(dirs_xml, "%s</rootdir>\n", start_indent);} else {print_dir_tree_indent(dirs_xml, depth);if (depth > 0)utstring_printf(dirs_xml, "%s<dir targ_name=\"%s\"/>\n", start_indent, root_dir_entry->name);elseutstring_printf(dirs_xml, "%s<rootdir>\n", start_indent);}}static inline int find_xml_tag(const char* data, const char* tag_start, const char* tag_end, size_t* offset, size_t* size) {const char* p1;const char* p2;int status = 0;assert(data != NULL);assert(tag_start != NULL);assert(tag_end != NULL);p1 = strstr(data, tag_start);if (!p1)goto error;p2 = strstr(p1 + strlen(tag_start), tag_end);if (!p2)goto error;if (offset)*offset = p1 - data;if (size)*size = p2 + strlen(tag_end) - p1;status = 1;error:return status;}int pkg_generate_gp4_project(struct pkg* pkg, const char* file_path, const char* output_directory) {static const char* storage_types[] = { "bd25", "bd50", "bd50_50", "bd50_25", "digital50", "digital25" };static const char* app_categories[] = { "gx", "gxe", "gxc", "gxe+", "gxk" };struct sfo* sfo = NULL;struct sfo_entry* sfo_entry;struct playgo* plgo = NULL;UT_string* package_params_xml = NULL;UT_string* psproject_xml = NULL;UT_string* chunks_xml = NULL;UT_string* chunks_params_xml = NULL;UT_string* files_xml = NULL;UT_string* dirs_xml = NULL;const char* volume_id = "PS4VOLUME";const char* volume_type = NULL;char volume_timestamp[32];const char* app_type;struct tm creation_time;time_t creation_timestamp;uint8_t passcode_bin[KEYMGR_PASSCODE_SIZE];char passcode[KEYMGR_PASSCODE_SIZE + 1];uint8_t* sfo_data;uint32_t sfo_data_size;uint8_t* playgo_data;uint32_t playgo_data_size;uint32_t sfo_uint_value;char* sfo_str_value = NULL;char* param_key_value;char* param_key;char* param_value;const char* playgo_manifest_xml_data;uint32_t playgo_manifest_xml_data_size;int has_playgo_manifest;size_t chunk_info_xml_offset, chunk_info_xml_size;size_t scenarios_xml_offset, scenarios_xml_size;struct playgo_chunk_attr_desc* chunk_attr;UT_string* supported_languages = NULL;UT_string* default_language = NULL;int use_all_langs = 1;struct enum_entries_for_gp4_cb_args enum_entries_args;struct build_dir_tree_from_entries_cb_args build_dir_tree_from_entries_args;struct dir_tree_entry* root_dir_entry = NULL;unsigned int attr = 0, iro_tag = 0;int has_iro_tag = 0;int is_remaster = 0;size_t i;int status = 0;assert(pkg != NULL);assert(file_path != NULL);UNUSED(storage_types);UNUSED(app_categories);sfo_data = pkg_locate_entry_data(pkg, PKG_ENTRY_ID__PARAM_SFO, &sfo_data_size);if (!sfo_data) {warning("System file object data is not found.");goto error;}sfo = sfo_alloc();if (!sfo) {warning("Unable to allocate memory for system file object.");goto error;}if (!sfo_load_from_memory(sfo, sfo_data, sfo_data_size)) {warning("Unable to load system file object.");goto error;}playgo_data = pkg_locate_entry_data(pkg, PKG_ENTRY_ID__PLAYGO_CHUNK_DAT, &playgo_data_size);if (!playgo_data) {warning("Playgo data is not found.");goto error;}plgo = playgo_alloc();if (!plgo) {warning("Unable to allocate memory for playgo object.");goto error;}if (!playgo_load_from_memory(plgo, playgo_data, playgo_data_size)) {warning("Unable to load playgo file object.");goto error;}playgo_dump(plgo);if (!generate_crypto_random(passcode_bin, sizeof(passcode_bin))) {warning("Unable to generate passcode.");goto error;}bin_to_readable(passcode, sizeof(passcode) - 1, passcode_bin, sizeof(passcode_bin));passcode[sizeof(passcode) - 1] = '\0';utstring_new(package_params_xml);utstring_printf(package_params_xml, " content_id=\"%36s\"", pkg->hdr->content_id);utstring_printf(package_params_xml, " passcode=\"%s\"", passcode);sfo_entry = sfo_find_entry(sfo, "PUBTOOLINFO");if (sfo_entry) {if (sfo_entry->format != SFO_FORMAT_STRING || sfo_entry->size < 1) {warning("Invalid format of PUBTOOLINFO entry in system file object.");goto error;}sfo_str_value = strdup((const char*)sfo_entry->value);if (!sfo_str_value || strlen(sfo_str_value) == 0) {warning("Invalid value of PUBTOOLINFO entry in system file object.");goto error;}param_key_value = strtok(sfo_str_value, "=,");while (param_key_value) {param_key = param_key_value;param_key_value = strtok(NULL, "=,");param_value = param_key_value;param_key_value = strtok(NULL, "=,");#ifdef WRITE_CREATION_DATEif (strcmp(param_key, "c_date") == 0 && strlen(param_value) == strlen("YYYYMMDD"))utstring_printf(package_params_xml, " c_date=\"%.4s-%.2s-%.2s\"", param_value, param_value + 4, param_value + 4 + 2);else#endifif (strcmp(param_key, "st_type") == 0)utstring_printf(package_params_xml, " storage_type=\"%s\"", param_value);}}sfo_entry = sfo_find_entry(sfo, "ATTRIBUTE");if (sfo_entry) {if (sfo_entry->format != SFO_FORMAT_UINT32 || sfo_entry->size != sizeof(sfo_uint_value)) {warning("Invalid format of ATTRIBUTE entry in system file object.");goto error;}sfo_uint_value = LE32(*(uint32_t*)sfo_entry->value);attr = sfo_uint_value;} else {warning("No ATTRIBUTE entry in system file object.");goto error;}sfo_entry = sfo_find_entry(sfo, "APP_TYPE");if (sfo_entry) {if (sfo_entry->format != SFO_FORMAT_UINT32 || sfo_entry->size != sizeof(sfo_uint_value)) {warning("Invalid format of APP_TYPE entry in system file object.");goto error;}sfo_uint_value = LE32(*(uint32_t*)sfo_entry->value);switch (sfo_uint_value) {case APP_TYPE_PAID_STANDALONE_FULL: app_type = "full"; break;case APP_TYPE_UPGRADABLE: app_type = "upgradable"; break;case APP_TYPE_DEMO: app_type = "demo"; break;case APP_TYPE_FREEMIUM: app_type = "freemium"; break;default: app_type = NULL; break;}if (app_type)utstring_printf(package_params_xml, " app_type=\"%s\"", app_type);}sfo_entry = sfo_find_entry(sfo, "IRO_TAG");if (sfo_entry) {if (sfo_entry->format != SFO_FORMAT_UINT32 || sfo_entry->size != sizeof(sfo_uint_value)) {warning("Invalid format of IRO_TAG entry in system file object.");goto error;}sfo_uint_value = LE32(*(uint32_t*)sfo_entry->value);iro_tag = sfo_uint_value;has_iro_tag = 1;}sfo_entry = sfo_find_entry(sfo, "REMASTER_TYPE");if (sfo_entry) {if (sfo_entry->format != SFO_FORMAT_UINT32 || sfo_entry->size != sizeof(sfo_uint_value)) {warning("Invalid format of REMASTER_TYPE entry in system file object.");goto error;}sfo_uint_value = LE32(*(uint32_t*)sfo_entry->value);is_remaster = (sfo_uint_value == 1);}sfo_entry = sfo_find_entry(sfo, "CATEGORY");if (sfo_entry) {if (sfo_entry->format != SFO_FORMAT_STRING || sfo_entry->size < 1) {warning("Invalid format of CATEGORY entry in system file object.");goto error;}sfo_str_value = strdup((const char*)sfo_entry->value);if (!sfo_str_value || strlen(sfo_str_value) == 0) {warning("Invalid value of CATEGORY entry in system file object.");goto error;}if (attr == 2) {if (strcmp(sfo_str_value, "gd") == 0) {if (!is_remaster)volume_type = "pkg_ps4_app";elsevolume_type = "pkg_ps4_remaster";} else if (strcmp(sfo_str_value, "gp") == 0) {volume_type = "pkg_ps4_patch";} else if (strcmp(sfo_str_value, "ac") == 0) {volume_type = "pkg_ps4_ac_data";}} else if (attr == 0) {if (strcmp(sfo_str_value, "ac") == 0) {if (has_iro_tag) {if (iro_tag == 1)volume_type = "pkg_ps4_sf_theme";else if (iro_tag == 2)volume_type = "pkg_ps4_theme";else {warning("Unsupported value of IRO_TAG entry in system file object, making it as additional content package.");volume_type = "pkg_ps4_ac_data";}} else {warning("ATTRIBUTE value is zero but no IRO_TAG entry in system file object, making it as additional content package.");volume_type = "pkg_ps4_ac_data";}} else {warning("ATTRIBUTE value is zero but CATEGORY value is not 'ac' in system file object.");goto error;}} else {warning("Unsupported value of ATTRIBUTE entry in system file object.");goto error;}}if (!volume_type) {warning("Unable to determine volume type.");goto error;}creation_timestamp = (time_t)LE64(pkg->pfs->hdr.super_root_dinode.creation_time);localtime_r(&creation_timestamp, &creation_time);strftime(volume_timestamp, sizeof(volume_timestamp), "%Y-%m-%d %H:%M:%S", &creation_time);root_dir_entry = (struct dir_tree_entry*)malloc(sizeof(*root_dir_entry));if (!root_dir_entry) {warning("Unable to allocate memory for root directory entry.");goto error;}memset(root_dir_entry, 0, sizeof(*root_dir_entry));{root_dir_entry->name = strdup("/");}utstring_new(chunks_xml);playgo_manifest_xml_data = (char*)pkg_locate_entry_data(pkg, PKG_ENTRY_ID__PLAYGO_MANIFEST_XML, &playgo_manifest_xml_data_size);if (playgo_manifest_xml_data) {if (!find_xml_tag(playgo_manifest_xml_data, "<chunk_info ", "</chunk_info>", &chunk_info_xml_offset, &chunk_info_xml_size))goto error;if (!find_xml_tag(playgo_manifest_xml_data, "<scenarios ", "</scenarios>", &scenarios_xml_offset, &scenarios_xml_size)) {if (!find_xml_tag(playgo_manifest_xml_data, "<scenarios>", "</scenarios>", &scenarios_xml_offset, &scenarios_xml_size))goto error;}if (scenarios_xml_offset <= chunk_info_xml_offset || scenarios_xml_size >= chunk_info_xml_size) {warning("Invalid playgo manifest xml.");goto error;}utstring_printf(chunks_xml, " ");utstring_bincpy(chunks_xml, playgo_manifest_xml_data + chunk_info_xml_offset, scenarios_xml_offset - chunk_info_xml_offset);utstring_new(chunks_params_xml);if (plgo->chunk_count > 0) {utstring_new(supported_languages);utstring_new(default_language);if (!playgo_get_languages(plgo, supported_languages, default_language, &use_all_langs)) {warning("Unable to get available languages.");goto error;}if (utstring_len(supported_languages) > 0) {utstring_printf(chunks_params_xml, " supported_languages=\"%s\"", utstring_body(supported_languages));if (utstring_len(default_language) > 0)utstring_printf(chunks_params_xml, " default_language=\"%s\"", utstring_body(default_language));} else {use_all_langs = 1;}}utstring_printf(chunks_xml, "<chunks%s", utstring_body(chunks_params_xml));if (plgo->chunk_count > 0) {utstring_printf(chunks_xml, ">\n");for (i = 0; i < plgo->chunk_count; ++i) {chunk_attr = plgo->chunk_attrs + i;utstring_printf(chunks_xml, " <chunk id=\"%" PRIuMAX "\"", (uintmax_t)i);utstring_renew(chunks_params_xml);utstring_printf(chunks_params_xml, " disc_no=\"%u\"", chunk_attr->disc_no);utstring_printf(chunks_params_xml, " layer_no=\"%u\"", chunk_attr->layer_no);if (chunk_attr->label && strlen(chunk_attr->label) > 0)utstring_printf(chunks_params_xml, " label=\"%s\"", chunk_attr->label);if (!use_all_langs) {utstring_renew(supported_languages);if (!playgo_get_chunk_languages(plgo, i, supported_languages)) {warning("Unable to get chunk language.");goto error;}if (utstring_len(supported_languages) > 0)utstring_printf(chunks_params_xml, " languages=\"%s\"", utstring_body(supported_languages));}utstring_printf(chunks_xml, "%s/>\n", utstring_body(chunks_params_xml));}utstring_printf(chunks_xml, " </chunks>\n ");} else { utstring_printf(chunks_xml, "/>\n ");}utstring_bincpy(chunks_xml, playgo_manifest_xml_data + scenarios_xml_offset, chunk_info_xml_size - (scenarios_xml_offset - chunk_info_xml_offset));utstring_printf(chunks_xml, "\n");has_playgo_manifest = 1;} else {warning("Playgo manifest xml is not found, using default one...");utstring_printf(chunks_xml," <chunk_info chunk_count=\"1\" scenario_count=\"1\">\n"" <chunks>\n"" <chunk id=\"0\" layer_no=\"0\" label=\"Chunk #0\"/>\n"" </chunks>\n"" <scenarios default_id=\"0\">\n"" <scenario id=\"0\" type=\"sp\" initial_chunk_count=\"1\" label=\"Scenario #0\">0</scenario>\n"" </scenarios>\n"" </chunk_info>\n");has_playgo_manifest = 0;}utstring_new(files_xml);utstring_printf(files_xml," <files img_no=\"0\">\n");{memset(&enum_entries_args, 0, sizeof(enum_entries_args));{enum_entries_args.plgo = plgo;enum_entries_args.files_xml = files_xml;enum_entries_args.output_directory = output_directory;enum_entries_args.has_playgo_manifest = has_playgo_manifest;}enum_entries_args.is_sc_entry = 1;pkg_enum_entries(pkg, &enum_sc_entries_for_gp4_cb, &enum_entries_args);enum_entries_args.is_sc_entry = 0;pfs_enum_user_root_directory(pkg->inner_pfs, &enum_pfs_entries_for_gp4_cb, &enum_entries_args);}utstring_printf(files_xml," </files>\n");memset(&build_dir_tree_from_entries_args, 0, sizeof(build_dir_tree_from_entries_args));{build_dir_tree_from_entries_args.root_dir_entry = root_dir_entry;}pkg_enum_entries(pkg, &build_dir_tree_from_sc_entries_cb, &build_dir_tree_from_entries_args);pfs_enum_user_root_directory(pkg->inner_pfs, &build_dir_tree_from_pfs_entries_cb, &build_dir_tree_from_entries_args);utstring_new(dirs_xml);print_dir_tree(dirs_xml, root_dir_entry, 0, " ");cleanup_dir_tree(root_dir_entry);root_dir_entry = NULL;utstring_new(psproject_xml);utstring_printf(psproject_xml,"<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n""<psproject fmt=\"gp4\" version=\"1000\">\n"" <volume>\n"" <volume_type>%s</volume_type>\n"" <volume_id>%s</volume_id>\n"" <volume_ts>%s</volume_ts>\n"" <package%s/>\n",volume_type, volume_id, volume_timestamp, utstring_body(package_params_xml));utstring_concat(psproject_xml, chunks_xml);utstring_printf(psproject_xml," </volume>\n");utstring_concat(psproject_xml, files_xml);utstring_concat(psproject_xml, dirs_xml);utstring_printf(psproject_xml,"</psproject>\n");if (!write_to_file(file_path, utstring_body(psproject_xml), utstring_len(psproject_xml), NULL, 0644)) {warning("Unable to write file '%s'.", file_path);goto error;}status = 1;error:if (dirs_xml)utstring_free(dirs_xml);if (files_xml)utstring_free(files_xml);if (default_language)utstring_free(default_language);if (supported_languages)utstring_free(supported_languages);if (chunks_params_xml)utstring_free(chunks_params_xml);if (chunks_xml)utstring_free(chunks_xml);if (package_params_xml)utstring_free(package_params_xml);if (psproject_xml)utstring_free(psproject_xml);if (root_dir_entry)free(root_dir_entry);if (sfo_str_value)free(sfo_str_value);if (plgo)playgo_free(plgo);if (sfo)sfo_free(sfo);return status;}
Some of PKG/PFS stuff
Here I’ve uploaded some structures, definitions and functions related to PKG file format, you may see some references to them from GP4 code.
pkg.h
#pragma once//...#define PKG_PFS_IMAGE_FILE_NAME "pfs_image.dat"#define PKG_CONTENT_ID_SIZE 0x30#define PKG_HASH_SIZE 0x20#define PKG_HEADER_SIZE 0x5A0#define PKG_TABLE_ENTRY_SIZE 0x20#define PKG_ENTRY_KEYSET_SIZE 0x20#define PKG_ENTRY_KEYSET_ENC_SIZE 0x100#define PKG_FLAG_FINALIZED (UINT32_C(1) << 31)enum app_type {APP_TYPE_PAID_STANDALONE_FULL = 1,APP_TYPE_UPGRADABLE = 2,APP_TYPE_DEMO = 3,APP_TYPE_FREEMIUM = 4,};enum drm_type {DRM_TYPE_NONE = 0x0,DRM_TYPE_PS4 = 0xF,};enum content_type {CONTENT_TYPE_GD = 0x1A, /* pkg_ps4_app, pkg_ps4_patch, pkg_ps4_remaster */CONTENT_TYPE_AC = 0x1B, /* pkg_ps4_ac_data, pkg_ps4_sf_theme, pkg_ps4_theme */CONTENT_TYPE_AL = 0x1C, /* pkg_ps4_ac_nodata */CONTENT_TYPE_DP = 0x1E, /* pkg_ps4_delta_patch */};enum iro_tag {IRO_TAG_SF_THEME = 0x1, /* SHAREfactory theme */IRO_TAG_SS_THEME = 0x2, /* System Software theme */};TYPE_BEGIN(struct pkg_header, PKG_HEADER_SIZE);TYPE_FIELD(char magic[4], 0x00);TYPE_FIELD(uint32_t flags, 0x04);#define PKG_FLAGS_VER_1 0x01000000#define PKG_FLAGS_VER_2 0x02000000#define PKG_FLAGS_INTERNAL 0x40000000#define PKG_FLAGS_FINALIZED 0x80000000TYPE_FIELD(uint32_t unk_0x08, 0x08);TYPE_FIELD(uint32_t unk_0x0C, 0x0C); /* 0xF */TYPE_FIELD(uint32_t entry_count, 0x10);TYPE_FIELD(uint16_t sc_entry_count, 0x14);TYPE_FIELD(uint16_t entry_count_2, 0x16); /* same as entry_count */TYPE_FIELD(uint32_t entry_table_offset, 0x18);TYPE_FIELD(uint32_t main_ent_data_size, 0x1C);TYPE_FIELD(uint64_t body_offset, 0x20);TYPE_FIELD(uint64_t body_size, 0x28);TYPE_FIELD(char content_id[PKG_CONTENT_ID_SIZE], 0x40);TYPE_FIELD(uint32_t drm_type, 0x70);TYPE_FIELD(uint32_t content_type, 0x74);TYPE_FIELD(uint32_t content_flags, 0x78);#define PKG_CONTENT_FLAGS_FIRST_PATCH 0x00100000#define PKG_CONTENT_FLAGS_PATCHGO 0x00200000#define PKG_CONTENT_FLAGS_REMASTER 0x00400000#define PKG_CONTENT_FLAGS_PS_CLOUD 0x00800000#define PKG_CONTENT_FLAGS_GD_AC 0x02000000#define PKG_CONTENT_FLAGS_NON_GAME 0x04000000#define PKG_CONTENT_FLAGS_0x8000000 0x08000000 /* has data? */#define PKG_CONTENT_FLAGS_SUBSEQUENT_PATCH 0x40000000#define PKG_CONTENT_FLAGS_DELTA_PATCH 0x41000000#define PKG_CONTENT_FLAGS_CUMULATIVE_PATCH 0x60000000TYPE_FIELD(uint32_t promote_size, 0x7C);TYPE_FIELD(uint32_t version_date, 0x80);TYPE_FIELD(uint32_t version_hash, 0x84);TYPE_FIELD(uint32_t unk_0x88, 0x88); /* for delta patches only? */TYPE_FIELD(uint32_t unk_0x8C, 0x8C); /* for delta patches only? */TYPE_FIELD(uint32_t unk_0x90, 0x90); /* for delta patches only? */TYPE_FIELD(uint32_t unk_0x94, 0x94); /* for delta patches only? */TYPE_FIELD(uint32_t iro_tag, 0x98);TYPE_FIELD(uint32_t ekc_version, 0x9C); /* drm type version */TYPE_FIELD(uint8_t sc_entries1_hash[PKG_HASH_SIZE], 0x100);TYPE_FIELD(uint8_t sc_entries2_hash[PKG_HASH_SIZE], 0x120);TYPE_FIELD(uint8_t digest_table_hash[PKG_HASH_SIZE], 0x140);TYPE_FIELD(uint8_t body_digest[PKG_HASH_SIZE], 0x160);// TODO: i think these fields are actually members of element of container arrayTYPE_FIELD(uint32_t unk_0x400, 0x400);TYPE_FIELD(uint32_t pfs_image_count, 0x404);TYPE_FIELD(uint64_t pfs_flags, 0x408);#define PKG_PFS_FLAG_NESTED_IMAGE UINT64_C(0x8000000000000000)TYPE_FIELD(uint64_t pfs_image_offset, 0x410);TYPE_FIELD(uint64_t pfs_image_size, 0x418);TYPE_FIELD(uint64_t mount_image_offset, 0x420);TYPE_FIELD(uint64_t mount_image_size, 0x428);TYPE_FIELD(uint64_t package_size, 0x430);TYPE_FIELD(uint32_t pfs_signed_size, 0x438);TYPE_FIELD(uint32_t pfs_cache_size, 0x43C);TYPE_FIELD(uint8_t pfs_image_digest[PKG_HASH_SIZE], 0x440);TYPE_FIELD(uint8_t pfs_signed_digest[PKG_HASH_SIZE], 0x460);TYPE_FIELD(uint64_t pfs_split_size_nth_0, 0x480);TYPE_FIELD(uint64_t pfs_split_size_nth_1, 0x488);TYPE_END();CT_SIZE_ASSERT(struct pkg_header, PKG_HEADER_SIZE);TYPE_BEGIN(struct pkg_table_entry, PKG_TABLE_ENTRY_SIZE);TYPE_FIELD(uint8_t raw_data[PKG_TABLE_ENTRY_SIZE], 0x00);TYPE_FIELD(uint32_t id, 0x00); // enum pkg_entry_idTYPE_FIELD(uint32_t unk1, 0x04);TYPE_FIELD(uint32_t flags1, 0x08);TYPE_FIELD(uint32_t flags2, 0x0C);TYPE_FIELD(uint32_t offset, 0x10);TYPE_FIELD(uint32_t size, 0x14);TYPE_FIELD(uint64_t pad, 0x18);TYPE_END();CT_SIZE_ASSERT(struct pkg_table_entry, PKG_TABLE_ENTRY_SIZE);enum pkg_entry_id {PKG_ENTRY_ID__DIGESTS = 0x00000001,PKG_ENTRY_ID__ENTRY_KEYS = 0x00000010,PKG_ENTRY_ID__IMAGE_KEY = 0x00000020,PKG_ENTRY_ID__GENERAL_DIGESTS = 0x00000080,PKG_ENTRY_ID__METAS = 0x00000100,PKG_ENTRY_ID__ENTRY_NAMES = 0x00000200,PKG_ENTRY_ID__LICENSE_DAT = 0x00000400,PKG_ENTRY_ID__LICENSE_INFO = 0x00000401,PKG_ENTRY_ID__NPTITLE_DAT = 0x00000402,PKG_ENTRY_ID__NPBIND_DAT = 0x00000403,PKG_ENTRY_ID__SELFINFO_DAT = 0x00000404,PKG_ENTRY_ID__IMAGEINFO_DAT = 0x00000406,PKG_ENTRY_ID__TARGET_DELTAINFO_DAT = 0x00000407,PKG_ENTRY_ID__ORIGIN_DELTAINFO_DAT = 0x00000408,PKG_ENTRY_ID__PSRESERVED_DAT = 0x00000409,PKG_ENTRY_ID__PARAM_SFO = 0x00001000,PKG_ENTRY_ID__PLAYGO_CHUNK_DAT = 0x00001001,PKG_ENTRY_ID__PLAYGO_CHUNK_SHA = 0x00001002,PKG_ENTRY_ID__PLAYGO_MANIFEST_XML = 0x00001003,PKG_ENTRY_ID__PRONUNCIATION_XML = 0x00001004,PKG_ENTRY_ID__PRONUNCIATION_SIG = 0x00001005,PKG_ENTRY_ID__PIC1_PNG = 0x00001006,PKG_ENTRY_ID__PUBTOOLINFO_DAT = 0x00001007,PKG_ENTRY_ID__APP__PLAYGO_CHUNK_DAT = 0x00001008,PKG_ENTRY_ID__APP__PLAYGO_CHUNK_SHA = 0x00001009,PKG_ENTRY_ID__APP__PLAYGO_MANIFEST_XML = 0x0000100A,PKG_ENTRY_ID__SHAREPARAM_JSON = 0x0000100B,PKG_ENTRY_ID__SHAREOVERLAYIMAGE_PNG = 0x0000100C,PKG_ENTRY_ID__SAVE_DATA_PNG = 0x0000100D,PKG_ENTRY_ID__SHAREPRIVACYGUARDIMAGE_PNG = 0x0000100E,PKG_ENTRY_ID__ICON0_PNG = 0x00001200,PKG_ENTRY_ID__ICON0_00_PNG = 0x00001201,PKG_ENTRY_ID__ICON0_01_PNG = 0x00001202,PKG_ENTRY_ID__ICON0_02_PNG = 0x00001203,PKG_ENTRY_ID__ICON0_03_PNG = 0x00001204,PKG_ENTRY_ID__ICON0_04_PNG = 0x00001205,PKG_ENTRY_ID__ICON0_05_PNG = 0x00001206,PKG_ENTRY_ID__ICON0_06_PNG = 0x00001207,PKG_ENTRY_ID__ICON0_07_PNG = 0x00001208,PKG_ENTRY_ID__ICON0_08_PNG = 0x00001209,PKG_ENTRY_ID__ICON0_09_PNG = 0x0000120A,PKG_ENTRY_ID__ICON0_10_PNG = 0x0000120B,PKG_ENTRY_ID__ICON0_11_PNG = 0x0000120C,PKG_ENTRY_ID__ICON0_12_PNG = 0x0000120D,PKG_ENTRY_ID__ICON0_13_PNG = 0x0000120E,PKG_ENTRY_ID__ICON0_14_PNG = 0x0000120F,PKG_ENTRY_ID__ICON0_15_PNG = 0x00001210,PKG_ENTRY_ID__ICON0_16_PNG = 0x00001211,PKG_ENTRY_ID__ICON0_17_PNG = 0x00001212,PKG_ENTRY_ID__ICON0_18_PNG = 0x00001213,PKG_ENTRY_ID__ICON0_19_PNG = 0x00001214,PKG_ENTRY_ID__ICON0_20_PNG = 0x00001215,PKG_ENTRY_ID__ICON0_21_PNG = 0x00001216,PKG_ENTRY_ID__ICON0_22_PNG = 0x00001217,PKG_ENTRY_ID__ICON0_23_PNG = 0x00001218,PKG_ENTRY_ID__ICON0_24_PNG = 0x00001219,PKG_ENTRY_ID__ICON0_25_PNG = 0x0000121A,PKG_ENTRY_ID__ICON0_26_PNG = 0x0000121B,PKG_ENTRY_ID__ICON0_27_PNG = 0x0000121C,PKG_ENTRY_ID__ICON0_28_PNG = 0x0000121D,PKG_ENTRY_ID__ICON0_29_PNG = 0x0000121E,PKG_ENTRY_ID__ICON0_30_PNG = 0x0000121F,PKG_ENTRY_ID__PIC0_PNG = 0x00001220,PKG_ENTRY_ID__SND0_AT9 = 0x00001240,PKG_ENTRY_ID__PIC1_00_PNG = 0x00001241,PKG_ENTRY_ID__PIC1_01_PNG = 0x00001242,PKG_ENTRY_ID__PIC1_02_PNG = 0x00001243,PKG_ENTRY_ID__PIC1_03_PNG = 0x00001244,PKG_ENTRY_ID__PIC1_04_PNG = 0x00001245,PKG_ENTRY_ID__PIC1_05_PNG = 0x00001246,PKG_ENTRY_ID__PIC1_06_PNG = 0x00001247,PKG_ENTRY_ID__PIC1_07_PNG = 0x00001248,PKG_ENTRY_ID__PIC1_08_PNG = 0x00001249,PKG_ENTRY_ID__PIC1_09_PNG = 0x0000124A,PKG_ENTRY_ID__PIC1_10_PNG = 0x0000124B,PKG_ENTRY_ID__PIC1_11_PNG = 0x0000124C,PKG_ENTRY_ID__PIC1_12_PNG = 0x0000124D,PKG_ENTRY_ID__PIC1_13_PNG = 0x0000124E,PKG_ENTRY_ID__PIC1_14_PNG = 0x0000124F,PKG_ENTRY_ID__PIC1_15_PNG = 0x00001250,PKG_ENTRY_ID__PIC1_16_PNG = 0x00001251,PKG_ENTRY_ID__PIC1_17_PNG = 0x00001252,PKG_ENTRY_ID__PIC1_18_PNG = 0x00001253,PKG_ENTRY_ID__PIC1_19_PNG = 0x00001254,PKG_ENTRY_ID__PIC1_20_PNG = 0x00001255,PKG_ENTRY_ID__PIC1_21_PNG = 0x00001256,PKG_ENTRY_ID__PIC1_22_PNG = 0x00001257,PKG_ENTRY_ID__PIC1_23_PNG = 0x00001258,PKG_ENTRY_ID__PIC1_24_PNG = 0x00001259,PKG_ENTRY_ID__PIC1_25_PNG = 0x0000125A,PKG_ENTRY_ID__PIC1_26_PNG = 0x0000125B,PKG_ENTRY_ID__PIC1_27_PNG = 0x0000125C,PKG_ENTRY_ID__PIC1_28_PNG = 0x0000125D,PKG_ENTRY_ID__PIC1_29_PNG = 0x0000125E,PKG_ENTRY_ID__PIC1_30_PNG = 0x0000125F,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_XML = 0x00001260,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_00_XML = 0x00001261,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_01_XML = 0x00001262,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_02_XML = 0x00001263,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_03_XML = 0x00001264,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_04_XML = 0x00001265,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_05_XML = 0x00001266,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_06_XML = 0x00001267,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_07_XML = 0x00001268,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_08_XML = 0x00001269,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_09_XML = 0x0000126A,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_10_XML = 0x0000126B,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_11_XML = 0x0000126C,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_12_XML = 0x0000126D,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_13_XML = 0x0000126E,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_14_XML = 0x0000126F,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_15_XML = 0x00001270,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_16_XML = 0x00001271,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_17_XML = 0x00001272,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_18_XML = 0x00001273,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_19_XML = 0x00001274,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_20_XML = 0x00001275,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_21_XML = 0x00001276,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_22_XML = 0x00001277,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_23_XML = 0x00001278,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_24_XML = 0x00001279,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_25_XML = 0x0000127A,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_26_XML = 0x0000127B,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_27_XML = 0x0000127C,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_28_XML = 0x0000127D,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_29_XML = 0x0000127E,PKG_ENTRY_ID__CHANGEINFO__CHANGEINFO_30_XML = 0x0000127F,PKG_ENTRY_ID__ICON0_DDS = 0x00001280,PKG_ENTRY_ID__ICON0_00_DDS = 0x00001281,PKG_ENTRY_ID__ICON0_01_DDS = 0x00001282,PKG_ENTRY_ID__ICON0_02_DDS = 0x00001283,PKG_ENTRY_ID__ICON0_03_DDS = 0x00001284,PKG_ENTRY_ID__ICON0_04_DDS = 0x00001285,PKG_ENTRY_ID__ICON0_05_DDS = 0x00001286,PKG_ENTRY_ID__ICON0_06_DDS = 0x00001287,PKG_ENTRY_ID__ICON0_07_DDS = 0x00001288,PKG_ENTRY_ID__ICON0_08_DDS = 0x00001289,PKG_ENTRY_ID__ICON0_09_DDS = 0x0000128A,PKG_ENTRY_ID__ICON0_10_DDS = 0x0000128B,PKG_ENTRY_ID__ICON0_11_DDS = 0x0000128C,PKG_ENTRY_ID__ICON0_12_DDS = 0x0000128D,PKG_ENTRY_ID__ICON0_13_DDS = 0x0000128E,PKG_ENTRY_ID__ICON0_14_DDS = 0x0000128F,PKG_ENTRY_ID__ICON0_15_DDS = 0x00001290,PKG_ENTRY_ID__ICON0_16_DDS = 0x00001291,PKG_ENTRY_ID__ICON0_17_DDS = 0x00001292,PKG_ENTRY_ID__ICON0_18_DDS = 0x00001293,PKG_ENTRY_ID__ICON0_19_DDS = 0x00001294,PKG_ENTRY_ID__ICON0_20_DDS = 0x00001295,PKG_ENTRY_ID__ICON0_21_DDS = 0x00001296,PKG_ENTRY_ID__ICON0_22_DDS = 0x00001297,PKG_ENTRY_ID__ICON0_23_DDS = 0x00001298,PKG_ENTRY_ID__ICON0_24_DDS = 0x00001299,PKG_ENTRY_ID__ICON0_25_DDS = 0x0000129A,PKG_ENTRY_ID__ICON0_26_DDS = 0x0000129B,PKG_ENTRY_ID__ICON0_27_DDS = 0x0000129C,PKG_ENTRY_ID__ICON0_28_DDS = 0x0000129D,PKG_ENTRY_ID__ICON0_29_DDS = 0x0000129E,PKG_ENTRY_ID__ICON0_30_DDS = 0x0000129F,PKG_ENTRY_ID__PIC0_DDS = 0x000012A0,PKG_ENTRY_ID__PIC1_DDS = 0x000012C0,PKG_ENTRY_ID__PIC1_00_DDS = 0x000012C1,PKG_ENTRY_ID__PIC1_01_DDS = 0x000012C2,PKG_ENTRY_ID__PIC1_02_DDS = 0x000012C3,PKG_ENTRY_ID__PIC1_03_DDS = 0x000012C4,PKG_ENTRY_ID__PIC1_04_DDS = 0x000012C5,PKG_ENTRY_ID__PIC1_05_DDS = 0x000012C6,PKG_ENTRY_ID__PIC1_06_DDS = 0x000012C7,PKG_ENTRY_ID__PIC1_07_DDS = 0x000012C8,PKG_ENTRY_ID__PIC1_08_DDS = 0x000012C9,PKG_ENTRY_ID__PIC1_09_DDS = 0x000012CA,PKG_ENTRY_ID__PIC1_10_DDS = 0x000012CB,PKG_ENTRY_ID__PIC1_11_DDS = 0x000012CC,PKG_ENTRY_ID__PIC1_12_DDS = 0x000012CD,PKG_ENTRY_ID__PIC1_13_DDS = 0x000012CE,PKG_ENTRY_ID__PIC1_14_DDS = 0x000012CF,PKG_ENTRY_ID__PIC1_15_DDS = 0x000012D0,PKG_ENTRY_ID__PIC1_16_DDS = 0x000012D1,PKG_ENTRY_ID__PIC1_17_DDS = 0x000012D2,PKG_ENTRY_ID__PIC1_18_DDS = 0x000012D3,PKG_ENTRY_ID__PIC1_19_DDS = 0x000012D4,PKG_ENTRY_ID__PIC1_20_DDS = 0x000012D5,PKG_ENTRY_ID__PIC1_21_DDS = 0x000012D6,PKG_ENTRY_ID__PIC1_22_DDS = 0x000012D7,PKG_ENTRY_ID__PIC1_23_DDS = 0x000012D8,PKG_ENTRY_ID__PIC1_24_DDS = 0x000012D9,PKG_ENTRY_ID__PIC1_25_DDS = 0x000012DA,PKG_ENTRY_ID__PIC1_26_DDS = 0x000012DB,PKG_ENTRY_ID__PIC1_27_DDS = 0x000012DC,PKG_ENTRY_ID__PIC1_28_DDS = 0x000012DD,PKG_ENTRY_ID__PIC1_29_DDS = 0x000012DE,PKG_ENTRY_ID__PIC1_30_DDS = 0x000012DF,PKG_ENTRY_ID__TROPHY__TROPHY00_TRP = 0x00001400,PKG_ENTRY_ID__TROPHY__TROPHY01_TRP = 0x00001401,PKG_ENTRY_ID__TROPHY__TROPHY02_TRP = 0x00001402,PKG_ENTRY_ID__TROPHY__TROPHY03_TRP = 0x00001403,PKG_ENTRY_ID__TROPHY__TROPHY04_TRP = 0x00001404,PKG_ENTRY_ID__TROPHY__TROPHY05_TRP = 0x00001405,PKG_ENTRY_ID__TROPHY__TROPHY06_TRP = 0x00001406,PKG_ENTRY_ID__TROPHY__TROPHY07_TRP = 0x00001407,PKG_ENTRY_ID__TROPHY__TROPHY08_TRP = 0x00001408,PKG_ENTRY_ID__TROPHY__TROPHY09_TRP = 0x00001409,PKG_ENTRY_ID__TROPHY__TROPHY10_TRP = 0x0000140A,PKG_ENTRY_ID__TROPHY__TROPHY11_TRP = 0x0000140B,PKG_ENTRY_ID__TROPHY__TROPHY12_TRP = 0x0000140C,PKG_ENTRY_ID__TROPHY__TROPHY13_TRP = 0x0000140D,PKG_ENTRY_ID__TROPHY__TROPHY14_TRP = 0x0000140E,PKG_ENTRY_ID__TROPHY__TROPHY15_TRP = 0x0000140F,PKG_ENTRY_ID__TROPHY__TROPHY16_TRP = 0x00001410,PKG_ENTRY_ID__TROPHY__TROPHY17_TRP = 0x00001411,PKG_ENTRY_ID__TROPHY__TROPHY18_TRP = 0x00001412,PKG_ENTRY_ID__TROPHY__TROPHY19_TRP = 0x00001413,PKG_ENTRY_ID__TROPHY__TROPHY20_TRP = 0x00001414,PKG_ENTRY_ID__TROPHY__TROPHY21_TRP = 0x00001415,PKG_ENTRY_ID__TROPHY__TROPHY22_TRP = 0x00001416,PKG_ENTRY_ID__TROPHY__TROPHY23_TRP = 0x00001417,PKG_ENTRY_ID__TROPHY__TROPHY24_TRP = 0x00001418,PKG_ENTRY_ID__TROPHY__TROPHY25_TRP = 0x00001419,PKG_ENTRY_ID__TROPHY__TROPHY26_TRP = 0x0000141A,PKG_ENTRY_ID__TROPHY__TROPHY27_TRP = 0x0000141B,PKG_ENTRY_ID__TROPHY__TROPHY28_TRP = 0x0000141C,PKG_ENTRY_ID__TROPHY__TROPHY29_TRP = 0x0000141D,PKG_ENTRY_ID__TROPHY__TROPHY30_TRP = 0x0000141E,PKG_ENTRY_ID__TROPHY__TROPHY31_TRP = 0x0000141F,PKG_ENTRY_ID__TROPHY__TROPHY32_TRP = 0x00001420,PKG_ENTRY_ID__TROPHY__TROPHY33_TRP = 0x00001421,PKG_ENTRY_ID__TROPHY__TROPHY34_TRP = 0x00001422,PKG_ENTRY_ID__TROPHY__TROPHY35_TRP = 0x00001423,PKG_ENTRY_ID__TROPHY__TROPHY36_TRP = 0x00001424,PKG_ENTRY_ID__TROPHY__TROPHY37_TRP = 0x00001425,PKG_ENTRY_ID__TROPHY__TROPHY38_TRP = 0x00001426,PKG_ENTRY_ID__TROPHY__TROPHY39_TRP = 0x00001427,PKG_ENTRY_ID__TROPHY__TROPHY40_TRP = 0x00001428,PKG_ENTRY_ID__TROPHY__TROPHY41_TRP = 0x00001429,PKG_ENTRY_ID__TROPHY__TROPHY42_TRP = 0x0000142A,PKG_ENTRY_ID__TROPHY__TROPHY43_TRP = 0x0000142B,PKG_ENTRY_ID__TROPHY__TROPHY44_TRP = 0x0000142C,PKG_ENTRY_ID__TROPHY__TROPHY45_TRP = 0x0000142D,PKG_ENTRY_ID__TROPHY__TROPHY46_TRP = 0x0000142E,PKG_ENTRY_ID__TROPHY__TROPHY47_TRP = 0x0000142F,PKG_ENTRY_ID__TROPHY__TROPHY48_TRP = 0x00001430,PKG_ENTRY_ID__TROPHY__TROPHY49_TRP = 0x00001431,PKG_ENTRY_ID__TROPHY__TROPHY50_TRP = 0x00001432,PKG_ENTRY_ID__TROPHY__TROPHY51_TRP = 0x00001433,PKG_ENTRY_ID__TROPHY__TROPHY52_TRP = 0x00001434,PKG_ENTRY_ID__TROPHY__TROPHY53_TRP = 0x00001435,PKG_ENTRY_ID__TROPHY__TROPHY54_TRP = 0x00001436,PKG_ENTRY_ID__TROPHY__TROPHY55_TRP = 0x00001437,PKG_ENTRY_ID__TROPHY__TROPHY56_TRP = 0x00001438,PKG_ENTRY_ID__TROPHY__TROPHY57_TRP = 0x00001439,PKG_ENTRY_ID__TROPHY__TROPHY58_TRP = 0x0000143A,PKG_ENTRY_ID__TROPHY__TROPHY59_TRP = 0x0000143B,PKG_ENTRY_ID__TROPHY__TROPHY60_TRP = 0x0000143C,PKG_ENTRY_ID__TROPHY__TROPHY61_TRP = 0x0000143D,PKG_ENTRY_ID__TROPHY__TROPHY62_TRP = 0x0000143E,PKG_ENTRY_ID__TROPHY__TROPHY63_TRP = 0x0000143F,PKG_ENTRY_ID__TROPHY__TROPHY64_TRP = 0x00001440,PKG_ENTRY_ID__TROPHY__TROPHY65_TRP = 0x00001441,PKG_ENTRY_ID__TROPHY__TROPHY66_TRP = 0x00001442,PKG_ENTRY_ID__TROPHY__TROPHY67_TRP = 0x00001443,PKG_ENTRY_ID__TROPHY__TROPHY68_TRP = 0x00001444,PKG_ENTRY_ID__TROPHY__TROPHY69_TRP = 0x00001445,PKG_ENTRY_ID__TROPHY__TROPHY70_TRP = 0x00001446,PKG_ENTRY_ID__TROPHY__TROPHY71_TRP = 0x00001447,PKG_ENTRY_ID__TROPHY__TROPHY72_TRP = 0x00001448,PKG_ENTRY_ID__TROPHY__TROPHY73_TRP = 0x00001449,PKG_ENTRY_ID__TROPHY__TROPHY74_TRP = 0x0000144A,PKG_ENTRY_ID__TROPHY__TROPHY75_TRP = 0x0000144B,PKG_ENTRY_ID__TROPHY__TROPHY76_TRP = 0x0000144C,PKG_ENTRY_ID__TROPHY__TROPHY77_TRP = 0x0000144D,PKG_ENTRY_ID__TROPHY__TROPHY78_TRP = 0x0000144E,PKG_ENTRY_ID__TROPHY__TROPHY79_TRP = 0x0000144F,PKG_ENTRY_ID__TROPHY__TROPHY80_TRP = 0x00001450,PKG_ENTRY_ID__TROPHY__TROPHY81_TRP = 0x00001451,PKG_ENTRY_ID__TROPHY__TROPHY82_TRP = 0x00001452,PKG_ENTRY_ID__TROPHY__TROPHY83_TRP = 0x00001453,PKG_ENTRY_ID__TROPHY__TROPHY84_TRP = 0x00001454,PKG_ENTRY_ID__TROPHY__TROPHY85_TRP = 0x00001455,PKG_ENTRY_ID__TROPHY__TROPHY86_TRP = 0x00001456,PKG_ENTRY_ID__TROPHY__TROPHY87_TRP = 0x00001457,PKG_ENTRY_ID__TROPHY__TROPHY88_TRP = 0x00001458,PKG_ENTRY_ID__TROPHY__TROPHY89_TRP = 0x00001459,PKG_ENTRY_ID__TROPHY__TROPHY90_TRP = 0x0000145A,PKG_ENTRY_ID__TROPHY__TROPHY91_TRP = 0x0000145B,PKG_ENTRY_ID__TROPHY__TROPHY92_TRP = 0x0000145C,PKG_ENTRY_ID__TROPHY__TROPHY93_TRP = 0x0000145D,PKG_ENTRY_ID__TROPHY__TROPHY94_TRP = 0x0000145E,PKG_ENTRY_ID__TROPHY__TROPHY95_TRP = 0x0000145F,PKG_ENTRY_ID__TROPHY__TROPHY96_TRP = 0x00001460,PKG_ENTRY_ID__TROPHY__TROPHY97_TRP = 0x00001461,PKG_ENTRY_ID__TROPHY__TROPHY98_TRP = 0x00001462,PKG_ENTRY_ID__TROPHY__TROPHY99_TRP = 0x00001463,PKG_ENTRY_ID__KEYMAP_RP__001_PNG = 0x00001600,PKG_ENTRY_ID__KEYMAP_RP__002_PNG = 0x00001601,PKG_ENTRY_ID__KEYMAP_RP__003_PNG = 0x00001602,PKG_ENTRY_ID__KEYMAP_RP__004_PNG = 0x00001603,PKG_ENTRY_ID__KEYMAP_RP__005_PNG = 0x00001604,PKG_ENTRY_ID__KEYMAP_RP__006_PNG = 0x00001605,PKG_ENTRY_ID__KEYMAP_RP__007_PNG = 0x00001606,PKG_ENTRY_ID__KEYMAP_RP__008_PNG = 0x00001607,PKG_ENTRY_ID__KEYMAP_RP__009_PNG = 0x00001608,PKG_ENTRY_ID__KEYMAP_RP__010_PNG = 0x00001609,PKG_ENTRY_ID__KEYMAP_RP__00__001_PNG = 0x00001610,PKG_ENTRY_ID__KEYMAP_RP__00__002_PNG = 0x00001611,PKG_ENTRY_ID__KEYMAP_RP__00__003_PNG = 0x00001612,PKG_ENTRY_ID__KEYMAP_RP__00__004_PNG = 0x00001613,PKG_ENTRY_ID__KEYMAP_RP__00__005_PNG = 0x00001614,PKG_ENTRY_ID__KEYMAP_RP__00__006_PNG = 0x00001615,PKG_ENTRY_ID__KEYMAP_RP__00__007_PNG = 0x00001616,PKG_ENTRY_ID__KEYMAP_RP__00__008_PNG = 0x00001617,PKG_ENTRY_ID__KEYMAP_RP__00__009_PNG = 0x00001618,PKG_ENTRY_ID__KEYMAP_RP__00__010_PNG = 0x00001619,PKG_ENTRY_ID__KEYMAP_RP__01__001_PNG = 0x00001620,PKG_ENTRY_ID__KEYMAP_RP__01__002_PNG = 0x00001621,PKG_ENTRY_ID__KEYMAP_RP__01__003_PNG = 0x00001622,PKG_ENTRY_ID__KEYMAP_RP__01__004_PNG = 0x00001623,PKG_ENTRY_ID__KEYMAP_RP__01__005_PNG = 0x00001624,PKG_ENTRY_ID__KEYMAP_RP__01__006_PNG = 0x00001625,PKG_ENTRY_ID__KEYMAP_RP__01__007_PNG = 0x00001626,PKG_ENTRY_ID__KEYMAP_RP__01__008_PNG = 0x00001627,PKG_ENTRY_ID__KEYMAP_RP__01__009_PNG = 0x00001628,PKG_ENTRY_ID__KEYMAP_RP__01__010_PNG = 0x00001629,PKG_ENTRY_ID__KEYMAP_RP__02__001_PNG = 0x00001630,PKG_ENTRY_ID__KEYMAP_RP__02__002_PNG = 0x00001631,PKG_ENTRY_ID__KEYMAP_RP__02__003_PNG = 0x00001632,PKG_ENTRY_ID__KEYMAP_RP__02__004_PNG = 0x00001633,PKG_ENTRY_ID__KEYMAP_RP__02__005_PNG = 0x00001634,PKG_ENTRY_ID__KEYMAP_RP__02__006_PNG = 0x00001635,PKG_ENTRY_ID__KEYMAP_RP__02__007_PNG = 0x00001636,PKG_ENTRY_ID__KEYMAP_RP__02__008_PNG = 0x00001637,PKG_ENTRY_ID__KEYMAP_RP__02__009_PNG = 0x00001638,PKG_ENTRY_ID__KEYMAP_RP__02__010_PNG = 0x00001639,PKG_ENTRY_ID__KEYMAP_RP__03__001_PNG = 0x00001640,PKG_ENTRY_ID__KEYMAP_RP__03__002_PNG = 0x00001641,PKG_ENTRY_ID__KEYMAP_RP__03__003_PNG = 0x00001642,PKG_ENTRY_ID__KEYMAP_RP__03__004_PNG = 0x00001643,PKG_ENTRY_ID__KEYMAP_RP__03__005_PNG = 0x00001644,PKG_ENTRY_ID__KEYMAP_RP__03__006_PNG = 0x00001645,PKG_ENTRY_ID__KEYMAP_RP__03__007_PNG = 0x00001646,PKG_ENTRY_ID__KEYMAP_RP__03__008_PNG = 0x00001647,PKG_ENTRY_ID__KEYMAP_RP__03__009_PNG = 0x00001648,PKG_ENTRY_ID__KEYMAP_RP__03__010_PNG = 0x00001649,PKG_ENTRY_ID__KEYMAP_RP__04__001_PNG = 0x00001650,PKG_ENTRY_ID__KEYMAP_RP__04__002_PNG = 0x00001651,PKG_ENTRY_ID__KEYMAP_RP__04__003_PNG = 0x00001652,PKG_ENTRY_ID__KEYMAP_RP__04__004_PNG = 0x00001653,PKG_ENTRY_ID__KEYMAP_RP__04__005_PNG = 0x00001654,PKG_ENTRY_ID__KEYMAP_RP__04__006_PNG = 0x00001655,PKG_ENTRY_ID__KEYMAP_RP__04__007_PNG = 0x00001656,PKG_ENTRY_ID__KEYMAP_RP__04__008_PNG = 0x00001657,PKG_ENTRY_ID__KEYMAP_RP__04__009_PNG = 0x00001658,PKG_ENTRY_ID__KEYMAP_RP__04__010_PNG = 0x00001659,PKG_ENTRY_ID__KEYMAP_RP__05__001_PNG = 0x00001660,PKG_ENTRY_ID__KEYMAP_RP__05__002_PNG = 0x00001661,PKG_ENTRY_ID__KEYMAP_RP__05__003_PNG = 0x00001662,PKG_ENTRY_ID__KEYMAP_RP__05__004_PNG = 0x00001663,PKG_ENTRY_ID__KEYMAP_RP__05__005_PNG = 0x00001664,PKG_ENTRY_ID__KEYMAP_RP__05__006_PNG = 0x00001665,PKG_ENTRY_ID__KEYMAP_RP__05__007_PNG = 0x00001666,PKG_ENTRY_ID__KEYMAP_RP__05__008_PNG = 0x00001667,PKG_ENTRY_ID__KEYMAP_RP__05__009_PNG = 0x00001668,PKG_ENTRY_ID__KEYMAP_RP__05__010_PNG = 0x00001669,PKG_ENTRY_ID__KEYMAP_RP__06__001_PNG = 0x00001670,PKG_ENTRY_ID__KEYMAP_RP__06__002_PNG = 0x00001671,PKG_ENTRY_ID__KEYMAP_RP__06__003_PNG = 0x00001672,PKG_ENTRY_ID__KEYMAP_RP__06__004_PNG = 0x00001673,PKG_ENTRY_ID__KEYMAP_RP__06__005_PNG = 0x00001674,PKG_ENTRY_ID__KEYMAP_RP__06__006_PNG = 0x00001675,PKG_ENTRY_ID__KEYMAP_RP__06__007_PNG = 0x00001676,PKG_ENTRY_ID__KEYMAP_RP__06__008_PNG = 0x00001677,PKG_ENTRY_ID__KEYMAP_RP__06__009_PNG = 0x00001678,PKG_ENTRY_ID__KEYMAP_RP__06__010_PNG = 0x00001679,PKG_ENTRY_ID__KEYMAP_RP__07__001_PNG = 0x00001680,PKG_ENTRY_ID__KEYMAP_RP__07__002_PNG = 0x00001681,PKG_ENTRY_ID__KEYMAP_RP__07__003_PNG = 0x00001682,PKG_ENTRY_ID__KEYMAP_RP__07__004_PNG = 0x00001683,PKG_ENTRY_ID__KEYMAP_RP__07__005_PNG = 0x00001684,PKG_ENTRY_ID__KEYMAP_RP__07__006_PNG = 0x00001685,PKG_ENTRY_ID__KEYMAP_RP__07__007_PNG = 0x00001686,PKG_ENTRY_ID__KEYMAP_RP__07__008_PNG = 0x00001687,PKG_ENTRY_ID__KEYMAP_RP__07__009_PNG = 0x00001688,PKG_ENTRY_ID__KEYMAP_RP__07__010_PNG = 0x00001689,PKG_ENTRY_ID__KEYMAP_RP__08__001_PNG = 0x00001690,PKG_ENTRY_ID__KEYMAP_RP__08__002_PNG = 0x00001691,PKG_ENTRY_ID__KEYMAP_RP__08__003_PNG = 0x00001692,PKG_ENTRY_ID__KEYMAP_RP__08__004_PNG = 0x00001693,PKG_ENTRY_ID__KEYMAP_RP__08__005_PNG = 0x00001694,PKG_ENTRY_ID__KEYMAP_RP__08__006_PNG = 0x00001695,PKG_ENTRY_ID__KEYMAP_RP__08__007_PNG = 0x00001696,PKG_ENTRY_ID__KEYMAP_RP__08__008_PNG = 0x00001697,PKG_ENTRY_ID__KEYMAP_RP__08__009_PNG = 0x00001698,PKG_ENTRY_ID__KEYMAP_RP__08__010_PNG = 0x00001699,PKG_ENTRY_ID__KEYMAP_RP__09__001_PNG = 0x000016A0,PKG_ENTRY_ID__KEYMAP_RP__09__002_PNG = 0x000016A1,PKG_ENTRY_ID__KEYMAP_RP__09__003_PNG = 0x000016A2,PKG_ENTRY_ID__KEYMAP_RP__09__004_PNG = 0x000016A3,PKG_ENTRY_ID__KEYMAP_RP__09__005_PNG = 0x000016A4,PKG_ENTRY_ID__KEYMAP_RP__09__006_PNG = 0x000016A5,PKG_ENTRY_ID__KEYMAP_RP__09__007_PNG = 0x000016A6,PKG_ENTRY_ID__KEYMAP_RP__09__008_PNG = 0x000016A7,PKG_ENTRY_ID__KEYMAP_RP__09__009_PNG = 0x000016A8,PKG_ENTRY_ID__KEYMAP_RP__09__010_PNG = 0x000016A9,PKG_ENTRY_ID__KEYMAP_RP__10__001_PNG = 0x000016B0,PKG_ENTRY_ID__KEYMAP_RP__10__002_PNG = 0x000016B1,PKG_ENTRY_ID__KEYMAP_RP__10__003_PNG = 0x000016B2,PKG_ENTRY_ID__KEYMAP_RP__10__004_PNG = 0x000016B3,PKG_ENTRY_ID__KEYMAP_RP__10__005_PNG = 0x000016B4,PKG_ENTRY_ID__KEYMAP_RP__10__006_PNG = 0x000016B5,PKG_ENTRY_ID__KEYMAP_RP__10__007_PNG = 0x000016B6,PKG_ENTRY_ID__KEYMAP_RP__10__008_PNG = 0x000016B7,PKG_ENTRY_ID__KEYMAP_RP__10__009_PNG = 0x000016B8,PKG_ENTRY_ID__KEYMAP_RP__10__010_PNG = 0x000016B9,PKG_ENTRY_ID__KEYMAP_RP__11__001_PNG = 0x000016C0,PKG_ENTRY_ID__KEYMAP_RP__11__002_PNG = 0x000016C1,PKG_ENTRY_ID__KEYMAP_RP__11__003_PNG = 0x000016C2,PKG_ENTRY_ID__KEYMAP_RP__11__004_PNG = 0x000016C3,PKG_ENTRY_ID__KEYMAP_RP__11__005_PNG = 0x000016C4,PKG_ENTRY_ID__KEYMAP_RP__11__006_PNG = 0x000016C5,PKG_ENTRY_ID__KEYMAP_RP__11__007_PNG = 0x000016C6,PKG_ENTRY_ID__KEYMAP_RP__11__008_PNG = 0x000016C7,PKG_ENTRY_ID__KEYMAP_RP__11__009_PNG = 0x000016C8,PKG_ENTRY_ID__KEYMAP_RP__11__010_PNG = 0x000016C9,PKG_ENTRY_ID__KEYMAP_RP__12__001_PNG = 0x000016D0,PKG_ENTRY_ID__KEYMAP_RP__12__002_PNG = 0x000016D1,PKG_ENTRY_ID__KEYMAP_RP__12__003_PNG = 0x000016D2,PKG_ENTRY_ID__KEYMAP_RP__12__004_PNG = 0x000016D3,PKG_ENTRY_ID__KEYMAP_RP__12__005_PNG = 0x000016D4,PKG_ENTRY_ID__KEYMAP_RP__12__006_PNG = 0x000016D5,PKG_ENTRY_ID__KEYMAP_RP__12__007_PNG = 0x000016D6,PKG_ENTRY_ID__KEYMAP_RP__12__008_PNG = 0x000016D7,PKG_ENTRY_ID__KEYMAP_RP__12__009_PNG = 0x000016D8,PKG_ENTRY_ID__KEYMAP_RP__12__010_PNG = 0x000016D9,PKG_ENTRY_ID__KEYMAP_RP__13__001_PNG = 0x000016E0,PKG_ENTRY_ID__KEYMAP_RP__13__002_PNG = 0x000016E1,PKG_ENTRY_ID__KEYMAP_RP__13__003_PNG = 0x000016E2,PKG_ENTRY_ID__KEYMAP_RP__13__004_PNG = 0x000016E3,PKG_ENTRY_ID__KEYMAP_RP__13__005_PNG = 0x000016E4,PKG_ENTRY_ID__KEYMAP_RP__13__006_PNG = 0x000016E5,PKG_ENTRY_ID__KEYMAP_RP__13__007_PNG = 0x000016E6,PKG_ENTRY_ID__KEYMAP_RP__13__008_PNG = 0x000016E7,PKG_ENTRY_ID__KEYMAP_RP__13__009_PNG = 0x000016E8,PKG_ENTRY_ID__KEYMAP_RP__13__010_PNG = 0x000016E9,PKG_ENTRY_ID__KEYMAP_RP__14__001_PNG = 0x000016F0,PKG_ENTRY_ID__KEYMAP_RP__14__002_PNG = 0x000016F1,PKG_ENTRY_ID__KEYMAP_RP__14__003_PNG = 0x000016F2,PKG_ENTRY_ID__KEYMAP_RP__14__004_PNG = 0x000016F3,PKG_ENTRY_ID__KEYMAP_RP__14__005_PNG = 0x000016F4,PKG_ENTRY_ID__KEYMAP_RP__14__006_PNG = 0x000016F5,PKG_ENTRY_ID__KEYMAP_RP__14__007_PNG = 0x000016F6,PKG_ENTRY_ID__KEYMAP_RP__14__008_PNG = 0x000016F7,PKG_ENTRY_ID__KEYMAP_RP__14__009_PNG = 0x000016F8,PKG_ENTRY_ID__KEYMAP_RP__14__010_PNG = 0x000016F9,PKG_ENTRY_ID__KEYMAP_RP__15__001_PNG = 0x00001700,PKG_ENTRY_ID__KEYMAP_RP__15__002_PNG = 0x00001701,PKG_ENTRY_ID__KEYMAP_RP__15__003_PNG = 0x00001702,PKG_ENTRY_ID__KEYMAP_RP__15__004_PNG = 0x00001703,PKG_ENTRY_ID__KEYMAP_RP__15__005_PNG = 0x00001704,PKG_ENTRY_ID__KEYMAP_RP__15__006_PNG = 0x00001705,PKG_ENTRY_ID__KEYMAP_RP__15__007_PNG = 0x00001706,PKG_ENTRY_ID__KEYMAP_RP__15__008_PNG = 0x00001707,PKG_ENTRY_ID__KEYMAP_RP__15__009_PNG = 0x00001708,PKG_ENTRY_ID__KEYMAP_RP__15__010_PNG = 0x00001709,PKG_ENTRY_ID__KEYMAP_RP__16__001_PNG = 0x00001710,PKG_ENTRY_ID__KEYMAP_RP__16__002_PNG = 0x00001711,PKG_ENTRY_ID__KEYMAP_RP__16__003_PNG = 0x00001712,PKG_ENTRY_ID__KEYMAP_RP__16__004_PNG = 0x00001713,PKG_ENTRY_ID__KEYMAP_RP__16__005_PNG = 0x00001714,PKG_ENTRY_ID__KEYMAP_RP__16__006_PNG = 0x00001715,PKG_ENTRY_ID__KEYMAP_RP__16__007_PNG = 0x00001716,PKG_ENTRY_ID__KEYMAP_RP__16__008_PNG = 0x00001717,PKG_ENTRY_ID__KEYMAP_RP__16__009_PNG = 0x00001718,PKG_ENTRY_ID__KEYMAP_RP__16__010_PNG = 0x00001719,PKG_ENTRY_ID__KEYMAP_RP__17__001_PNG = 0x00001720,PKG_ENTRY_ID__KEYMAP_RP__17__002_PNG = 0x00001721,PKG_ENTRY_ID__KEYMAP_RP__17__003_PNG = 0x00001722,PKG_ENTRY_ID__KEYMAP_RP__17__004_PNG = 0x00001723,PKG_ENTRY_ID__KEYMAP_RP__17__005_PNG = 0x00001724,PKG_ENTRY_ID__KEYMAP_RP__17__006_PNG = 0x00001725,PKG_ENTRY_ID__KEYMAP_RP__17__007_PNG = 0x00001726,PKG_ENTRY_ID__KEYMAP_RP__17__008_PNG = 0x00001727,PKG_ENTRY_ID__KEYMAP_RP__17__009_PNG = 0x00001728,PKG_ENTRY_ID__KEYMAP_RP__17__010_PNG = 0x00001729,PKG_ENTRY_ID__KEYMAP_RP__18__001_PNG = 0x00001730,PKG_ENTRY_ID__KEYMAP_RP__18__002_PNG = 0x00001731,PKG_ENTRY_ID__KEYMAP_RP__18__003_PNG = 0x00001732,PKG_ENTRY_ID__KEYMAP_RP__18__004_PNG = 0x00001733,PKG_ENTRY_ID__KEYMAP_RP__18__005_PNG = 0x00001734,PKG_ENTRY_ID__KEYMAP_RP__18__006_PNG = 0x00001735,PKG_ENTRY_ID__KEYMAP_RP__18__007_PNG = 0x00001736,PKG_ENTRY_ID__KEYMAP_RP__18__008_PNG = 0x00001737,PKG_ENTRY_ID__KEYMAP_RP__18__009_PNG = 0x00001738,PKG_ENTRY_ID__KEYMAP_RP__18__010_PNG = 0x00001739,PKG_ENTRY_ID__KEYMAP_RP__19__001_PNG = 0x00001740,PKG_ENTRY_ID__KEYMAP_RP__19__002_PNG = 0x00001741,PKG_ENTRY_ID__KEYMAP_RP__19__003_PNG = 0x00001742,PKG_ENTRY_ID__KEYMAP_RP__19__004_PNG = 0x00001743,PKG_ENTRY_ID__KEYMAP_RP__19__005_PNG = 0x00001744,PKG_ENTRY_ID__KEYMAP_RP__19__006_PNG = 0x00001745,PKG_ENTRY_ID__KEYMAP_RP__19__007_PNG = 0x00001746,PKG_ENTRY_ID__KEYMAP_RP__19__008_PNG = 0x00001747,PKG_ENTRY_ID__KEYMAP_RP__19__009_PNG = 0x00001748,PKG_ENTRY_ID__KEYMAP_RP__19__010_PNG = 0x00001749,PKG_ENTRY_ID__KEYMAP_RP__20__001_PNG = 0x00001750,PKG_ENTRY_ID__KEYMAP_RP__20__002_PNG = 0x00001751,PKG_ENTRY_ID__KEYMAP_RP__20__003_PNG = 0x00001752,PKG_ENTRY_ID__KEYMAP_RP__20__004_PNG = 0x00001753,PKG_ENTRY_ID__KEYMAP_RP__20__005_PNG = 0x00001754,PKG_ENTRY_ID__KEYMAP_RP__20__006_PNG = 0x00001755,PKG_ENTRY_ID__KEYMAP_RP__20__007_PNG = 0x00001756,PKG_ENTRY_ID__KEYMAP_RP__20__008_PNG = 0x00001757,PKG_ENTRY_ID__KEYMAP_RP__20__009_PNG = 0x00001758,PKG_ENTRY_ID__KEYMAP_RP__20__010_PNG = 0x00001759,PKG_ENTRY_ID__KEYMAP_RP__21__001_PNG = 0x00001760,PKG_ENTRY_ID__KEYMAP_RP__21__002_PNG = 0x00001761,PKG_ENTRY_ID__KEYMAP_RP__21__003_PNG = 0x00001762,PKG_ENTRY_ID__KEYMAP_RP__21__004_PNG = 0x00001763,PKG_ENTRY_ID__KEYMAP_RP__21__005_PNG = 0x00001764,PKG_ENTRY_ID__KEYMAP_RP__21__006_PNG = 0x00001765,PKG_ENTRY_ID__KEYMAP_RP__21__007_PNG = 0x00001766,PKG_ENTRY_ID__KEYMAP_RP__21__008_PNG = 0x00001767,PKG_ENTRY_ID__KEYMAP_RP__21__009_PNG = 0x00001768,PKG_ENTRY_ID__KEYMAP_RP__21__010_PNG = 0x00001769,PKG_ENTRY_ID__KEYMAP_RP__22__001_PNG = 0x00001770,PKG_ENTRY_ID__KEYMAP_RP__22__002_PNG = 0x00001771,PKG_ENTRY_ID__KEYMAP_RP__22__003_PNG = 0x00001772,PKG_ENTRY_ID__KEYMAP_RP__22__004_PNG = 0x00001773,PKG_ENTRY_ID__KEYMAP_RP__22__005_PNG = 0x00001774,PKG_ENTRY_ID__KEYMAP_RP__22__006_PNG = 0x00001775,PKG_ENTRY_ID__KEYMAP_RP__22__007_PNG = 0x00001776,PKG_ENTRY_ID__KEYMAP_RP__22__008_PNG = 0x00001777,PKG_ENTRY_ID__KEYMAP_RP__22__009_PNG = 0x00001778,PKG_ENTRY_ID__KEYMAP_RP__22__010_PNG = 0x00001779,PKG_ENTRY_ID__KEYMAP_RP__23__001_PNG = 0x00001780,PKG_ENTRY_ID__KEYMAP_RP__23__002_PNG = 0x00001781,PKG_ENTRY_ID__KEYMAP_RP__23__003_PNG = 0x00001782,PKG_ENTRY_ID__KEYMAP_RP__23__004_PNG = 0x00001783,PKG_ENTRY_ID__KEYMAP_RP__23__005_PNG = 0x00001784,PKG_ENTRY_ID__KEYMAP_RP__23__006_PNG = 0x00001785,PKG_ENTRY_ID__KEYMAP_RP__23__007_PNG = 0x00001786,PKG_ENTRY_ID__KEYMAP_RP__23__008_PNG = 0x00001787,PKG_ENTRY_ID__KEYMAP_RP__23__009_PNG = 0x00001788,PKG_ENTRY_ID__KEYMAP_RP__23__010_PNG = 0x00001789,PKG_ENTRY_ID__KEYMAP_RP__24__001_PNG = 0x00001790,PKG_ENTRY_ID__KEYMAP_RP__24__002_PNG = 0x00001791,PKG_ENTRY_ID__KEYMAP_RP__24__003_PNG = 0x00001792,PKG_ENTRY_ID__KEYMAP_RP__24__004_PNG = 0x00001793,PKG_ENTRY_ID__KEYMAP_RP__24__005_PNG = 0x00001794,PKG_ENTRY_ID__KEYMAP_RP__24__006_PNG = 0x00001795,PKG_ENTRY_ID__KEYMAP_RP__24__007_PNG = 0x00001796,PKG_ENTRY_ID__KEYMAP_RP__24__008_PNG = 0x00001797,PKG_ENTRY_ID__KEYMAP_RP__24__009_PNG = 0x00001798,PKG_ENTRY_ID__KEYMAP_RP__24__010_PNG = 0x00001799,PKG_ENTRY_ID__KEYMAP_RP__25__001_PNG = 0x000017A0,PKG_ENTRY_ID__KEYMAP_RP__25__002_PNG = 0x000017A1,PKG_ENTRY_ID__KEYMAP_RP__25__003_PNG = 0x000017A2,PKG_ENTRY_ID__KEYMAP_RP__25__004_PNG = 0x000017A3,PKG_ENTRY_ID__KEYMAP_RP__25__005_PNG = 0x000017A4,PKG_ENTRY_ID__KEYMAP_RP__25__006_PNG = 0x000017A5,PKG_ENTRY_ID__KEYMAP_RP__25__007_PNG = 0x000017A6,PKG_ENTRY_ID__KEYMAP_RP__25__008_PNG = 0x000017A7,PKG_ENTRY_ID__KEYMAP_RP__25__009_PNG = 0x000017A8,PKG_ENTRY_ID__KEYMAP_RP__25__010_PNG = 0x000017A9,PKG_ENTRY_ID__KEYMAP_RP__26__001_PNG = 0x000017B0,PKG_ENTRY_ID__KEYMAP_RP__26__002_PNG = 0x000017B1,PKG_ENTRY_ID__KEYMAP_RP__26__003_PNG = 0x000017B2,PKG_ENTRY_ID__KEYMAP_RP__26__004_PNG = 0x000017B3,PKG_ENTRY_ID__KEYMAP_RP__26__005_PNG = 0x000017B4,PKG_ENTRY_ID__KEYMAP_RP__26__006_PNG = 0x000017B5,PKG_ENTRY_ID__KEYMAP_RP__26__007_PNG = 0x000017B6,PKG_ENTRY_ID__KEYMAP_RP__26__008_PNG = 0x000017B7,PKG_ENTRY_ID__KEYMAP_RP__26__009_PNG = 0x000017B8,PKG_ENTRY_ID__KEYMAP_RP__26__010_PNG = 0x000017B9,PKG_ENTRY_ID__KEYMAP_RP__27__001_PNG = 0x000017C0,PKG_ENTRY_ID__KEYMAP_RP__27__002_PNG = 0x000017C1,PKG_ENTRY_ID__KEYMAP_RP__27__003_PNG = 0x000017C2,PKG_ENTRY_ID__KEYMAP_RP__27__004_PNG = 0x000017C3,PKG_ENTRY_ID__KEYMAP_RP__27__005_PNG = 0x000017C4,PKG_ENTRY_ID__KEYMAP_RP__27__006_PNG = 0x000017C5,PKG_ENTRY_ID__KEYMAP_RP__27__007_PNG = 0x000017C6,PKG_ENTRY_ID__KEYMAP_RP__27__008_PNG = 0x000017C7,PKG_ENTRY_ID__KEYMAP_RP__27__009_PNG = 0x000017C8,PKG_ENTRY_ID__KEYMAP_RP__27__010_PNG = 0x000017C9,PKG_ENTRY_ID__KEYMAP_RP__28__001_PNG = 0x000017D0,PKG_ENTRY_ID__KEYMAP_RP__28__002_PNG = 0x000017D1,PKG_ENTRY_ID__KEYMAP_RP__28__003_PNG = 0x000017D2,PKG_ENTRY_ID__KEYMAP_RP__28__004_PNG = 0x000017D3,PKG_ENTRY_ID__KEYMAP_RP__28__005_PNG = 0x000017D4,PKG_ENTRY_ID__KEYMAP_RP__28__006_PNG = 0x000017D5,PKG_ENTRY_ID__KEYMAP_RP__28__007_PNG = 0x000017D6,PKG_ENTRY_ID__KEYMAP_RP__28__008_PNG = 0x000017D7,PKG_ENTRY_ID__KEYMAP_RP__28__009_PNG = 0x000017D8,PKG_ENTRY_ID__KEYMAP_RP__28__010_PNG = 0x000017D9,PKG_ENTRY_ID__KEYMAP_RP__29__001_PNG = 0x000017E0,PKG_ENTRY_ID__KEYMAP_RP__29__002_PNG = 0x000017E1,PKG_ENTRY_ID__KEYMAP_RP__29__003_PNG = 0x000017E2,PKG_ENTRY_ID__KEYMAP_RP__29__004_PNG = 0x000017E3,PKG_ENTRY_ID__KEYMAP_RP__29__005_PNG = 0x000017E4,PKG_ENTRY_ID__KEYMAP_RP__29__006_PNG = 0x000017E5,PKG_ENTRY_ID__KEYMAP_RP__29__007_PNG = 0x000017E6,PKG_ENTRY_ID__KEYMAP_RP__29__008_PNG = 0x000017E7,PKG_ENTRY_ID__KEYMAP_RP__29__009_PNG = 0x000017E8,PKG_ENTRY_ID__KEYMAP_RP__29__010_PNG = 0x000017E9,PKG_ENTRY_ID__KEYMAP_RP__30__001_PNG = 0x000017F0,PKG_ENTRY_ID__KEYMAP_RP__30__002_PNG = 0x000017F1,PKG_ENTRY_ID__KEYMAP_RP__30__003_PNG = 0x000017F2,PKG_ENTRY_ID__KEYMAP_RP__30__004_PNG = 0x000017F3,PKG_ENTRY_ID__KEYMAP_RP__30__005_PNG = 0x000017F4,PKG_ENTRY_ID__KEYMAP_RP__30__006_PNG = 0x000017F5,PKG_ENTRY_ID__KEYMAP_RP__30__007_PNG = 0x000017F6,PKG_ENTRY_ID__KEYMAP_RP__30__008_PNG = 0x000017F7,PKG_ENTRY_ID__KEYMAP_RP__30__009_PNG = 0x000017F8,PKG_ENTRY_ID__KEYMAP_RP__30__010_PNG = 0x000017F9,};#define PKG_ENTRY_NAME__PARAM_SFO "param.sfo"#define PKG_ENTRY_NAME__SHAREPARAM_JSON "shareparam.json"#define PKG_SC_ENTRY_ID_START PKG_ENTRY_ID__LICENSE_DATstruct pkg_entry_keyset {uint8_t key[16];uint8_t iv[16];};CT_SIZE_ASSERT(struct pkg_entry_keyset, PKG_ENTRY_KEYSET_SIZE);#define PKG_MAX_ENTRY_KEYS 7#define PKG_CONTENT_ID_HASH_SIZE PKG_HASH_SIZE#define PKG_ENTRY_KEYS_XHASHES_SIZE (PKG_MAX_ENTRY_KEYS * PKG_HASH_SIZE)#define PKG_PASSCODE_KEY_SIZE 0x100#define PKG_IMAGE_KEY_SIZE 0x100#define PKG_ENTRY_KEY_SIZE 0x100#define PKG_PLAYGO_CHUNK_HASH_TABLE_OFFSET 0x40#define PKG_PLAYGO_CHUNK_HASH_SIZE 0x4#define PKG_PLAYGO_PFS_CHUNK_SIZE 0x10000#define PKG_SHAREPARAM_FILE_VERSION_MAJOR 1#define PKG_SHAREPARAM_FILE_VERSION_MINOR 10/* XXX: it's a custom structure to store stuff at runtime */struct pkg {struct file_map* map;struct pfs* pfs;struct pfs* inner_pfs;struct pkg_header* hdr;struct pkg_table_entry* entry_table;struct pfs_file_context* image_file;struct pfs_io_callbacks io;struct pfs_io_callbacks inner_io;uint64_t pfs_image_offset;uint64_t pfs_image_size;uint32_t pfs_signed_size;uint64_t pfs_offset;uint64_t inner_pfs_offset;size_t entry_count;size_t sc_entry_count;pfs_ino image_file_ino;};struct pkg_entry_desc {struct pkg_table_entry* entry;char name[PATH_MAX];enum pkg_entry_id id;uint32_t unk1;uint32_t flags1;uint32_t flags2;uint32_t offset;uint32_t size;unsigned int key_index;int is_encrypted;};//...typedef enum cb_result (*pkg_enum_entries_cb)(void* arg, struct pkg* pkg, struct pkg_entry_desc* desc);int pkg_get_name_by_id(char* name, size_t max_size, enum pkg_entry_id id);struct pkg_table_entry* pkg_find_entry(struct pkg* pkg, enum pkg_entry_id id);size_t pkg_enum_entries(struct pkg* pkg, pkg_enum_entries_cb cb, void* arg);uint8_t* pkg_locate_entry_data(struct pkg* pkg, enum pkg_entry_id id, uint32_t* size);int pkg_get_entry_keyset(struct pkg* pkg, enum pkg_entry_id id, struct pkg_entry_keyset* keyset);static inline unsigned int pkg_table_entry_key_index(struct pkg_table_entry* entry) {assert(entry != NULL);return (BE32(entry->flags2) & 0xF000) >> 12;}static inline int pkg_table_entry_is_encrypted(struct pkg_table_entry* entry) {assert(entry != NULL);return (BE32(entry->flags1) & 0x80000000) != 0 ? 1 : 0;}
pkg.c
#include "pkg.h"//...int pkg_create_dummy_shareparam_json(struct pkg* pkg, const char* output_directory, const char* name, int* created) {char file_path[PATH_MAX], directory[PATH_MAX];struct sfo* sfo = NULL;struct sfo_entry* sfo_entry;uint8_t* sfo_data;uint32_t sfo_data_size;char app_ver_str[sizeof("00.00")];UT_string* content = NULL;int status = 0;assert(pkg != NULL);assert(output_directory != NULL);assert(name != NULL);if (created)*created = 0;sfo_data = pkg_locate_entry_data(pkg, PKG_ENTRY_ID__PARAM_SFO, &sfo_data_size);if (!sfo_data) {// No param.sfo (package file is not a game package?).goto done;}sfo = sfo_alloc();if (!sfo) {warning("Unable to allocate memory for system file object.");goto error;}if (!sfo_load_from_memory(sfo, sfo_data, sfo_data_size)) {warning("Unable to load system file object.");goto error;}sfo_entry = sfo_find_entry(sfo, "APP_VER");if (sfo_entry) {if (sfo_entry->format != SFO_FORMAT_STRING || sfo_entry->size < strlen("00.00") + 1) {warning("Invalid format of APP_VER entry in system file object.");goto error;}snprintf(app_ver_str, sizeof(app_ver_str), "%s", (const char*)sfo_entry->value);} else {snprintf(app_ver_str, sizeof(app_ver_str), "%02u.%02u", 1, 0);warning("No APP_VER entry in system file object, using default app version '%s'.", app_ver_str);}utstring_new(content);utstring_printf(content,"{\n""\t\"ps4_share_param_version\":\"%02u.%02u\",\n""\t\"game_version\":\"%s\",\n"#if 0"\t\"client\":\"\",\n"#endif"\t\"overlay_position\":{\n""\t\t\"x\":0,\n""\t\t\"y\":0\n""\t}\n""}\n",PKG_SHAREPARAM_FILE_VERSION_MAJOR, PKG_SHAREPARAM_FILE_VERSION_MINOR,app_ver_str);snprintf(file_path, sizeof(file_path), "%s%s", output_directory, name);path_get_directory(directory, sizeof(directory), file_path);if (*directory != '\0')make_directories(directory, 0755);if (!write_to_file(file_path, utstring_body(content), utstring_len(content), NULL, 0644)) {warning("Unable to write file '%s'.", file_path);goto error;}if (created)*created = 1;done:status = 1;error:if (content)utstring_free(content);if (sfo)sfo_free(sfo);return status;}//...
Conclusion
Too much time passed since releasing of first public exploit for 1.76 firmware and still there was no usable method to load custom content. No idea why it happened, because it’s not a rocket science to do (yeah, even with all these snippets of code above, it’s not hard to implement, or better saying, to port that once on each exploitable firmware, thus having habitual way to run any stuff). Of course, it will take more time to implement all functionality that the community wants to, but hey, it’s doable and at least a few people already did that.
I just hope that smart people will start to contribute to the revived scene. There are a lot of things that still needs to investigate which are mostly related to security, including SAMU, this is what we want to get in the end. Some people, including me, who participate in console hacking are not interested in piracy at all because there are more interesting tasks to do, thus I won’t take a much involvement publicly. However, I’ve reversed almost all things related to PKG/PFS/NPDRM stuff and a bunch of other PS4 functionality, so I could give hints if you need them. Anyway we’ll see how far will it go. And maybe exploits on recent firmwares will see the light in the future too, thus bringing more nicer and elegant ways to do things we’ve discussed before. (:
Good luck.
Popular
- Popular Posts
- Recent Posts
Review: Rocket City Keyboards
November 29, 2024
PS4/PS5 Remote Lua Loader (Artemis engine exploit) updates and releases
November 28, 2024
PS5: etaHEN is coming to byepervisor supported firmwares
November 11, 2024
PS4: GoldHEN 2.4b18 released, adds support for Firmwares 10.50/10.70/10.71 and more improvements
October 29, 2024
PS5: “Byepervisor” exploit files and presentation slides released
October 26, 2024
PS5: etaHEN is coming to byepervisor supported firmwares
November 11, 2024
PS4/PS5 Remote Lua Loader (Artemis engine exploit) updates and releases
November 28, 2024
Review: Rocket City Keyboards
November 29, 2024
CHECK OUT OUR BOARD GAME SISTER BLOG:
Latest entries on our Smart TV dedicated sister site
- Mi Box Oreo and Kodi, how to fix External HDD issue (NTFS/ExFat)
- Did you find this secret website in the Ghostbusters trailer?
- Yes, Ezoic actually doubles your adsense revenue… if you’re willing to make these tradeoffs
- Review: CloudnetGo CR18 – 4K Ultra HD Octa-Core Android Set top box
- The PS4 is rapidly becoming the living room device of choice
Featured
content Manager assistant
How to get your hands on a PS4 with Firmware 9.00
How to purchase US PSN Cards when you don't live in the US
PS4 Media Server