diff --git a/pkg2zip.c b/pkg2zip.c index 970b7b9..ce2c796 100644 --- a/pkg2zip.c +++ b/pkg2zip.c @@ -275,6 +275,7 @@ int main(int argc, char* argv[]) printf("pkg2zip v1.8\n"); int zipped = 1; + int cso = 5; const char* pkg_arg = NULL; const char* zrif_arg = NULL; for (int i = 1; i < argc; i++) @@ -283,6 +284,14 @@ int main(int argc, char* argv[]) { zipped = 0; } + else if (strncmp(argv[i], "-c", 2) == 0) + { + if (argv[i][2] != 0) + { + cso = atoi(argv[i] + 2); + cso = cso > 9 ? 9 : cso < 0 ? 0 : cso; + } + } else { if (pkg_arg != NULL) @@ -300,7 +309,7 @@ int main(int argc, char* argv[]) if (pkg_arg == NULL) { fprintf(stderr, "ERROR: no pkg file specified\n"); - fatal("Usage: %s [-x] file.pkg [zRIF]\n", argv[0]); + fatal("Usage: %s [-x] [-c[N]] file.pkg [zRIF]\n", argv[0]); } printf("[*] loading...\n"); @@ -706,8 +715,8 @@ int main(int argc, char* argv[]) { if (strcmp("USRDIR/CONTENT/EBOOT.PBP", name) == 0) { - snprintf(path, sizeof(path), "pspemu/ISO/%s [%.9s].iso", title, id); - unpack_psp_eboot(path, item_key, iv, pkg, enc_offset, data_offset, data_size); + snprintf(path, sizeof(path), "pspemu/ISO/%s [%.9s].%s", title, id, cso ? "cso" : "iso"); + unpack_psp_eboot(path, item_key, iv, pkg, enc_offset, data_offset, data_size, cso); continue; } else if (strcmp("USRDIR/CONTENT/PSP-KEY.EDAT", name) == 0) diff --git a/pkg2zip_crc32.c b/pkg2zip_crc32.c index 27b95be..3722c28 100644 --- a/pkg2zip_crc32.c +++ b/pkg2zip_crc32.c @@ -258,3 +258,70 @@ uint32_t crc32_done(crc32_ctx* ctx) return ~ctx->crc[0]; } + +static uint32_t crc32_gfmulv(const uint32_t* m, uint32_t v) +{ + uint32_t r = 0; + while (v) + { + if (v & 1) + { + r ^= *m; + } + v >>= 1; + m++; + } + return r; +} + +static void crc32_gfsq(const uint32_t* m, uint32_t* r) +{ + for (size_t i = 0; i < 32; i++) + { + r[i] = crc32_gfmulv(m, m[i]); + } +} + +uint32_t crc32_combine(uint32_t a, uint32_t b, uint32_t blen) +{ + uint32_t m1[32]; + uint32_t m2[32]; + + m1[0] = 0xedb88320; + for (uint32_t i = 1; i < 32; i++) + { + m1[i] = 1 << (i - 1); + } + + crc32_gfsq(m1, m2); + crc32_gfsq(m2, m1); + + for (;;) + { + crc32_gfsq(m1, m2); + if (blen & 1) + { + a = crc32_gfmulv(m2, a); + } + blen >>= 1; + + if (blen == 0) + { + break; + } + + crc32_gfsq(m2, m1); + if (blen & 1) + { + a = crc32_gfmulv(m1, a); + } + blen >>= 1; + + if (blen == 0) + { + break; + } + } + + return a ^ b; +} diff --git a/pkg2zip_crc32.h b/pkg2zip_crc32.h index 977cc0e..d8f9dea 100644 --- a/pkg2zip_crc32.h +++ b/pkg2zip_crc32.h @@ -9,3 +9,8 @@ typedef struct { void crc32_init(crc32_ctx* ctx); void crc32_update(crc32_ctx* ctx, const void* buffer, size_t size); uint32_t crc32_done(crc32_ctx* ctx); + +// returns crc32(x||y) +// where a=crc32(x) and b=crc32(y) +// crc32(x||y) = crc32(x||z) ^ crc32(y), where z=00...00 (same length as y) +uint32_t crc32_combine(uint32_t a, uint32_t b, uint32_t blen); diff --git a/pkg2zip_out.c b/pkg2zip_out.c index 8fbe0fb..e548b26 100644 --- a/pkg2zip_out.c +++ b/pkg2zip_out.c @@ -36,16 +36,17 @@ void out_add_folder(const char* path) } } -void out_begin_file(const char* name, int compress) +uint64_t out_begin_file(const char* name, int compress) { if (out_zipped) { - zip_begin_file(&out_zip, name, compress); + return zip_begin_file(&out_zip, name, compress); } else { out_file = sys_create(name); out_file_offset = 0; + return 0; } } @@ -73,3 +74,45 @@ void out_write(const void* buffer, uint32_t size) out_file_offset += size; } } + +void out_write_at(uint64_t offset, const void* buffer, uint32_t size) +{ + if (out_zipped) + { + zip_write_file_at(&out_zip, offset, buffer, size); + } + else + { + sys_write(out_file, offset, buffer, size); + } +} + +void out_set_offset(uint64_t offset) +{ + if (out_zipped) + { + zip_set_offset(&out_zip, offset); + } + else + { + out_file_offset = offset; + + } +} + +uint32_t out_zip_get_crc32(void) +{ + if (out_zipped) + { + return zip_get_crc32(&out_zip); + } + return 0; +} + +void out_zip_set_crc32(uint32_t crc) +{ + if (out_zipped) + { + zip_set_crc32(&out_zip, crc); + } +} diff --git a/pkg2zip_out.h b/pkg2zip_out.h index 5e37244..0d3156b 100644 --- a/pkg2zip_out.h +++ b/pkg2zip_out.h @@ -5,6 +5,12 @@ void out_begin(const char* name, int zipped); void out_end(void); void out_add_folder(const char* path); -void out_begin_file(const char* name, int compress); +uint64_t out_begin_file(const char* name, int compress); void out_end_file(void); void out_write(const void* buffer, uint32_t size); + +// hacky solution to be able to write cso header after the data is written +void out_write_at(uint64_t offset, const void* buffer, uint32_t size); +void out_set_offset(uint64_t offset); +uint32_t out_zip_get_crc32(void); +void out_zip_set_crc32(uint32_t crc); diff --git a/pkg2zip_psp.c b/pkg2zip_psp.c index 7ae3f58..c718c95 100644 --- a/pkg2zip_psp.c +++ b/pkg2zip_psp.c @@ -1,12 +1,15 @@ #include "pkg2zip_psp.h" #include "pkg2zip_out.h" +#include "pkg2zip_crc32.h" #include "pkg2zip_utils.h" +#include "miniz_tdef.h" #include #include -#define PSP_ISO_BLOCK_SIZE 16 -#define PSP_ISO_SECTOR_SIZE 2048 +#define ISO_SECTOR_SIZE 2048 + +#define CSO_HEADER_SIZE 24 // https://vitadevwiki.com/vita/Keys_NonVita#PSPAESKirk4.2F7 static const uint8_t kirk7_key38[] = { 0x12, 0x46, 0x8d, 0x7e, 0x1c, 0x42, 0x20, 0x9b, 0xba, 0x54, 0x26, 0x83, 0x5e, 0xb0, 0x33, 0x03 }; @@ -308,7 +311,7 @@ static void init_psp_decrypt(aes128_key* key, uint8_t* iv, int eboot, const uint } } -void unpack_psp_eboot(const char* path, const aes128_key* pkg_key, const uint8_t* pkg_iv, sys_file* pkg, uint64_t enc_offset, uint64_t item_offset, uint64_t item_size) +void unpack_psp_eboot(const char* path, const aes128_key* pkg_key, const uint8_t* pkg_iv, sys_file* pkg, uint64_t enc_offset, uint64_t item_offset, uint64_t item_size, int cso) { if (item_size < 0x28) { @@ -341,9 +344,9 @@ void unpack_psp_eboot(const char* path, const aes128_key* pkg_key, const uint8_t } uint32_t iso_block = get32le(psar_header + 0x0c); - if (iso_block != PSP_ISO_BLOCK_SIZE) + if (iso_block > 16) { - fatal("ERROR: unsupported data.psar block size %u, only %u supported!", iso_block, PSP_ISO_BLOCK_SIZE); + fatal("ERROR: unsupported data.psar block size %u, max %u supported!\b", iso_block, 16); } uint8_t mac[16]; @@ -357,16 +360,37 @@ void unpack_psp_eboot(const char* path, const aes128_key* pkg_key, const uint8_t uint32_t iso_start = get32le(psar_header + 0x54); uint32_t iso_end = get32le(psar_header + 0x64); uint32_t iso_total = iso_end - iso_start - 1; - uint32_t block_count = (iso_total + PSP_ISO_BLOCK_SIZE - 1) / PSP_ISO_BLOCK_SIZE; + uint32_t block_count = (iso_total + iso_block - 1) / iso_block; uint32_t iso_table = get32le(psar_header + 0x6c); if (iso_table + block_count * 32 > item_size) { - fatal("ERROR: offset table in data.psar file is too large!"); + fatal("ERROR: offset table in data.psar file is too large!\n"); + } + + mz_uint cso_compress_flags = 0; + uint32_t cso_index = 0; + uint32_t cso_offset = 0; + uint64_t cso_size = 0; + uint32_t* cso_block = NULL; + uint32_t initial_size = 0; + + uint64_t file_offset = out_begin_file(path, !cso); + if (cso) + { + cso_size = block_count * iso_block * ISO_SECTOR_SIZE; + cso_compress_flags = tdefl_create_comp_flags_from_zip_params(cso, -MZ_DEFAULT_WINDOW_BITS, MZ_DEFAULT_STRATEGY); + + uint32_t cso_block_count = (uint32_t)(1 + (cso_size + ISO_SECTOR_SIZE - 1) / ISO_SECTOR_SIZE); + cso_block = sys_realloc(NULL, cso_block_count * sizeof(uint32_t)); + + initial_size = CSO_HEADER_SIZE + cso_block_count * sizeof(uint32_t); + out_set_offset(file_offset + initial_size); + + cso_offset = initial_size; } - out_begin_file(path, 1); for (uint32_t i = 0; i < block_count; i++) { uint64_t table_offset = item_offset + psar_offset + iso_table + 32 * i; @@ -390,7 +414,7 @@ void unpack_psp_eboot(const char* path, const aes128_key* pkg_key, const uint8_t fatal("ERROR: iso block size/offset is to large!"); } - uint8_t data[PSP_ISO_BLOCK_SIZE * PSP_ISO_SECTOR_SIZE]; + uint8_t data[16 * ISO_SECTOR_SIZE]; uint64_t abs_offset = item_offset + psar_offset + block_offset; sys_read(pkg, enc_offset + abs_offset, data, block_size); @@ -402,22 +426,114 @@ void unpack_psp_eboot(const char* path, const aes128_key* pkg_key, const uint8_t } uint32_t out_size; - if (block_size == sizeof(data)) + if (block_size == iso_block * ISO_SECTOR_SIZE) { - out_write(data, (uint32_t)block_size); + if (cso) + { + for (size_t n = 0; n < iso_block * ISO_SECTOR_SIZE; n += ISO_SECTOR_SIZE) + { + cso_block[cso_index] = cso_offset; + + uint8_t PKG_ALIGN(16) output[ISO_SECTOR_SIZE]; + size_t insize = ISO_SECTOR_SIZE; + size_t outsize = sizeof(output); + + tdefl_compressor c; + tdefl_init(&c, cso_compress_flags); + tdefl_status st = tdefl_compress(&c, data + n, &insize, output, &outsize, TDEFL_FINISH); + if (st == TDEFL_STATUS_DONE) + { + out_write(output, (uint32_t)outsize); + cso_offset += (uint32_t)outsize; + } + else + { + cso_block[cso_index] |= 0x80000000; + out_write(data + n, ISO_SECTOR_SIZE); + cso_offset += ISO_SECTOR_SIZE; + } + cso_index++; + } + } + else + { + out_write(data, (uint32_t)block_size); + } } else { - uint8_t uncompressed[PSP_ISO_BLOCK_SIZE * PSP_ISO_SECTOR_SIZE]; + uint8_t PKG_ALIGN(16) uncompressed[16 * ISO_SECTOR_SIZE]; out_size = lzrc_decompress(uncompressed, sizeof(uncompressed), data, block_size); - if (out_size != sizeof(uncompressed)) + if (out_size != iso_block * ISO_SECTOR_SIZE) { - fatal("ERROR: internal error - lzrc decompression failed! pkg may be corrupted?"); + fatal("ERROR: internal error - lzrc decompression failed! pkg may be corrupted?\n"); + } + if (cso) + { + for (size_t n = 0; n < iso_block * ISO_SECTOR_SIZE; n += ISO_SECTOR_SIZE) + { + cso_block[cso_index] = cso_offset; + + uint8_t output[ISO_SECTOR_SIZE]; + size_t insize = ISO_SECTOR_SIZE; + size_t outsize = sizeof(output); + + tdefl_compressor c; + tdefl_init(&c, cso_compress_flags); + tdefl_status st = tdefl_compress(&c, uncompressed + n, &insize, output, &outsize, TDEFL_FINISH); + if (st == TDEFL_STATUS_DONE) + { + out_write(output, (uint32_t)outsize); + cso_offset += (uint32_t)outsize; + } + else + { + cso_block[cso_index] |= 0x80000000; + out_write(uncompressed + n, ISO_SECTOR_SIZE); + cso_offset += ISO_SECTOR_SIZE; + } + cso_index++; + } + } + else + { + out_write(uncompressed, (uint32_t)out_size); } - out_write(uncompressed, (uint32_t)out_size); } } + if (cso) + { + cso_block[cso_index++] = cso_offset; + + uint8_t cso_header[CSO_HEADER_SIZE] = { 0x43, 0x49, 0x53, 0x4f }; + // header size + set32le(cso_header + 4, sizeof(cso_header)); + // original size + set64le(cso_header + 8, cso_size); + // block size + set32le(cso_header + 16, ISO_SECTOR_SIZE); + // version + cso_header[20] = 1; + + out_write_at(file_offset, cso_header, sizeof(cso_header)); + out_write_at(file_offset + sizeof(cso_header), cso_block, cso_index * sizeof(uint32_t)); + + crc32_ctx cheader; + crc32_init(&cheader); + crc32_update(&cheader, cso_header, sizeof(cso_header)); + crc32_update(&cheader, cso_block, cso_index * sizeof(uint32_t)); + + uint32_t header_crc32 = crc32_done(&cheader); + uint32_t data_crc32 = out_zip_get_crc32(); + uint32_t data_len = (uint32_t)(cso_offset - initial_size); + + uint32_t crc32 = crc32_combine(header_crc32, data_crc32, data_len); + out_zip_set_crc32(crc32); + + sys_realloc(cso_block, 0); + } + out_end_file(); } diff --git a/pkg2zip_psp.h b/pkg2zip_psp.h index d6d14a1..e2183be 100644 --- a/pkg2zip_psp.h +++ b/pkg2zip_psp.h @@ -1,5 +1,5 @@ #include "pkg2zip_aes.h" #include "pkg2zip_sys.h" -void unpack_psp_eboot(const char* path, const aes128_key* pkg_key, const uint8_t* pkg_iv, sys_file* pkg, uint64_t enc_offset, uint64_t item_offset, uint64_t item_size); +void unpack_psp_eboot(const char* path, const aes128_key* pkg_key, const uint8_t* pkg_iv, sys_file* pkg, uint64_t enc_offset, uint64_t item_offset, uint64_t item_size, int cso); void unpack_psp_key(const char* path, const aes128_key* pkg_key, const uint8_t* pkg_iv, sys_file* pkg, uint64_t enc_offset, uint64_t item_offset, uint64_t item_size); diff --git a/pkg2zip_zip.c b/pkg2zip_zip.c index b7c8d7c..d5f9edb 100644 --- a/pkg2zip_zip.c +++ b/pkg2zip_zip.c @@ -108,7 +108,7 @@ void zip_add_folder(zip* z, const char* name) z->total += 1; } -void zip_begin_file(zip* z, const char* name, int compress) +uint64_t zip_begin_file(zip* z, const char* name, int compress) { size_t name_length = strlen(name); if (name_length > ZIP_MAX_FILENAME) @@ -124,6 +124,7 @@ void zip_begin_file(zip* z, const char* name, int compress) z->current = f; crc32_init(&z->crc32); + z->crc32_set = 0; uint8_t header[ZIP_LOCAL_HEADER_SIZE] = { 0x50, 0x4b, 0x03, 0x04 }; // version needed to extract @@ -150,6 +151,8 @@ void zip_begin_file(zip* z, const char* name, int compress) int flags = tdefl_create_comp_flags_from_zip_params(MZ_BEST_SPEED, -MZ_DEFAULT_WINDOW_BITS, MZ_DEFAULT_STRATEGY); tdefl_init(&z->tdefl, flags); } + + return z->total - f->offset; } void zip_write_file(zip* z, const void* data, uint32_t size) @@ -211,7 +214,10 @@ void zip_end_file(zip* z) } } - z->current->crc32 = crc32_done(&z->crc32); + if (!z->crc32_set) + { + z->current->crc32 = crc32_done(&z->crc32); + } if (z->current->size != 0) { @@ -400,3 +406,31 @@ void zip_close(zip* z) sys_realloc(z->files, 0); } + +void zip_write_file_at(zip* z, uint64_t offset, const void* data, uint32_t size) +{ + if (z->current->compress) + { + fatal("ERROR: cannot write at specific offset for compressed files\n"); + } + + sys_write(z->file, z->current->offset + offset, data, size); + z->current->size += size; + z->current->compressed += size; +} + +void zip_set_offset(zip* z, uint64_t offset) +{ + z->total = z->current->offset + offset; +} + +void zip_set_crc32(zip* z, uint32_t crc) +{ + z->current->crc32 = crc; + z->crc32_set = 1; +} + +uint32_t zip_get_crc32(zip* z) +{ + return crc32_done(&z->crc32); +} diff --git a/pkg2zip_zip.h b/pkg2zip_zip.h index 5c189e8..3e6e04e 100644 --- a/pkg2zip_zip.h +++ b/pkg2zip_zip.h @@ -20,6 +20,7 @@ typedef struct { uint16_t date; tdefl_compressor tdefl; crc32_ctx crc32; + int crc32_set; uint32_t allocated; // bytes zip_file* files; zip_file* current; @@ -27,7 +28,13 @@ typedef struct { void zip_create(zip* z, const char* name); void zip_add_folder(zip* z, const char* name); -void zip_begin_file(zip* z, const char* name, int compress); +uint64_t zip_begin_file(zip* z, const char* name, int compress); void zip_write_file(zip* z, const void* data, uint32_t size); void zip_end_file(zip* z); void zip_close(zip* z); + +// hacky solution to be able to write cso header after the data is written +void zip_write_file_at(zip* z, uint64_t offset, const void* data, uint32_t size); +void zip_set_offset(zip* z, uint64_t offset); +void zip_set_crc32(zip* z, uint32_t crc); +uint32_t zip_get_crc32(zip* z);