Skip to content

Instantly share code, notes, and snippets.

@bruvzg
Last active January 23, 2024 16:08
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bruvzg/ee5a90d27856dbbd8c8561f81ee06bde to your computer and use it in GitHub Desktop.
Save bruvzg/ee5a90d27856dbbd8c8561f81ee06bde to your computer and use it in GitHub Desktop.
/***************************************************************************/
/* 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;
}
@girng
Copy link

girng commented Jan 2, 2019

ty for the share. unfortunately i'm getting

pckextractor.cpp: In function ‘bool test_file(uint64_t, uint64_t, FILE*, uint8_t*)’:
pckextractor.cpp:91:64: warning: format ‘%llu’ expects argument of type ‘long long unsigned int’, but argument 3 has type ‘uint64_t {aka long unsigned int}’ [-Wformat=]
   printf(", size mismatch - %lu (should be %llu)", read, p_size);
                                                                ^
pckextractor.cpp: In function ‘bool extract_file(const char*, uint64_t, uint64_t, FILE*, uint8_t*)’:
pckextractor.cpp:151:64: warning: format ‘%llu’ expects argument of type ‘long long unsigned int’, but argument 3 has type ‘uint64_t {aka long unsigned int}’ [-Wformat=]
   printf(", size mismatch - %lu (should be %llu)", read, p_size);
                                                                ^
pckextractor.cpp: In function ‘int main(int, char**)’:
pckextractor.cpp:277:55: warning: format ‘%llu’ expects argument of type ‘long long unsigned int’, but argument 3 has type ‘uint64_t {aka long unsigned int}’ [-Wformat=]
    printf("  %s (%llu bytes)\n", name_buffer + 6, size);  //skip res://
                                                       ^
/tmp/ccE2Q1wl.o: In function `test_file(unsigned long, unsigned long, _IO_FILE*, unsigned char*)':
pckextractor.cpp:(.text+0x1ee): undefined reference to `MD5_Init'
pckextractor.cpp:(.text+0x2ed): undefined reference to `MD5_Update'
pckextractor.cpp:(.text+0x334): undefined reference to `MD5_Final'
/tmp/ccE2Q1wl.o: In function `extract_file(char const*, unsigned long, unsigned long, _IO_FILE*, unsigned char*)':
pckextractor.cpp:(.text+0x509): undefined reference to `MD5_Init'
pckextractor.cpp:(.text+0x65b): undefined reference to `MD5_Update'
pckextractor.cpp:(.text+0x6b2): undefined reference to `MD5_Final'
collect2: error: ld returned 1 exit status

my cmd is: g++ -std=c++11 pckextractor.cpp -o pckextractor && ./pckextractor

@girng
Copy link

girng commented Jan 2, 2019

@monkeyx-net
Copy link

This compiled for me on Manjaro
g++ -std=c++11 pckext.cpp -lcrypto -o pckextractor && ./pckextractor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment