Skip to content

Instantly share code, notes, and snippets.

@shinyquagsire23
Created May 7, 2019 22:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shinyquagsire23/a85abd77e29bc594281c1bc53a0e4573 to your computer and use it in GitHub Desktop.
Save shinyquagsire23/a85abd77e29bc594281c1bc53a0e4573 to your computer and use it in GitHub Desktop.
TBV package extraction for Lionel Train Town
#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