diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..057fb25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +pkg2zip +pkg2zip.exe +*.d +*.o +*.obj +*.pdb + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d388a4 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# pkg2zip + +Utility that decrypts PlayStation Vita pkg file and creates zip package. + +Optionally embedds [NoNpDrm](https://github.com/TheOfficialFloW/NoNpDrm) license into work.bin file. You must provide license key. + +# Features + +* **portable**, written in cross-platform C code, runs on Windows, GNU/Linux, macOS (system dependent functionality is isolated in sys.c file). +* **small**, uses zero dynamic memory allocations and has no external library dependencies. +* **fast**, uses AESNI hardware accelerated AES decryption if supported by CPU (requires [AESNI](https://en.wikipedia.org/wiki/AES_instruction_set) and [SSSE3](https://en.wikipedia.org/wiki/SSSE3) instructions). +* **simple**, creates zip package with same folder structure that Vita expects (just drag & drop all file from zip archive to ux0:). Zip file is created directly from pkg without any intermediate temporary files. + +Limitations: +* currently works only for main application pkg files, no DLC. + +# Usage + +Execute `pkg2zip package.pkg` to create `title [id] [region].zip` file. Title, ID and region is automatically detected from pkg file. + +If you have license key (32 hex characters) you can execute `pkg2zip package.pkg hexkey` to embed key into work.bin file. + +# Download + +Get latest Windows binaries [here](https://github.com/mmozeiko/pkg2zip/releases). + +# Building + +Execute `make` if you are on GNU/Linux or macOS. + +On Windows you can build either with MinGW (get [MinGW-w64](http://www.msys2.org/)) or [Visual Studio 2017 Community Edition](https://www.visualstudio.com/vs/community/). +* for MinGW make sure you have make installed, and then execute `mingw32-make` +* for Visual Studio run `build.cmd` + +# Alternatives + +* https://github.com/RikuKH3/unpkg_vita +* https://github.com/St4rk/PkgDecrypt +* https://github.com/TheRadziu/PkgDecrypt + +# License + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..9da98c9 --- /dev/null +++ b/build.cmd @@ -0,0 +1,12 @@ +@echo off + +call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" -arch=amd64 -host_arch=amd64 + +set CL=/nologo /errorReport:none /Gm- /GF /GS- /MT /W4 +set LINK=/errorReport:none /INCREMENTAL:NO + +set CL=%CL% /Ox +rem set CL=%CL% /Od /Zi +rem set LINK=%LINK% /DEBUG + +cl.exe pkg2zip*.c /Fepkg2zip.exe diff --git a/makefile b/makefile new file mode 100644 index 0000000..a7e265a --- /dev/null +++ b/makefile @@ -0,0 +1,35 @@ +ifeq ($(OS),Windows_NT) + RM := del /q + EXE := .exe +else + EXE := +endif + +BIN=pkg2zip${EXE} +SRC=${wildcard pkg2zip*.c} +OBJ=${SRC:.c=.o} +DEP=${SRC:.c=.d} + +CFLAGS=-pipe -fvisibility=hidden -Wall -Wextra -DNDEBUG -O2 +LDFLAGS=-s + +.PHONY: all clean + +all: ${BIN} + +clean: + @${RM} ${BIN} ${OBJ} ${DEP} + +${BIN}: ${OBJ} + @echo [L] $@ + @${CC} ${LDFLAGS} -o $@ $^ + +%_x86.o: %_x86.c + @echo [C] $< + @${CC} ${CFLAGS} -maes -mssse3 -MMD -c -o $@ $< + +%.o: %.c + @echo [C] $< + @${CC} ${CFLAGS} -MMD -c -o $@ $< + +-include ${DEP} diff --git a/pkg2zip.c b/pkg2zip.c new file mode 100644 index 0000000..f36242e --- /dev/null +++ b/pkg2zip.c @@ -0,0 +1,351 @@ +#include "pkg2zip_aes.h" +#include "pkg2zip_zip.h" +#include "pkg2zip_utils.h" + +#include +#include +#include +#include +#include + +static const uint8_t pkg_psp_key[] = { + 0x07, 0xf2, 0xc6, 0x82, 0x90, 0xb5, 0x0d, 0x2c, 0x33, 0x81, 0x8d, 0x70, 0x9b, 0x60, 0xe6, 0x2b, +}; + +static const uint8_t pkg_vita_2[] = { + 0xe3, 0x1a, 0x70, 0xc9, 0xce, 0x1d, 0xd7, 0x2b, 0xf3, 0xc0, 0x62, 0x29, 0x63, 0xf2, 0xec, 0xcb, +}; + +static const uint8_t pkg_vita_3[] = { + 0x42, 0x3a, 0xca, 0x3a, 0x2b, 0xd5, 0x64, 0x9f, 0x96, 0x86, 0xab, 0xad, 0x6f, 0xd8, 0x80, 0x1f, +}; + +static const uint8_t pkg_vita_4[] = { + 0xaf, 0x07, 0xfd, 0x59, 0x65, 0x25, 0x27, 0xba, 0xf1, 0x33, 0x89, 0x66, 0x8b, 0x17, 0xd9, 0xea, +}; + +static const uint8_t rif_header[] = { + 0, 1, 0, 1, 0, 1, 0, 2, 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, +}; + +static void get_title(const uint8_t* data, size_t data_size, char* title) +{ + if (data_size < 16) + { + fatal("ERROR: wrong param.sfo size\n"); + } + + uint32_t magic = get32le(data); + if (magic != 0x46535000) + { + fatal("ERROR: incorrect param.sfo signature\n"); + } + + // https://github.com/TheOfficialFloW/VitaShell/blob/1.74/sfo.h#L29 + uint32_t keys = get32le(data + 8); + uint32_t values = get32le(data + 12); + size_t count = get32le(data + 16); + + int index = -1; + + for (size_t i=0; i data_size) + { + fatal("ERROR: truncated param.sfo size\n"); + } + + char* key = (char*)data + keys + get16le(data + i*16 + 20); + if (strcmp(key, "TITLE") == 0) + { + if (index < 0) + { + index = (int)i; + } + } + else if (strcmp(key, "STITLE") == 0) + { + index = (int)i; + break; + } + } + + if (index >= 0) + { + const char* value = (char*)data + values + get32le(data + index*16 + 20 + 12); + size_t i; + size_t max = 255; + for (i=0; i= 32 && *value < 127 && strchr("<>\"/\\|?*", *value) == NULL) + { + if (*value == ':') + { + *title++ = ' '; + *title++ = '-'; + max--; + } + else + { + *title++ = *value; + } + } + else if (*value == 10) + { + *title++ = ' '; + } + } + *title = 0; + } +} + +static const char* get_region(const char* id) +{ + if (memcmp(id, "PCSE", 4) == 0 || memcmp(id, "PCSA", 4) == 0) + { + return "USA"; + } + else if (memcmp(id, "PCSF", 4) == 0 || memcmp(id, "PCSB", 4) == 0) + { + return "EUR"; + } + else if (memcmp(id, "PCSC", 4) == 0 || memcmp(id, "VCJS", 4) == 0 || + memcmp(id, "PCSG", 4) == 0 || memcmp(id, "VLJS", 4) == 0 || + memcmp(id, "VLJM", 4) == 0) + { + return "JPN"; + } + else if (memcmp(id, "VCAS", 4) == 0 || memcmp(id, "PCSH", 4) == 0 || + memcmp(id, "VLAS", 4) == 0) + { + return "ASA"; + } + else + { + return "unknown region"; + } +} + +int main(int argc, char* argv[]) +{ + printf("pkg2zip v1.0\n"); + if (argc < 2 || argc > 3) + { + fatal("Usage: %s file.pkg [NoNpDrmKey]\n", argv[0]); + } + + printf("[*] loading...\n"); + + uint64_t pkg_size; + sys_file pkg = sys_open(argv[1], &pkg_size); + + uint8_t pkg_header[256]; + sys_read(pkg, 0, pkg_header, sizeof(pkg_header)); + + if (get32be(pkg_header) != 0x7f504b47) + { + fatal("ERROR: pkg file is corrupted\n"); + } + + uint32_t item_count = get32be(pkg_header + 20); + uint64_t enc_offset = get64be(pkg_header + 32); + + if (item_count > ZIP_MAX_FILES) + { + fatal("ERROR: pkg has too many files"); + } + + if (pkg_size < enc_offset + item_count * 32) + { + fatal("ERROR: pkg file is truncated\n"); + } + + const char* id = (char*)pkg_header + 0x37; + const uint8_t* iv = pkg_header + 0x70; + int key_type = pkg_header[0xe7] & 7; + + uint8_t main_key[16]; + + if (key_type == 1) + { + memcpy(main_key, pkg_psp_key, sizeof(main_key)); + } + else if (key_type == 2) + { + aes128_key key; + aes128_init(&key, pkg_vita_2); + aes128_ecb_encrypt(&key, iv, main_key); + } + else if (key_type == 3) + { + aes128_key key; + aes128_init(&key, pkg_vita_3); + aes128_ecb_encrypt(&key, iv, main_key); + } + else if (key_type == 4) + { + aes128_key key; + aes128_init(&key, pkg_vita_4); + aes128_ecb_encrypt(&key, iv, main_key); + } + + uint8_t items[32 * ZIP_MAX_FILES]; + uint32_t items_size = 32 * item_count; + + sys_read(pkg, enc_offset, items, items_size); + + uint8_t temp_flag = 0; + size_t head_size = enc_offset; + char title[256] = "unknown title"; + + char path[1024]; + snprintf(path, sizeof(path), ".~%.*s.zip", 9, id); + + printf("[*] creating temporary '%s' archive\n", path); + + zip z; + zip_create(&z, path); + + snprintf(path, sizeof(path), "app/"); + zip_add_folder(&z, path); + + snprintf(path, sizeof(path), "app/%.9s/", id); + zip_add_folder(&z, path); + + printf("[*] decrypting...\n"); + aes128_key key; + aes128_init(&key, main_key); + aes128_ctr_xor(&key, iv, 0, items, items_size); + + for (uint32_t item_index=0; item_index= 14 && flags <= 17) || + flags == 19 || flags == 21 || flags == 22) + { + if (memcmp("sce_pfs/pflist", name, name_size) != 0) + { + snprintf(path, sizeof(path), "app/%.9s/%s", id, name); + + uint64_t offset = data_offset; + + zip_begin_file(&z, path); + while (data_size != 0) + { + uint8_t buffer[1 << 16]; + uint32_t size = (uint32_t)min64(data_size, sizeof(buffer)); + sys_read(pkg, enc_offset + offset, buffer, size); + aes128_ctr_xor(&key, iv, offset / 16, buffer, size); + + // process data at beginning of file + if (offset == data_offset) + { + if (memcmp("sce_sys/package/temp.bin", name, name_size) == 0) + { + // https://github.com/TheOfficialFloW/NoNpDrm/blob/v1.1/src/main.c#L116 + uint32_t sku_flag = get32be(buffer + 252); + if (sku_flag == 1 || sku_flag == 3) + { + temp_flag = 3; + } + } + else if (memcmp("sce_sys/param.sfo", name, name_size) == 0) + { + get_title(buffer, size, title); + } + } + zip_write_file(&z, buffer, size); + offset += size; + data_size -= size; + } + + zip_end_file(&z); + } + } + } + + printf("[*] creating head.bin\n"); + snprintf(path, sizeof(path), "app/%.9s/sce_sys/package/head.bin", id); + + zip_begin_file(&z, path); + while (head_size != 0) + { + uint8_t buffer[1 << 16]; + uint32_t size = (uint32_t)min64(head_size, sizeof(buffer)); + sys_read(pkg, 0, buffer, size); + zip_write_file(&z, buffer, size); + head_size -= size; + } + zip_end_file(&z); + + printf("[*] creating tail.bin\n"); + snprintf(path, sizeof(path), "app/%.9s/sce_sys/package/tail.bin", id); + + uint8_t tail[480]; + zip_begin_file(&z, path); + sys_read(pkg, pkg_size - sizeof(tail), tail, sizeof(tail)); + zip_write_file(&z, tail, sizeof(tail)); + zip_end_file(&z); + + printf("[*] creating work.bin\n"); + snprintf(path, sizeof(path), "app/%.9s/sce_sys/package/work.bin", id); + + // https://github.com/TheOfficialFloW/NoNpDrm/blob/v1.1/src/main.c#L42 + uint8_t work[512] = { 0 }; + memcpy(work, rif_header, sizeof(rif_header)); + memcpy(work + 0x10, pkg_header + 0x30, 0x30); + if (argc == 3) + { + printf("[*] embedding '%s' key\n", argv[2]); + get_hex_bytes16(argv[2], work + 0x50); + } + work[255] = temp_flag; + zip_begin_file(&z, path); + zip_write_file(&z, work, sizeof(work)); + zip_end_file(&z); + + zip_close(&z); + + snprintf(path, sizeof(path), ".~%.*s.zip", 9, id); + + char target[1024]; + snprintf(target, sizeof(target), "%s [%.9s] [%s].zip", title, id, get_region(id)); + + printf("[*] renaming to '%s'\n", target); + sys_rename(path, target); + + printf("[*] done!\n"); +} diff --git a/pkg2zip_aes.c b/pkg2zip_aes.c new file mode 100644 index 0000000..e3fabf1 --- /dev/null +++ b/pkg2zip_aes.c @@ -0,0 +1,250 @@ +#include "pkg2zip_aes.h" +#include "pkg2zip_utils.h" + +#if defined(_MSC_VER) +#define PLATFORM_SUPPORTS_AESNI 1 + +#include +static void get_cpuid(uint32_t level, uint32_t* arr) +{ + __cpuidex((int*)arr, level, 0); +} + +#elif defined(__x86_64__) || defined(__i386__) +#define PLATFORM_SUPPORTS_AESNI 1 + +#include +static void get_cpuid(uint32_t level, uint32_t* arr) +{ + __cpuid_count(level, 0, arr[0], arr[1], arr[2], arr[3]); +} + +#else +#define PLATFORM_SUPPORTS_AESNI 0 +#endif + +#if PLATFORM_SUPPORTS_AESNI +static int aes128_supported_x86() +{ + static int init = 0; + static int supported; + if (!init) + { + init = 1; + + uint32_t a[4]; + get_cpuid(0, a); + + if (a[0] >= 1) + { + get_cpuid(1, a); + supported = ((a[2] & (1 << 9)) && (a[2] & (1 << 25))); + } + } + return supported; +} + +void aes128_init_x86(aes128_key* context, const uint8_t* key); +void aes128_ecb_encrypt_x86(const aes128_key* context, const uint8_t* input, uint8_t* output); +void aes128_ctr_xor_x86(const aes128_key* context, const uint8_t* iv, uint8_t* buffer, size_t size); +#endif + +static const uint8_t rcon[] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, +}; + +static const uint8_t Te[256] = { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, +}; + +static const uint32_t TE[256] = { + 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, + 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, + 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, + 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, + 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, + 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, + 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, + 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, + 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, + 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, + 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, + 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, + 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, + 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, + 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, + 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, + 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, + 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, + 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, + 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, + 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, + 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, + 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, + 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, + 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, + 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, + 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, + 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, + 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, + 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, + 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, + 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a, +}; + +static uint8_t byte32(uint32_t x, int n) +{ + return (uint8_t)(x >> (8 * n)); +} + +static uint32_t setup_mix(uint32_t temp) +{ + return (Te[byte32(temp, 2)] << 24) ^ (Te[byte32(temp, 1)] << 16) ^ (Te[byte32(temp, 0)] << 8) ^ Te[byte32(temp, 3)]; +} + +static uint32_t ror32(uint32_t x, int n) +{ + return (x >> n) | (x << (32 - n)); +} + +void aes128_init(aes128_key* context, const uint8_t* key) +{ +#if PLATFORM_SUPPORTS_AESNI + if (aes128_supported_x86()) + { + aes128_init_x86(context, key); + return; + } +#endif + + uint32_t* rk = context->key; + + rk[0] = get32be(key + 0); + rk[1] = get32be(key + 4); + rk[2] = get32be(key + 8); + rk[3] = get32be(key + 12); + + for (size_t i=0; i<10; i++) + { + uint32_t temp = rk[3]; + rk[4] = rk[0] ^ setup_mix(temp) ^ (rcon[i] << 24); + rk[5] = rk[1] ^ rk[4]; + rk[6] = rk[2] ^ rk[5]; + rk[7] = rk[3] ^ rk[6]; + rk += 4; + } +} + +static void aes128_encrypt(const aes128_key* context, const uint8_t* input, uint8_t* output) +{ + uint32_t t0, t1, t2, t3; + const uint32_t* key = context->key; + + uint32_t s0 = get32be(input + 0) ^ *key++; + uint32_t s1 = get32be(input + 4) ^ *key++; + uint32_t s2 = get32be(input + 8) ^ *key++; + uint32_t s3 = get32be(input + 12) ^ *key++; + + for (size_t i = 0; i<4; i++) + { + t0 = TE[byte32(s0, 3)] ^ ror32(TE[byte32(s1, 2)], 8) ^ ror32(TE[byte32(s2, 1)], 16) ^ ror32(TE[byte32(s3, 0)], 24) ^ *key++; + t1 = TE[byte32(s1, 3)] ^ ror32(TE[byte32(s2, 2)], 8) ^ ror32(TE[byte32(s3, 1)], 16) ^ ror32(TE[byte32(s0, 0)], 24) ^ *key++; + t2 = TE[byte32(s2, 3)] ^ ror32(TE[byte32(s3, 2)], 8) ^ ror32(TE[byte32(s0, 1)], 16) ^ ror32(TE[byte32(s1, 0)], 24) ^ *key++; + t3 = TE[byte32(s3, 3)] ^ ror32(TE[byte32(s0, 2)], 8) ^ ror32(TE[byte32(s1, 1)], 16) ^ ror32(TE[byte32(s2, 0)], 24) ^ *key++; + s0 = TE[byte32(t0, 3)] ^ ror32(TE[byte32(t1, 2)], 8) ^ ror32(TE[byte32(t2, 1)], 16) ^ ror32(TE[byte32(t3, 0)], 24) ^ *key++; + s1 = TE[byte32(t1, 3)] ^ ror32(TE[byte32(t2, 2)], 8) ^ ror32(TE[byte32(t3, 1)], 16) ^ ror32(TE[byte32(t0, 0)], 24) ^ *key++; + s2 = TE[byte32(t2, 3)] ^ ror32(TE[byte32(t3, 2)], 8) ^ ror32(TE[byte32(t0, 1)], 16) ^ ror32(TE[byte32(t1, 0)], 24) ^ *key++; + s3 = TE[byte32(t3, 3)] ^ ror32(TE[byte32(t0, 2)], 8) ^ ror32(TE[byte32(t1, 1)], 16) ^ ror32(TE[byte32(t2, 0)], 24) ^ *key++; + } + + t0 = TE[byte32(s0, 3)] ^ ror32(TE[byte32(s1, 2)], 8) ^ ror32(TE[byte32(s2, 1)], 16) ^ ror32(TE[byte32(s3, 0)], 24) ^ *key++; + t1 = TE[byte32(s1, 3)] ^ ror32(TE[byte32(s2, 2)], 8) ^ ror32(TE[byte32(s3, 1)], 16) ^ ror32(TE[byte32(s0, 0)], 24) ^ *key++; + t2 = TE[byte32(s2, 3)] ^ ror32(TE[byte32(s3, 2)], 8) ^ ror32(TE[byte32(s0, 1)], 16) ^ ror32(TE[byte32(s1, 0)], 24) ^ *key++; + t3 = TE[byte32(s3, 3)] ^ ror32(TE[byte32(s0, 2)], 8) ^ ror32(TE[byte32(s1, 1)], 16) ^ ror32(TE[byte32(s2, 0)], 24) ^ *key++; + + s0 = (Te[byte32(t0, 3)] << 24) ^ (Te[byte32(t1, 2)] << 16) ^ (Te[byte32(t2, 1)] << 8) ^ (Te[byte32(t3, 0)]) ^ *key++; + s1 = (Te[byte32(t1, 3)] << 24) ^ (Te[byte32(t2, 2)] << 16) ^ (Te[byte32(t3, 1)] << 8) ^ (Te[byte32(t0, 0)]) ^ *key++; + s2 = (Te[byte32(t2, 3)] << 24) ^ (Te[byte32(t3, 2)] << 16) ^ (Te[byte32(t0, 1)] << 8) ^ (Te[byte32(t1, 0)]) ^ *key++; + s3 = (Te[byte32(t3, 3)] << 24) ^ (Te[byte32(t0, 2)] << 16) ^ (Te[byte32(t1, 1)] << 8) ^ (Te[byte32(t2, 0)]) ^ *key++; + + set32be(output + 0, s0); + set32be(output + 4, s1); + set32be(output + 8, s2); + set32be(output + 12, s3); +} + +void aes128_ecb_encrypt(const aes128_key* context, const uint8_t* input, uint8_t* output) +{ +#if PLATFORM_SUPPORTS_AESNI + if (aes128_supported_x86()) + { + aes128_ecb_encrypt_x86(context, input, output); + return; + } +#endif + aes128_encrypt(context, input, output); +} + +static void ctr_add(uint8_t* counter, uint64_t n) +{ + for (int i=15; i>=0; i--) + { + n = n + counter[i]; + counter[i] = (uint8_t)n; + n >>= 8; + } +} + +void aes128_ctr_xor(const aes128_key* context, const uint8_t* iv, uint64_t block, uint8_t* buffer, size_t size) +{ + uint8_t tmp[16]; + uint8_t counter[16]; + for (uint32_t i=0; i<16; i++) + { + counter[i] = iv[i]; + } + ctr_add(counter, block); + +#if PLATFORM_SUPPORTS_AESNI + if (aes128_supported_x86()) + { + aes128_ctr_xor_x86(context, counter, buffer, size); + return; + } +#endif + + while (size >= 16) + { + aes128_encrypt(context, counter, tmp); + for (uint32_t i=0; i<16; i++) + { + *buffer++ ^= tmp[i]; + } + ctr_add(counter, 1); + size -= 16; + } + + if (size != 0) + { + aes128_encrypt(context, counter, tmp); + for (size_t i=0; i +#include + +#if defined(_MSC_VER) +# define MSVC_ALIGN(x) __declspec(align(x)) +# define GCC_ALIGN(x) +#else +# define MSVC_ALIGN(x) +# define GCC_ALIGN(x) __attribute__((aligned(x))) +#endif + +typedef struct { + MSVC_ALIGN(16) uint32_t key[44] GCC_ALIGN(16); +} aes128_key; + +void aes128_init(aes128_key* context, const uint8_t* key); +void aes128_ecb_encrypt(const aes128_key* context, const uint8_t* input, uint8_t* output); +void aes128_ctr_xor(const aes128_key* context, const uint8_t* iv, uint64_t block, uint8_t* buffer, size_t size); diff --git a/pkg2zip_aes_x86.c b/pkg2zip_aes_x86.c new file mode 100644 index 0000000..e32b7fb --- /dev/null +++ b/pkg2zip_aes_x86.c @@ -0,0 +1,99 @@ +#include "pkg2zip_aes.h" + +#include +#include // AESNI +#include // SSSE3 + +#define AES128_INIT(ctx, x, rcon) \ +{ \ + __m128i a, b; \ + _mm_store_si128(ctx, x); \ + a = _mm_aeskeygenassist_si128(x, rcon); \ + a = _mm_shuffle_epi32(a, 0xff); \ + b = _mm_slli_si128(x, 4); \ + x = _mm_xor_si128(x, b); \ + b = _mm_slli_si128(b, 4); \ + x = _mm_xor_si128(x, b); \ + b = _mm_slli_si128(b, 4); \ + x = _mm_xor_si128(x, b); \ + x = _mm_xor_si128(x, a); \ +} + +void aes128_init_x86(aes128_key* context, const uint8_t* key) +{ + __m128i* ctx = (__m128i*)context->key; + + __m128i x = _mm_loadu_si128((const __m128i*)key); + AES128_INIT(ctx + 0, x, 0x01); + AES128_INIT(ctx + 1, x, 0x02); + AES128_INIT(ctx + 2, x, 0x04); + AES128_INIT(ctx + 3, x, 0x08); + AES128_INIT(ctx + 4, x, 0x10); + AES128_INIT(ctx + 5, x, 0x20); + AES128_INIT(ctx + 6, x, 0x40); + AES128_INIT(ctx + 7, x, 0x80); + AES128_INIT(ctx + 8, x, 0x1b); + AES128_INIT(ctx + 9, x, 0x36); + _mm_storeu_si128(ctx + 10, x); +} + +static __m128i aes128_encrypt_x86(__m128i input, const __m128i* key) +{ + __m128i tmp = _mm_xor_si128(input, key[0]); + tmp = _mm_aesenc_si128(tmp, key[1]); + tmp = _mm_aesenc_si128(tmp, key[2]); + tmp = _mm_aesenc_si128(tmp, key[3]); + tmp = _mm_aesenc_si128(tmp, key[4]); + tmp = _mm_aesenc_si128(tmp, key[5]); + tmp = _mm_aesenc_si128(tmp, key[6]); + tmp = _mm_aesenc_si128(tmp, key[7]); + tmp = _mm_aesenc_si128(tmp, key[8]); + tmp = _mm_aesenc_si128(tmp, key[9]); + return _mm_aesenclast_si128(tmp, key[10]); +} + +void aes128_ecb_encrypt_x86(const aes128_key* context, const uint8_t* input, uint8_t* output) +{ + const __m128i* key = (__m128i*)context->key; + __m128i tmp = aes128_encrypt_x86(_mm_loadu_si128((const __m128i*)input), key); + _mm_storeu_si128((__m128i*)output, tmp); +} + +static __m128i ctr_increment(__m128i counter) +{ + __m128i swap = _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + __m128i tmp = _mm_shuffle_epi8(counter, swap); + tmp = _mm_add_epi64(tmp, _mm_set_epi32(0, 0, 0, 1)); + return _mm_shuffle_epi8(tmp, swap); +} + +void aes128_ctr_xor_x86(const aes128_key* context, const uint8_t* iv, uint8_t* buffer, size_t size) +{ + const __m128i* key = (__m128i*)context->key; + __m128i counter = _mm_loadu_si128((const __m128i*)iv); + + while (size >= 16) + { + __m128i block = aes128_encrypt_x86(counter, key); + __m128i tmp = _mm_xor_si128(_mm_loadu_si128((const __m128i*)buffer), block); + _mm_storeu_si128((__m128i*)buffer, tmp); + + counter = ctr_increment(counter); + + buffer += 16; + size -= 16; + } + + if (size != 0) + { + uint8_t full[16]; + memcpy(full, buffer, size); + memset(full + size, 0, 16 - size); + + __m128i block = aes128_encrypt_x86(counter, key); + __m128i tmp = _mm_xor_si128(_mm_loadu_si128((const __m128i*)full), block); + _mm_storeu_si128((__m128i*)full, tmp); + + memcpy(buffer, full, size); + } +} diff --git a/pkg2zip_sys.c b/pkg2zip_sys.c new file mode 100644 index 0000000..46a16bf --- /dev/null +++ b/pkg2zip_sys.c @@ -0,0 +1,162 @@ +#include "pkg2zip_sys.h" +#include "pkg2zip_utils.h" + +#if defined(_WIN32) + +#define WIN32_LEAN_AND_MEAN +#include + +sys_file sys_open(const char* fname, uint64_t* size) +{ + WCHAR path[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, fname, -1, path, MAX_PATH); + + HANDLE handle = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (handle == INVALID_HANDLE_VALUE) + { + fatal("ERROR: cannot open '%s' file\n", fname); + } + + LARGE_INTEGER sz; + if (!GetFileSizeEx(handle, &sz)) + { + fatal("ERROR: cannot get size of '%s' file\n", fname); + } + *size = sz.QuadPart; + + return handle; +} + +sys_file sys_create(const char* fname) +{ + WCHAR path[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, fname, -1, path, MAX_PATH); + + HANDLE handle = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL); + if (handle == INVALID_HANDLE_VALUE) + { + fatal("ERROR: cannot create '%s' file\n", fname); + } + + return handle; +} + +void sys_close(sys_file file) +{ + if (!CloseHandle(file)) + { + fatal("ERROR: failed to close file\n"); + } +} + +void sys_read(sys_file file, uint64_t offset, void* buffer, uint32_t size) +{ + DWORD read; + OVERLAPPED ov; + ov.hEvent = NULL; + ov.Offset = (uint32_t)offset; + ov.OffsetHigh = (uint32_t)(offset >> 32); + if (!ReadFile(file, buffer, size, &read, &ov) || read != size) + { + fatal("ERROR: failed to read %u bytes from file\n", size); + } +} + +void sys_write(sys_file file, uint64_t offset, const void* buffer, uint32_t size) +{ + DWORD written; + OVERLAPPED ov; + ov.hEvent = NULL; + ov.Offset = (uint32_t)offset; + ov.OffsetHigh = (uint32_t)(offset >> 32); + if (!WriteFile(file, buffer, size, &written, &ov) || written != size) + { + fatal("ERROR: failed to write %u bytes to file\n", size); + } +} + +void sys_rename(const char* src, const char* dst) +{ + WCHAR wsrc[MAX_PATH]; + WCHAR wdst[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, src, -1, wsrc, MAX_PATH); + MultiByteToWideChar(CP_UTF8, 0, dst, -1, wdst, MAX_PATH); + + if (!MoveFileExW(wsrc, wdst, MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) + { + fatal("ERROR: failed to rename '%s' to '%s'\n", src, dst); + } +} + +#else + +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include + +sys_file sys_open(const char* fname, uint64_t* size) +{ + int fd = open(fname, O_RDONLY); + if (fd < 0) + { + fatal("ERROR: cannot open '%s' file\n", fname); + } + + struct stat st; + if (fstat(fd, &st) != 0) + { + fatal("ERROR: cannot get size of '%s' file\n", fname); + } + *size = st.st_size; + + return (void*)(intptr_t)fd; +} + +sys_file sys_create(const char* fname) +{ + int fd = open(fname, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) + { + fatal("ERROR: cannot create '%s' file\n", fname); + } + + return (void*)(intptr_t)fd; +} + +void sys_close(sys_file file) +{ + if (close((int)(intptr_t)file) != 0) + { + fatal("ERROR: failed to close file\n"); + } +} + +void sys_read(sys_file file, uint64_t offset, void* buffer, uint32_t size) +{ + ssize_t read = pread((int)(intptr_t)file, buffer, size, offset); + if (read != size) + { + fatal("ERROR: failed to read %u bytes from file\n", size); + } +} + +void sys_write(sys_file file, uint64_t offset, const void* buffer, uint32_t size) +{ + ssize_t wrote = pwrite((int)(intptr_t)file, buffer, size, offset); + if (wrote != size) + { + fatal("ERROR: failed to read %u bytes from file\n", size); + } +} + +void sys_rename(const char* src, const char* dst) +{ + if (rename(src, dst) != 0) + { + fatal("ERROR: failed to rename '%s' to '%s'\n", src, dst); + } +} + +#endif diff --git a/pkg2zip_sys.h b/pkg2zip_sys.h new file mode 100644 index 0000000..1579b07 --- /dev/null +++ b/pkg2zip_sys.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +typedef void* sys_file; + +sys_file sys_open(const char* fname, uint64_t* size); +sys_file sys_create(const char* fname); +void sys_close(sys_file file); +void sys_read(sys_file file, uint64_t offset, void* buffer, uint32_t size); +void sys_write(sys_file file, uint64_t offset, const void* buffer, uint32_t size); + +void sys_rename(const char* src, const char* dst); diff --git a/pkg2zip_utils.c b/pkg2zip_utils.c new file mode 100644 index 0000000..a8c4c10 --- /dev/null +++ b/pkg2zip_utils.c @@ -0,0 +1,48 @@ +#include "pkg2zip_utils.h" + +#include +#include +#include + +void fatal(const char* msg, ...) +{ + va_list args; + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); + + exit(EXIT_FAILURE); +} + +static int hex2byte(char ch) +{ + if (ch >= '0' && ch <= '9') + { + return ch - '0'; + } + else if (ch >= 'A' && ch <= 'Z') + { + return ch - 'A' + 10; + } + else if (ch >= 'a' && ch <= 'z') + { + return ch - 'a' + 10; + } + else + { + fatal("ERROR: invalid '%c' hex character\n", ch); + } +} + +void get_hex_bytes16(const char* str, uint8_t* bytes) +{ + for (size_t i = 0; i<16; i++) + { + if (str[0] == 0 || str[1] == 0) + { + fatal("ERROR: hex string must be 32 hex characters long\n"); + } + bytes[i] = (uint8_t)((hex2byte(str[0]) << 4) + hex2byte(str[1])); + str += 2; + } +} \ No newline at end of file diff --git a/pkg2zip_utils.h b/pkg2zip_utils.h new file mode 100644 index 0000000..1fd57c8 --- /dev/null +++ b/pkg2zip_utils.h @@ -0,0 +1,118 @@ +#pragma once + +#include + +#if defined(_MSC_VER) +# define NORETURN __declspec(noreturn) +#else +# define NORETURN __attribute__((noreturn)) +#endif + +void NORETURN fatal(const char* msg, ...) ; +void get_hex_bytes16(const char* str, uint8_t* bytes); + +static inline uint32_t min32(uint32_t a, uint32_t b) +{ + return a < b ? a : b; +} + +static inline uint64_t min64(uint64_t a, uint64_t b) +{ + return a < b ? a : b; +} + +static inline uint16_t get16le(const uint8_t* bytes) +{ + return (bytes[0]) | (bytes[1] << 8); +} + +static inline uint32_t get32le(const uint8_t* bytes) +{ + return (bytes[0]) | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); +} + +static inline uint64_t get64le(const uint8_t* bytes) +{ + return (uint64_t)bytes[0] + | ((uint64_t)bytes[1] << 8) + | ((uint64_t)bytes[2] << 16) + | ((uint64_t)bytes[3] << 24) + | ((uint64_t)bytes[4] << 32) + | ((uint64_t)bytes[5] << 40) + | ((uint64_t)bytes[6] << 48) + | ((uint64_t)bytes[7] << 56); +} + +static inline uint16_t get16be(const uint8_t* bytes) +{ + return (bytes[1]) | (bytes[0] << 8); +} + +static inline uint32_t get32be(const uint8_t* bytes) +{ + return (bytes[3]) | (bytes[2] << 8) | (bytes[1] << 16) | (bytes[0] << 24); +} + +static inline uint64_t get64be(const uint8_t* bytes) +{ + return (uint64_t)bytes[7] + | ((uint64_t)bytes[6] << 8) + | ((uint64_t)bytes[5] << 16) + | ((uint64_t)bytes[4] << 24) + | ((uint64_t)bytes[3] << 32) + | ((uint64_t)bytes[2] << 40) + | ((uint64_t)bytes[1] << 48) + | ((uint64_t)bytes[0] << 56); +} + +static inline void set16le(uint8_t* bytes, uint16_t x) +{ + bytes[0] = (uint8_t)x; + bytes[1] = (uint8_t)(x >> 8); +} + +static inline void set32le(uint8_t* bytes, uint32_t x) +{ + bytes[0] = (uint8_t)x; + bytes[1] = (uint8_t)(x >> 8); + bytes[2] = (uint8_t)(x >> 16); + bytes[3] = (uint8_t)(x >> 24); +} + +static inline void set64le(uint8_t* bytes, uint64_t x) +{ + bytes[0] = (uint8_t)x; + bytes[1] = (uint8_t)(x >> 8); + bytes[2] = (uint8_t)(x >> 16); + bytes[3] = (uint8_t)(x >> 24); + bytes[4] = (uint8_t)(x >> 32); + bytes[5] = (uint8_t)(x >> 40); + bytes[6] = (uint8_t)(x >> 48); + bytes[7] = (uint8_t)(x >> 56); +} + +static inline void set16be(uint8_t* bytes, uint16_t x) +{ + bytes[0] = (uint8_t)(x >> 8); + bytes[1] = (uint8_t)x; +} + +static inline void set32be(uint8_t* bytes, uint32_t x) +{ + bytes[0] = (uint8_t)(x >> 24); + bytes[1] = (uint8_t)(x >> 16); + bytes[2] = (uint8_t)(x >> 8); + bytes[3] = (uint8_t)x; +} + +static inline void set64be(uint8_t* bytes, uint64_t x) +{ + bytes[0] = (uint8_t)(x >> 56); + bytes[1] = (uint8_t)(x >> 48); + bytes[2] = (uint8_t)(x >> 40); + bytes[3] = (uint8_t)(x >> 32); + bytes[4] = (uint8_t)(x >> 24); + bytes[5] = (uint8_t)(x >> 16); + bytes[6] = (uint8_t)(x >> 8); + bytes[7] = (uint8_t)x; +} diff --git a/pkg2zip_zip.c b/pkg2zip_zip.c new file mode 100644 index 0000000..ecded47 --- /dev/null +++ b/pkg2zip_zip.c @@ -0,0 +1,344 @@ +#define _CRT_SECURE_NO_DEPRECATE + +#include "pkg2zip_zip.h" +#include "pkg2zip_utils.h" + +#include +#include + +#define ZIP_VERSION 45 +#define ZIP_METHOD_STORE 0 +#define ZIP_UTF8_FLAG (1 << 11) + +#define ZIP_DOS_ATTRIBUTE_DIRECTORY 0x10 +#define ZIP_DOS_ATTRIBUTE_ARCHIVE 0x20 + +#define ZIP_LOCAL_HEADER_SIZE 30 +#define ZIP_GLOBAL_HEADER_SIZE 46 +#define ZIP64_EOC_DIR_SIZE 56 +#define ZIP64_EOC_DIR_LOCATOR_SIZE 20 +#define ZIP_EOC_DIR_SIZE 22 + +#define ZIP_LOCAL_HEADER_CRC32_OFFSET 14 +#define ZIP_LOCAL_HEADER_FILENAME_LENGTH_OFFSET 26 + +static const uint32_t crc32[256] = +{ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +}; + +void zip_create(zip* z, const char* name) +{ + z->file = sys_create(name); + z->total = 0; + z->count = 0; + + time_t t = time(NULL); + struct tm* tm = localtime(&t); + z->date = (uint16_t)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); + z->time = (uint16_t)((tm->tm_hour << 11) + (tm->tm_min << 5) + (tm->tm_sec / 2)); +} + +void zip_add_folder(zip* z, const char* name) +{ + if (z->count == ZIP_MAX_FILES) + { + fatal("ERROR: too many files\n"); + } + + size_t name_length = strlen(name); + if (name_length > ZIP_MAX_FILENAME) + { + fatal("ERROR: dirname too long\n"); + } + + z->offset[z->count] = z->total; + z->size[z->count] = 0; + z->crc32[z->count] = 0; + z->count++; + + uint8_t header[ZIP_LOCAL_HEADER_SIZE] = { 0x50, 0x4b, 0x03, 0x04 }; + // version needed to extract + set16le(header + 4, ZIP_VERSION); + // general purpose bit flag + set16le(header + 6, ZIP_UTF8_FLAG); + // compression method + set16le(header + 8, ZIP_METHOD_STORE); + // last mod file time + set16le(header + 10, z->time); + // last mod file date + set16le(header + 12, z->date); + // file name length + set16le(header + 26, (uint16_t)name_length); + + sys_write(z->file, z->total, header, sizeof(header)); + z->total += sizeof(header); + + sys_write(z->file, z->total, name, (uint16_t)name_length); + z->total += name_length; +} + +void zip_begin_file(zip* z, const char* name) +{ + if (z->count == ZIP_MAX_FILES) + { + fatal("ERROR: too many files\n"); + } + + size_t name_length = strlen(name); + if (name_length > ZIP_MAX_FILENAME) + { + fatal("ERROR: filename too long\n"); + } + + z->offset[z->count] = z->total; + z->size[z->count] = 0; + z->crc32[z->count] = 0xffffffff; + + uint8_t header[ZIP_LOCAL_HEADER_SIZE] = { 0x50, 0x4b, 0x03, 0x04 }; + // version needed to extract + set16le(header + 4, ZIP_VERSION); + // general purpose bit flag + set16le(header + 6, ZIP_UTF8_FLAG); + // compression method + set16le(header + 8, ZIP_METHOD_STORE); + // last mod file time + set16le(header + 10, z->time); + // last mod file date + set16le(header + 12, z->date); + // file name length + set16le(header + 26, (uint16_t)name_length); + + sys_write(z->file, z->total, header, sizeof(header)); + z->total += sizeof(header); + + sys_write(z->file, z->total, name, (uint16_t)name_length); + z->total += name_length; +} + +void zip_write_file(zip* z, const void* data, uint32_t size) +{ + sys_write(z->file, z->total, data, size); + z->total += size; + z->size[z->count] += size; + + const uint8_t* bytes = data; + uint32_t tmp = z->crc32[z->count]; + for (uint32_t i = 0; i < size; i++) + { + tmp = (tmp >> 8) ^ crc32[(uint8_t)(tmp ^ bytes[i])]; + } + z->crc32[z->count] = tmp; +} + +void zip_end_file(zip* z) +{ + z->crc32[z->count] ^= 0xffffffff; + + uint64_t size = z->size[z->count]; + if (size > 0) + { + uint8_t update[3 * sizeof(uint32_t)]; + // crc-32 + set32le(update + 0, z->crc32[z->count]); + // compressed size + set32le(update + 4, (uint32_t)min64(size, 0xffffffff)); + // uncompressed size + set32le(update + 8, (uint32_t)min64(size, 0xffffffff)); + + sys_write(z->file, z->offset[z->count] + ZIP_LOCAL_HEADER_CRC32_OFFSET, update, sizeof(update)); + } + + z->count++; +} + +void zip_close(zip* z) +{ + uint64_t central_dir_offset = z->total; + + // central directory headers + for (uint32_t i = 0; i < z->count; i++) + { + uint8_t local[ZIP_LOCAL_HEADER_SIZE]; + sys_read(z->file, z->offset[i], local, sizeof(local)); + + uint32_t filename_length = get16le(local + ZIP_LOCAL_HEADER_FILENAME_LENGTH_OFFSET); + + uint8_t global[ZIP_GLOBAL_HEADER_SIZE + ZIP_MAX_FILENAME] = { 0x50, 0x4b, 0x01, 0x02 }; + sys_read(z->file, z->offset[i] + sizeof(local), global + ZIP_GLOBAL_HEADER_SIZE, filename_length); + int is_folder = global[ZIP_GLOBAL_HEADER_SIZE + filename_length - 1] == '/'; + + uint8_t extra[28]; + uint16_t extra_size = 0; + uint64_t size = z->size[i]; + uint64_t offset = z->offset[i]; + uint32_t attributes = ZIP_DOS_ATTRIBUTE_ARCHIVE; + if (is_folder) + { + attributes |= ZIP_DOS_ATTRIBUTE_DIRECTORY; + if (offset > 0xffffffff) + { + extra_size += sizeof(uint64_t); + } + } + else + { + if (size > 0xffffffff) + { + extra_size += 2 * sizeof(uint64_t); + } + if (offset > 0xffffffff) + { + extra_size += sizeof(uint64_t); + } + } + + if (extra_size) + { + extra_size += 2 * sizeof(uint16_t); + } + + // version made by + set16le(global + 4, ZIP_VERSION); + // version needed to extract + set16le(global + 6, ZIP_VERSION); + // general purpose bit flag + set16le(global + 8, ZIP_UTF8_FLAG); + // compression method + set16le(global + 10, ZIP_METHOD_STORE); + // last mod file time + set16le(global + 12, z->time); + // last mod file date + set16le(global + 14, z->date); + // crc-32 + set32le(global + 16, z->crc32[i]); + // compressed size + set32le(global + 20, (uint32_t)min64(size, 0xffffffff)); + // uncompressed size + set32le(global + 24, (uint32_t)min64(size, 0xffffffff)); + // file name length + set16le(global + 28, (uint16_t)filename_length); + // extra field length + set16le(global + 30, extra_size); + // external file attributes + set32le(global + 38, attributes); + // relative offset of local header 4 bytes + set32le(global + 42, (uint32_t)min64(offset, 0xffffffff)); + + sys_write(z->file, z->total, global, ZIP_GLOBAL_HEADER_SIZE + filename_length); + z->total += ZIP_GLOBAL_HEADER_SIZE + filename_length; + + // zip64 Extended Information Extra Field + set16le(extra + 0, 1); + // size of this "extra" block + set16le(extra + 2, extra_size - 2*sizeof(uint16_t)); + if (size > 0xffffffff) + { + // original uncompressed file size + set64le(extra + 4, size); + // size of compressed data + set64le(extra + 12, size); + } + if (offset > 0xffffffff) + { + // offset of local header record + set64le(extra + 20, offset); + } + + if (extra_size > 2 * sizeof(uint16_t)) + { + sys_write(z->file, z->total, extra, extra_size); + z->total += extra_size; + } + } + + uint64_t end_of_central_dir_offset = z->total; + uint32_t central_dir_size = (uint32_t)(end_of_central_dir_offset - central_dir_offset); + + // zip64 end of central directory record + { + uint8_t header[ZIP64_EOC_DIR_SIZE] = { 0x50, 0x4b, 0x06, 0x06 }; + // size of zip64 end of central directory record + set64le(header + 4, sizeof(header) - sizeof(uint32_t) - sizeof(uint64_t)); + // version made by + set16le(header + 12, ZIP_VERSION); + // version needed to extract + set16le(header + 14, ZIP_VERSION); + // number of this disk + set32le(header + 16, 0); + // number of the disk with the start of the central directory + set32le(header + 20, 0); + // total number of entries in the central directory on this disk + set64le(header + 24, z->count); + // total number of entries in the central directory + set64le(header + 32, z->count); + // size of the central directory + set64le(header + 40, central_dir_size); + // offset of start of central directory with respect to the starting disk number + set64le(header + 48, central_dir_offset); + + sys_write(z->file, z->total, header, sizeof(header)); + z->total += sizeof(header); + } + + // zip64 end of central directory locator + { + uint8_t header[ZIP64_EOC_DIR_LOCATOR_SIZE] = { 0x50, 0x4b, 0x06, 0x07 }; + // relative offset of the zip64 end of central directory record 8 bytes + set64le(header + 8, end_of_central_dir_offset); + // total number of disks + set32le(header + 16, 1); + + sys_write(z->file, z->total, header, sizeof(header)); + z->total += sizeof(header); + } + + // end of central directory record + { + uint8_t header[ZIP_EOC_DIR_SIZE] = { 0x50, 0x4b, 0x05, 0x06 }; + // total number of entries in the central directory on this disk + set16le(header + 8, (uint16_t)z->count); + // total number of entries in the central directory + set16le(header + 10, (uint16_t)z->count); + // size of the central directory + set32le(header + 12, central_dir_size); + // offset of start of central directory with respect to the starting disk number + set32le(header + 16, (uint32_t)min64(central_dir_offset, 0xffffffff)); + + sys_write(z->file, z->total, header, sizeof(header)); + z->total += sizeof(header); + } + + sys_close(z->file); +} diff --git a/pkg2zip_zip.h b/pkg2zip_zip.h new file mode 100644 index 0000000..c91c8ad --- /dev/null +++ b/pkg2zip_zip.h @@ -0,0 +1,27 @@ +#pragma once + +#include "pkg2zip_sys.h" + +#include +#include + +#define ZIP_MAX_FILES 8192 +#define ZIP_MAX_FILENAME 1024 + +typedef struct { + sys_file file; + uint64_t total; + uint32_t count; + uint16_t time; + uint16_t date; + uint64_t offset[ZIP_MAX_FILES]; + uint64_t size[ZIP_MAX_FILES]; + uint32_t crc32[ZIP_MAX_FILES]; +} zip; + +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); +void zip_write_file(zip* z, const void* data, uint32_t size); +void zip_end_file(zip* z); +void zip_close(zip* z);