Created
May 7, 2019 22:59
-
-
Save shinyquagsire23/a85abd77e29bc594281c1bc53a0e4573 to your computer and use it in GitHub Desktop.
TBV package extraction for Lionel Train Town
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
#include <stdint.h> | |
#include <stdio.h> | |
struct pkx_header | |
{ | |
uint32_t magic1; | |
uint32_t magic2; | |
uint32_t magic3; | |
uint32_t type; | |
uint32_t comp_size; | |
uint32_t decomp_size; | |
uint8_t data_start[]; | |
}; | |
struct __attribute__((packed)) tbv_header | |
{ | |
char magic[9]; | |
uint16_t unk1; | |
uint16_t count; | |
uint32_t unk2; | |
char unk3[24]; | |
}; | |
struct __attribute__((packed)) tbv_offset | |
{ | |
uint32_t hash_maybe; | |
uint32_t offset; | |
}; | |
// PKX uses LZO compression apparently | |
int64_t decompress_pkx(uint32_t comp_size_unused, uint8_t *unk_unused, uint8_t *src, uint32_t comp_size, uint8_t *dst, uint32_t *decomp_size_out, uint8_t is_102 /* added */); | |
int main(int argc, char** argv) | |
{ | |
struct tbv_header tbv_head; | |
if (argc < 3) | |
{ | |
printf("Usage: %s [file.tbv] [outdir/]\n", argv[0]); | |
return -1; | |
} | |
FILE* f = fopen(argv[1], "rb"); | |
if (!f) | |
{ | |
printf("Failed to open file `%s'!\n", argv[1]); | |
return -1; | |
} | |
fread(&tbv_head, sizeof(tbv_head), 1, f); | |
printf("Magic: `%s'\n", tbv_head.magic); | |
printf("Unk1: 0x%x\n", tbv_head.unk1); | |
printf("Total files: %u\n", tbv_head.count); | |
printf("Unk2: 0x%x\n", tbv_head.unk2); | |
printf("Unk3: `%s'\n", tbv_head.unk3); | |
long offsets_start = ftell(f); | |
for (int i = 0; i < tbv_head.count; i++) | |
{ | |
char name[24]; | |
uint32_t size; | |
struct tbv_offset f_offset; | |
char tmp[0x100]; | |
fseek(f, offsets_start + sizeof(f_offset) * i, SEEK_SET); | |
fread(&f_offset, sizeof(f_offset), 1, f); | |
fseek(f, f_offset.offset, SEEK_SET); | |
fread(&name, sizeof(name), 1, f); | |
fread(&size, sizeof(size), 1, f); | |
// Hash is almost definitely for the name (for lookup) | |
printf("%u: hash(?) %x offset %x, name `%s' size %x\n", i, f_offset.hash_maybe, f_offset.offset, name, size); | |
snprintf(tmp, 0x100, "%s/%s", argv[2], name); | |
FILE* fout = fopen(tmp, "wb"); | |
if (!fout) | |
{ | |
printf("Failed to open file `%s' for writing.\n", tmp); | |
continue; | |
} | |
void* contents = malloc(size); | |
fread(contents, size, 1, f); | |
if (!memcmp(contents, "PKX", 3)) | |
{ | |
struct pkx_header* header = (struct pkx_header*)contents; | |
void* out = malloc(header->decomp_size + 0x1000); | |
if (out == NULL) | |
{ | |
printf("Failed to allocate %x bytes for file `%s'! Exiting...\n", header->decomp_size, name); | |
return -1; | |
} | |
uint32_t decomp_size_out = 0; | |
decompress_pkx(-1, NULL, header->data_start, header->comp_size, out, &decomp_size_out, header->type == 0x102); | |
if (decomp_size_out != header->decomp_size) | |
{ | |
printf(" Decompressed size mismatch! Expected %x, got %x\n Writing out compressed file...\n", header->decomp_size, decomp_size_out); | |
fwrite(contents, size, 1, fout); | |
snprintf(tmp, 0x100, "%s/%s.fail", argv[2], name); | |
FILE* fout2 = fopen(tmp, "wb"); | |
if (fout2) | |
{ | |
fwrite(out, decomp_size_out, 1, fout2); | |
fclose(fout2); | |
} | |
} | |
else | |
{ | |
fwrite(out, header->decomp_size, 1, fout); | |
} | |
free(out); | |
} | |
else | |
{ | |
fwrite(contents, size, 1, fout); | |
} | |
free(contents); | |
fclose(fout); | |
} | |
fclose(f); | |
} | |
int64_t decompress_pkx(uint32_t comp_size_unused, uint8_t *unk_unused, uint8_t *src, uint32_t comp_size, uint8_t *dst, uint32_t *decomp_size_out, uint8_t is_102 /* added */) | |
{ | |
uint8_t current_code; | |
uint32_t uVar2; | |
uint32_t copy_cnt; | |
uint8_t *cur_data; | |
uint8_t *cur_out; | |
void copy8(uint8_t* dst, uint8_t* src, uint32_t size) | |
{ | |
while (size != 0) | |
{ | |
*(dst++) = *(src++); | |
size--; | |
} | |
} | |
void copy32(uint8_t* dst, uint8_t* src, uint32_t size) | |
{ | |
uint32_t copy_cnt = size >> 2; | |
do | |
{ | |
*(uint32_t *)dst = *(uint32_t *)src; | |
dst += 4; | |
src += 4; | |
copy_cnt--; | |
} while (copy_cnt != 0); | |
} | |
cur_data = src + 1; | |
current_code = *src; | |
cur_out = dst; | |
start_label: | |
uVar2 = (uint32_t)current_code; | |
if (current_code != 0x10 && current_code != 0x11) | |
{ | |
if (current_code == 0) | |
{ | |
while(1) | |
{ | |
current_code = *(cur_data++); | |
if (current_code != 0) break; | |
uVar2 = uVar2 + 0xff; | |
} | |
uVar2 = uVar2 + 0x15 + (uint32_t)current_code; | |
} | |
else if (current_code < 0x10) | |
{ | |
uVar2 = uVar2 + 6; | |
} | |
else | |
{ | |
uVar2 = (current_code - 0xe); | |
cur_out = dst; | |
} | |
copy32(cur_out, cur_data, uVar2); | |
cur_out += (uVar2 >> 2) << 2; | |
cur_data += (uVar2 >> 2) << 2; | |
// Not sure what this is doing exactlyyyy? | |
// I mean idk what any of this is doing honestly. | |
uVar2 = (uVar2 ^ 3) & 3; | |
cur_data -= uVar2; | |
cur_out -= uVar2; | |
current_code = *(cur_data++); | |
if (current_code < 0x10) | |
{ | |
*(uint32_t *)cur_out = *(uint32_t *)(cur_out - (is_102 ? 0x401 : 0x801) - ((current_code >> 2) + *cur_data * 4)); | |
cur_out += 3; | |
cur_data += 2; | |
if (current_code & 3 == 0) | |
{ | |
current_code = *(cur_data++); | |
if (current_code < 0x10) goto start_label; | |
} | |
else | |
{ | |
*(uint32_t *)cur_out = *(uint32_t *)cur_data; | |
cur_data += current_code & 3; | |
cur_out += current_code & 3; | |
current_code = *(cur_data++); | |
} | |
} | |
} | |
mid_label: | |
uVar2 = current_code; | |
if (current_code >= 0x40) | |
{ | |
uint32_t uVar3 = ((current_code >> 2) & 3) + (uint32_t)*cur_data * 4; | |
cur_data++; | |
copy_cnt = (current_code >> 4) + 2; | |
if (uVar3 < 3) | |
{ | |
copy8(cur_out, (cur_out - uVar3 - 1), copy_cnt - 3); | |
cur_out += (copy_cnt - 3); | |
} | |
else | |
{ | |
copy32(cur_out, (cur_out - uVar3 - 1), copy_cnt); | |
cur_out += (copy_cnt - 3); | |
} | |
} | |
else if (current_code >= 0x20) | |
{ | |
uVar2 = uVar2 & 0x1f; | |
if (uVar2 == 0) | |
{ | |
while(1) | |
{ | |
current_code = *(cur_data++); | |
if (current_code != 0) break; | |
uVar2 = uVar2 + 0xff; | |
} | |
copy_cnt = uVar2 + 0x24 + (uint32_t)current_code; | |
} | |
else | |
{ | |
copy_cnt = uVar2 + 5; | |
} | |
if (*(uint16_t *)cur_data >> 2 < 3) | |
{ | |
copy8(cur_out, (cur_out - (*(uint16_t *)cur_data >> 2) - 1), copy_cnt - 3); | |
cur_out += (copy_cnt - 3); | |
} | |
else | |
{ | |
copy32(cur_out, (cur_out - (*(uint16_t *)cur_data >> 2) - 1), copy_cnt); | |
cur_out += (copy_cnt - 3); | |
} | |
cur_data += 2; | |
} | |
else if (current_code >= 0x10) | |
{ | |
copy_cnt = current_code & 7; | |
if (copy_cnt == 0) | |
{ | |
while(1) | |
{ | |
current_code = *(cur_data++); | |
if (current_code != 0) break; | |
copy_cnt = copy_cnt + 0xff; | |
} | |
copy_cnt = (uint32_t)current_code + 0xc + copy_cnt; | |
} | |
else | |
{ | |
copy_cnt = copy_cnt + 5; | |
} | |
uVar2 = (uVar2 & 8) << 13 | *(uint16_t *)cur_data; | |
cur_data += 2; | |
if ((uVar2 >> 2) == 0) | |
{ | |
int retval = ((uint32_t)(uVar2 >> 10) << 24) | copy_cnt != 6; | |
if (src + comp_size < cur_data) | |
{ | |
retval = 4; | |
} | |
else | |
{ | |
if (cur_data < src + comp_size) | |
{ | |
retval = 8; | |
} | |
} | |
if (decomp_size_out) // this was added | |
*decomp_size_out = (uint32_t)(cur_out - dst); | |
return -retval; //CONCAT44(unk_unused,-retval); | |
} | |
copy32(cur_out, (cur_out - (uVar2 >> 2) - 0x4000), copy_cnt); | |
cur_out += (copy_cnt - 3); | |
} | |
else | |
{ | |
int idx_tmp = -((uVar2 >> 2) + (*cur_data * 4)) - 1; | |
cur_data++; | |
cur_out[0] = cur_out[idx_tmp]; | |
cur_out[1] = cur_out[idx_tmp + 1]; | |
cur_out += 2; | |
} | |
uVar2 = (uint32_t)*(cur_data - 2) & 3; | |
if (uVar2 == 0) | |
{ | |
current_code = *(cur_data++); | |
if (current_code < 0x10) goto start_label; | |
} | |
else | |
{ | |
*(uint32_t *)cur_out = *(uint32_t *)cur_data; | |
cur_data += uVar2; | |
cur_out += uVar2; | |
current_code = (uint32_t)*(cur_data++); | |
} | |
goto mid_label; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment