Last active
November 17, 2019 15:31
-
-
Save matthinc/7385e0ed6fc2528e184f038ee3e72f0e to your computer and use it in GitHub Desktop.
This file contains 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<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