Last active
October 4, 2024 18:04
-
-
Save bruvzg/ee5a90d27856dbbd8c8561f81ee06bde to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/***************************************************************************/ | |
/* Godot PCK extractor (pckext.cpp) */ | |
/***************************************************************************/ | |
/* This program is free software: you can redistribute it and/or modify */ | |
/* it under the terms of the GNU General Public License as published by */ | |
/* the Free Software Foundation, either version 3 of the License, or */ | |
/* (at your option) any later version. */ | |
/* */ | |
/* This program is distributed in the hope that it will be useful, */ | |
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ | |
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ | |
/* GNU General Public License for more details. */ | |
/* */ | |
/* You should have received a copy of the GNU General Public License */ | |
/* along with this program. If not, see <https://www.gnu.org/licenses/>. */ | |
/***************************************************************************/ | |
//Build command: g++ ./pckext.cpp -o pckext -lcrypto | |
#include <errno.h> | |
#include <stdio.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <sys/stat.h> | |
#include <openssl/md5.h> | |
int32_t read32(FILE *p_file) { | |
int32_t i32 = 0; | |
fread(&i32, sizeof(i32), 1, p_file); | |
return i32; | |
} | |
int64_t read64(FILE *p_file) { | |
int64_t i64 = 0; | |
fread(&i64, sizeof(i64), 1, p_file); | |
return i64; | |
} | |
void print_usage(const char *p_exename) { | |
printf("Godot PCK extractor.\n\n"); | |
printf("Usage: %s [command] [file] [-d dir]\n\n", p_exename); | |
printf("Commands:\n"); | |
printf(" -t test integrity of archive\n"); | |
printf(" -l list contents of archive\n"); | |
printf(" -x extract files (with full paths)\n"); | |
printf("Options:\n"); | |
printf(" -d <DIR> output directory\n"); | |
} | |
void make_tree(const char *p_path) { | |
int path_len = strlen(p_path); | |
for (int i = 1; i < path_len; ++i) { | |
if (p_path[i] == '/') { | |
char *dir_buffer = (char *)calloc(i, 1); | |
strncpy(dir_buffer, p_path, i); | |
#if defined(_WIN32) | |
mkdir(dir_buffer); | |
#else | |
mkdir(dir_buffer, 0700); | |
#endif | |
free(dir_buffer); | |
} | |
} | |
} | |
bool test_file(uint64_t p_offset, uint64_t p_size, FILE *p_archive, uint8_t p_md5[16]) { | |
MD5_CTX ctx; | |
MD5_Init(&ctx); | |
fpos_t old_pos; | |
fgetpos(p_archive, &old_pos); | |
fseek(p_archive, p_offset, SEEK_SET); | |
void *buffer = calloc(1, 65536); | |
int blk = p_size / 65536; | |
size_t read = 0; | |
for (int i = 0; i <= blk; i++) { | |
size_t _size = (i == blk) ? p_size % 65536 : 65536; | |
size_t _read = fread(buffer, 1, _size, p_archive); | |
read += _read; | |
MD5_Update(&ctx, (unsigned char *)buffer, _read); | |
} | |
free(buffer); | |
fsetpos(p_archive, &old_pos); | |
uint8_t md5[MD5_DIGEST_LENGTH]; | |
MD5_Final(md5, &ctx); | |
if (p_size != read) { | |
printf(", size mismatch - %lu (should be %llu)", read, p_size); | |
} | |
bool ok = true; | |
for (int i = 0; i < 16; i++) { | |
ok &= (md5[i] == p_md5[i]); | |
} | |
if (!ok) { | |
printf(", digest mismatch - "); | |
for (int i = 0; i < 16; i++) { | |
printf("%02x", md5[i]); | |
} | |
printf(" (should be "); | |
for (int i = 0; i < 16; i++) { | |
printf("%02x", p_md5[i]); | |
} | |
printf(")"); | |
} else { | |
printf(", digest OK"); | |
} | |
return ok; | |
} | |
bool extract_file(const char* p_filename, uint64_t p_offset, uint64_t p_size, FILE *p_archive, uint8_t p_md5[16]) { | |
//Make directory tree | |
make_tree(p_filename); | |
MD5_CTX ctx; | |
MD5_Init(&ctx); | |
//Extract file | |
FILE *ext_file = fopen(p_filename, "wb"); | |
if (ext_file == NULL) { | |
printf(", could not open %s.", p_filename); | |
return false; | |
} | |
fpos_t old_pos; | |
fgetpos(p_archive, &old_pos); | |
fseek(p_archive, p_offset, SEEK_SET); | |
void *buffer = calloc(1, 65536); | |
int blk = p_size / 65536; | |
size_t read = 0; | |
size_t writ = 0; | |
for (int i = 0; i <= blk; i++) { | |
size_t _size = (i == blk) ? p_size % 65536 : 65536; | |
size_t _read = fread(buffer, 1, _size, p_archive); | |
read += _read; | |
MD5_Update(&ctx, (unsigned char *)buffer, _read); | |
writ += fwrite(buffer, 1, _read, ext_file); | |
} | |
free(buffer); | |
uint8_t md5[MD5_DIGEST_LENGTH]; | |
MD5_Final(md5, &ctx); | |
if ((read != writ) || (p_size != writ)) { | |
printf(", size mismatch - %lu (should be %llu)", read, p_size); | |
} | |
bool ok = true; | |
for (int i = 0; i < 16; i++) { | |
ok &= (md5[i] == p_md5[i]); | |
} | |
if (!ok) { | |
printf(", digest mismatch - "); | |
for (int i = 0; i < 16; i++) { | |
printf("%02x", md5[i]); | |
} | |
printf(" (should be "); | |
for (int i = 0; i < 16; i++) { | |
printf("%02x", p_md5[i]); | |
} | |
printf(")"); | |
} | |
fsetpos(p_archive, &old_pos); | |
fclose(ext_file); | |
return ok; | |
} | |
int main(int argc, char *argv[]) { | |
if ((argc != 3) && (argc != 5)) { | |
print_usage(argv[0]); | |
return -1; | |
} | |
if ((strcmp(argv[1], "-t") != 0) && (strcmp(argv[1], "-l") != 0) && (strcmp(argv[1], "-x") != 0)) { | |
printf("Invalid command: %s\n\n", argv[1]); | |
print_usage(argv[0]); | |
return -1; | |
} | |
if ((argc == 5) && ((strcmp(argv[3], "-d") != 0) || (strcmp(argv[1], "-x") != 0))) { | |
printf("Invalid option: %s\n\n", argv[3]); | |
print_usage(argv[0]); | |
return -1; | |
} | |
FILE *file = fopen(argv[2], "rb"); | |
if (file == NULL) { | |
printf("Could not open %s\n", argv[2]); | |
return -1; | |
} | |
//Change current dir to output | |
if ((argc == 5) && (strcmp(argv[3], "-d") == 0)) { | |
if (chdir(argv[4]) != 0) { | |
printf("Could not open output directory.\n"); | |
fclose(file); | |
return -1; | |
} | |
} | |
//Read magic @ find offset for self contained exe | |
int32_t magic = read32(file); | |
bool is_embedded = false; | |
if (magic != 0x43504447) { | |
fseek(file, -4, SEEK_END); | |
magic = read32(file); | |
if (magic != 0x43504447) { | |
printf("Invalid file format.\n"); | |
fclose(file); | |
return -1; | |
} | |
fseek(file, -12, SEEK_CUR); | |
uint64_t ds = read64(file); | |
fseek(file, -(ds + 8), SEEK_CUR); | |
magic = read32(file); | |
if (magic != 0x43504447) { | |
printf("Invalid file format.\n"); | |
fclose(file); | |
return -1; | |
} | |
bool is_embedded = true; | |
} | |
//Read version | |
uint32_t version = read32(file); | |
uint32_t ver_major = read32(file); | |
uint32_t ver_minor = read32(file); | |
uint32_t ver_rev = read32(file); | |
//Reserved | |
uint32_t extra[16]; | |
for (int i = 0; i < 16; i++) { | |
extra[i] = read32(file); | |
} | |
printf("PCK version: %d, created by Godot %d.%d rev %d, %s\n\n", version, ver_major, ver_minor, ver_rev, is_embedded ? "self contained exe" : "standalone"); | |
if ((version != 0) && (version != 1)) { | |
printf("Unsupported PCK version!\n"); | |
fclose(file); | |
return -1; | |
} | |
//Read file list | |
int file_count = read32(file); | |
bool valid = true; | |
for (int i = 0; i < file_count; i++) { | |
uint32_t name_size = read32(file); | |
char *name_buffer = (char *)calloc(name_size + 1, 1); | |
memset(name_buffer, 0, name_size + 1); | |
fread(name_buffer, name_size, 1, file); | |
uint64_t ofs = read64(file); | |
uint64_t size = read64(file); | |
uint8_t md5[16]; | |
fread(md5, 16, 1, file); | |
if (strcmp(argv[1], "-t") == 0) { | |
printf(" testing: %s", name_buffer + 6); //skip res:// | |
valid &= test_file(ofs, size, file, md5); | |
printf("\n"); | |
} else if (strcmp(argv[1], "-x") == 0) { | |
printf(" extracting: %s", name_buffer + 6); //skip res:// | |
valid &= extract_file(name_buffer + 6, ofs, size, file, md5); //skip res:// | |
printf("\n"); | |
} else { | |
printf(" %s (%llu bytes)\n", name_buffer + 6, size); //skip res:// | |
} | |
free(name_buffer); | |
} | |
fclose(file); | |
if ((strcmp(argv[1], "-t") == 0) || (strcmp(argv[1], "-x") == 0)) { | |
if (valid) { | |
printf("\nNo errors detected.\n"); | |
} else { | |
printf("\nAt least one error was detected!\n"); | |
return -1; | |
} | |
} | |
return 0; | |
} |
found solution here
https://stackoverflow.com/a/14296004/10741163
This compiled for me on Manjaro
g++ -std=c++11 pckext.cpp -lcrypto -o pckextractor && ./pckextractor
For OSX:
brew install openssl
g++ -std=c++11 pckext.cpp -L/opt/homebrew/opt/openssl/lib/ -I/opt/homebrew/opt/openssl/include/ -lcrypto -o godot_pck_extract
Although it doesn't seem to work with pck files created with Godot 4.
./godot_pck_extract -l myapp-3.0.pck
PCK version: 2, created by Godot 4.2 rev 2, standalone
Unsupported PCK version!
Is it an easy fix to get v2 (Godot 4??) support? reddit discussion
Is it an easy fix to get v2 (Godot 4??) support?
You can use something like this (if it's not encrypted PCK):
--- a.cpp 2024-08-16 07:56:39
+++ b.cpp 2024-08-16 08:10:56
@@ -241,6 +241,19 @@
uint32_t ver_major = read32(file);
uint32_t ver_minor = read32(file);
uint32_t ver_rev = read32(file);
+ uint64_t file_base = 0;
+ uint32_t flags = 0;
+
+ if (version == 2) {
+ flags = read32(file);
+ file_base = read64(file);
+
+ if (flags & 1) {
+ printf("Encrypted PCK!");
+ fclose(file);
+ return -1;
+ }
+ }
//Reserved
uint32_t extra[16];
\ No newline at end of file
@@ -250,7 +263,7 @@
printf("PCK version: %d, created by Godot %d.%d rev %d, %s\n\n", version, ver_major, ver_minor, ver_rev, is_embedded ? "self contained exe" : "standalone");
- if ((version != 0) && (version != 1)) {
+ if ((version != 0) && (version != 1) && (version != 2)) {
printf("Unsupported PCK version!\n");
fclose(file);
return -1;
\ No newline at end of file
@@ -266,12 +279,19 @@
memset(name_buffer, 0, name_size + 1);
fread(name_buffer, name_size, 1, file);
- uint64_t ofs = read64(file);
+ uint64_t ofs = read64(file) + file_base;
uint64_t size = read64(file);
+ uint32_t file_flags = 0;
uint8_t md5[16];
fread(md5, 16, 1, file);
-
- if (strcmp(argv[1], "-t") == 0) {
+ if (version == 2) {
+ file_flags = read32(file);
+ }
+
+ if (file_flags & 1) {
+ printf(" %s (%llu bytes), encrypted file, skipped\n", name_buffer + 6, size);
+ valid = false;
+ } else if (strcmp(argv[1], "-t") == 0) {
printf(" testing: %s", name_buffer + 6); //skip res://
valid &= test_file(ofs, size, file, md5);
printf("\n");
\ No newline at end of file
Or you can use https://github.com/bruvzg/gdsdecomp
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
ty for the share. unfortunately i'm getting
my cmd is:
g++ -std=c++11 pckextractor.cpp -o pckextractor && ./pckextractor