Skip to content

Instantly share code, notes, and snippets.

@matthinc
Last active November 17, 2019 15:31
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 matthinc/7385e0ed6fc2528e184f038ee3e72f0e to your computer and use it in GitHub Desktop.
Save matthinc/7385e0ed6fc2528e184f038ee3e72f0e to your computer and use it in GitHub Desktop.
#include<stdio.h>
#include<stdint.h>
#include<stdlib.h>
#include <stdbool.h>
#include <sys/stat.h>
// Constants
const uint32_t MAX_READ_WRITE_BLOCK_SIZE = 256;
const uint32_t MAX_FILENAME_SIZE = 64;
// Location and length of a data block
// Represents a row in the file table
typedef struct {
uint32_t b_fb_0;
uint32_t b_fb_1;
uint32_t b_pos;
uint32_t b_len;
} data_block;
// Entry in directory table
typedef struct {
uint32_t d_u;
uint32_t d_l;
} file_meta;
// Meta information and name of a file/directory
typedef struct {
file_meta* meta;
char* name;
} game_file;
// The first 4 bytes mark the begin of the file-table
uint32_t get_position_of_file_table(FILE* file) {
uint32_t idx_table_pos;
fread(&idx_table_pos, 4, 1, file);
return idx_table_pos;
}
// Read single row from file table
data_block* read_file_table(FILE* file, uint32_t position, int file_num) {
data_block* row = malloc(0x16);
fseek(file, position + (file_num * 0x10), SEEK_SET);
fread(row, 0x10, 1, file);
return row;
}
// Length of file-table
uint32_t read_length_of_file_table(FILE* file, uint32_t position) {
data_block* first_row = read_file_table(file, position, 0);
uint32_t length = first_row->b_fb_1;
return length;
}
// Length of directory table (in entries)
uint32_t read_file_table_len(FILE* file, uint32_t position) {
uint32_t* len;
fseek(file, position, SEEK_SET);
fread(len, 4, 1, file);
return *len;
}
// Read file list with names and metadata from archive
game_file** get_files(FILE* file, uint32_t dir_pos, uint32_t dir_len, uint32_t name_pos) {
dir_pos += 16;
game_file** files = malloc(dir_len * sizeof(game_file));
// Read metadata
for (int i = 0; i < dir_len; i++) {
// Read entry from file-table
files[i] = malloc(sizeof(game_file));
file_meta* entry = malloc(sizeof(file_meta));
fseek(file, dir_pos + (i * 8), SEEK_SET);
fread(entry, 8, 1, file);
files[i]->meta = entry;
}
// Read names
fseek(file, name_pos, SEEK_SET);
for (int i = 0; i < dir_len; i++) {
char buff[MAX_FILENAME_SIZE];
char* name = malloc(MAX_FILENAME_SIZE);
fread(buff, 1, MAX_FILENAME_SIZE, file);
strcpy(name, buff);
files[i]->name = name;
name_pos = strlen(name) + name_pos + 1;
fseek(file, name_pos, SEEK_SET);
}
return files;
}
// returns true if file is a directory
bool is_directory(game_file* file) {
return (file->meta->d_l & 0xffff0000) == 0;
}
// Export to file
void export_data(FILE* from, data_block* location, char* filename) {
FILE* export = fopen(filename, "w");
fseek(from, location->b_pos, SEEK_SET);
// Read byte per byte
uint32_t left_to_read = location->b_len;
while (left_to_read > 0) {
int read_size = MAX_READ_WRITE_BLOCK_SIZE;
if (left_to_read < MAX_READ_WRITE_BLOCK_SIZE) read_size = left_to_read;
// Read and write
char* buff = malloc(read_size);
fread(buff, 1, read_size, from);
fwrite(buff, 1, read_size, export);
free(buff);
left_to_read -= read_size;
}
fclose(export);
}
int main(int argc, char** argv) {
if (argc != 2) {
printf("Expected 1 argument, got %i\n", (argc - 1));
return 1;
}
FILE* game_dat = fopen(argv[1], "r");
uint32_t idx_table_pos = get_position_of_file_table(game_dat);
uint32_t idx_table_len = read_length_of_file_table(game_dat, idx_table_pos);
uint32_t dir_table_pos = idx_table_pos + (idx_table_len * 0x10) + 8;
uint32_t dir_table_len = read_file_table_len(game_dat, dir_table_pos);
uint32_t nam_table_pos = dir_table_pos + (dir_table_len * 8) + 9;
game_file** files = get_files(game_dat, dir_table_pos, dir_table_len, nam_table_pos);
printf("file table start: %i\n", idx_table_pos);
printf("file table length: %i\n", idx_table_len);
printf("dir table start: %i\n", dir_table_pos);
printf("dir table length: %i\n", dir_table_len);
printf("names table start: %i\n", nam_table_pos);
int data_pos = 0;
char current_dir[MAX_FILENAME_SIZE];
for (int i = 0; i < dir_table_len; i++) {
game_file* file = files[i];
if (is_directory(file)) {
sprintf(current_dir, "./export/%s", file->name);
mkdir(current_dir, 0700);
continue;
}
data_block* data = read_file_table(game_dat, idx_table_pos, data_pos++);
char filename[512];
sprintf(filename, "%s/%s", current_dir, file->name);
export_data(game_dat, data, filename);
free(file->meta);
free(file->name);
free(file);
free(data);
}
free(files);
close(game_dat);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment