diff --git a/bindat_to_gcdl.c b/bindat_to_gcdl.c index be75b8e..ac28e60 100644 --- a/bindat_to_gcdl.c +++ b/bindat_to_gcdl.c @@ -15,6 +15,14 @@ #include "utils.h" int main(int argc, char *argv[]) { + int returncode, validation_result; + uint8_t *compressed_bin = NULL; + uint8_t *compressed_dat = NULL; + uint8_t *decompressed_bin = NULL; + uint8_t *decompressed_dat = NULL; + uint8_t *final_bin = NULL; + uint8_t *final_dat = NULL; + if (argc != 4) { printf("Usage: bindat_to_gcdl quest.bin quest.dat output.qst\n"); return 1; @@ -29,88 +37,95 @@ int main(int argc, char *argv[]) { /** validate lengths of the given quest .bin and .dat files, to make sure they fit into the packet structs **/ const char *bin_base_filename = path_to_filename(bin_filename); - if (strlen(bin_base_filename) > 16) { + if (strlen(bin_base_filename) > QUEST_FILENAME_MAX_LENGTH) { printf("Bin filename is too long to fit in a QST file header. Maximum length is 16 including file extension.\n"); - return 1; + goto error; } const char *dat_base_filename = path_to_filename(dat_filename); - if (strlen(dat_base_filename) > 16) { + if (strlen(dat_base_filename) > QUEST_FILENAME_MAX_LENGTH) { printf("Dat filename is too long to fit in a QST file header. Maximum length is 16 including file extension.\n"); - return 1; + goto error; } /** read in given quest .bin and .dat files **/ - uint8_t *compressed_bin, *compressed_dat; uint32_t compressed_bin_size, compressed_dat_size; - if (read_file(bin_filename, &compressed_bin, &compressed_bin_size)) { - printf("Error reading bin file: %s\n", bin_filename); - return 1; + printf("Reading quest .bin file %s ...\n", bin_filename); + returncode = read_file(bin_filename, &compressed_bin, &compressed_bin_size); + if (returncode) { + printf("Error code %d (%s) reading bin file: %s\n", returncode, get_error_message(returncode), bin_filename); + goto error; } - if (read_file(dat_filename, &compressed_dat, &compressed_dat_size)) { - printf("Error reading dat file: %s\n", dat_filename); - return 1; + + printf("Reading quest .dat file %s ...\n", dat_filename); + returncode = read_file(dat_filename, &compressed_dat, &compressed_dat_size); + if (returncode) { + printf("Error code %d (%s) reading dat file: %s\n", returncode, get_error_message(returncode), dat_filename); + goto error; } - /** prs decompress the .bin file. store the prs decompressed data sizes for both the .bin and .dat files **/ - - uint8_t *decompressed_bin; - uint32_t decompressed_bin_size, decompressed_dat_size; + /** prs decompress the .bin file, parse out it's header and validate it **/ + printf("Decompressing and validating .bin file ...\n"); + uint32_t decompressed_bin_size; result = fuzziqer_prs_decompress_buf(compressed_bin, &decompressed_bin, compressed_bin_size); if (result < 0) { - printf("prs_decompress_buf() error %d with bin file data: %s\n", result, bin_filename); - return 1; + printf("Error code %d decompressing .dat data.\n", result); + goto error; } decompressed_bin_size = (uint32_t)result; - result = fuzziqer_prs_decompress_size(compressed_dat, compressed_dat_size); - if (result < 0) { - printf("prs_decompress_size() error %d with dat file data: %s\n", result, dat_filename); - return 1; - } - decompressed_dat_size = (uint32_t)result; - - - /** parse quest .bin header from decompressed .bin file data. also set the "download" flag in the .bin header **/ - QUEST_BIN_HEADER *bin_header = (QUEST_BIN_HEADER*)decompressed_bin; + validation_result = validate_quest_bin(bin_header, decompressed_bin_size, true); + if (validation_result) { + printf("Aborting due to invalid quest .bin data.\n"); + goto error; + } - // TODO: validations might need tweaking ... - if (bin_header->object_code_offset != 468) { - printf("Quest bin file invalid (unexpected object_code_offset = %d).\n", bin_header->object_code_offset); - return 1; + + /** prs decompress the .dat file and validate it **/ + printf("Decompressing and validating .dat file ...\n"); + + uint32_t decompressed_dat_size; + result = fuzziqer_prs_decompress_buf(compressed_dat, &decompressed_dat, compressed_dat_size); + if (result < 0) { + printf("Error code %d decompressing .dat data.\n", result); + goto error; } - if (bin_header->bin_size != decompressed_bin_size) { - printf("Quest bin file invalid (decompressed size does not match header bin_size value: %d).\n", bin_header->bin_size); - return 1; - } - if (strlen(bin_header->name) == 0) { - printf("Quest bin file invalid or missing quest name.\n"); - return 1; - } - if (bin_header->quest_number == 0) { - printf("Quest bin file invalid (quest_number is zero).\n"); - return 1; + decompressed_dat_size = result; + + validation_result = validate_quest_dat(decompressed_dat, decompressed_dat_size, true); + if (validation_result != QUESTDAT_ERROR_EOF_EMPTY_TABLE) { + printf("Aborting due to invalid quest .dat data.\n"); + goto error; } + + printf("Quest: id=%d (%d), episode=%d, download=%d, unknown=0x%02x, name=\"%s\", compressed_bin_size=%d, compressed_dat_size=%d\n", + bin_header->quest_number_byte, + bin_header->quest_number_word, + bin_header->episode+1, + bin_header->download, + bin_header->unknown, + bin_header->name, + compressed_bin_size, + compressed_dat_size); + + + /** set the "download" flag in the .bin header and then re-compress the .bin data **/ + printf("Setting .bin header 'download' flag and re-compressing .bin file data ...\n"); + bin_header->download = 1; // gamecube pso client will not find quests on a memory card if this is not set! - printf("Quest: id=%d, download=%d, language=0x%02x, name=%s\n", bin_header->quest_number, bin_header->download, bin_header->language, bin_header->name); - - - /** re-compress bin data, so it includes our modified header "download" flag **/ - uint8_t *recompressed_bin; - result = fuzziqer_prs_compress(decompressed_bin, &recompressed_bin, decompressed_bin_size); if (result < 0) { - printf("prs_compress() error %d with modified bin file data: %s\n", result, bin_filename); - return 1; + printf("Error code %d re-compressing .bin file data.\n", result); + goto error; } // overwrite old compressed bin data, since we don't need it anymore @@ -121,11 +136,12 @@ int main(int argc, char *argv[]) { /** encrypt compressed .bin and .dat file data, using PC crypt method with randomly generated crypt key. prefix unencrypted download quest chunks header to prs compressed + encrypted .bin and .dat file data. **/ + printf("Preparing final .qst file data ... \n"); srand(time(NULL)); uint32_t final_bin_size = compressed_bin_size + sizeof(DOWNLOAD_QUEST_CHUNKS_HEADER); - uint8_t *final_bin = malloc(final_bin_size); + final_bin = malloc(final_bin_size); memset(final_bin, 0, final_bin_size); uint8_t *crypt_compressed_bin = final_bin + sizeof(DOWNLOAD_QUEST_CHUNKS_HEADER); DOWNLOAD_QUEST_CHUNKS_HEADER *bin_dlchunks_header = (DOWNLOAD_QUEST_CHUNKS_HEADER*)final_bin; @@ -134,7 +150,7 @@ int main(int argc, char *argv[]) { memcpy(crypt_compressed_bin, compressed_bin, compressed_bin_size); uint32_t final_dat_size = compressed_dat_size + sizeof(DOWNLOAD_QUEST_CHUNKS_HEADER); - uint8_t *final_dat = malloc(final_dat_size); + final_dat = malloc(final_dat_size); memset(final_dat, 0, final_dat_size); uint8_t *crypt_compressed_dat = final_dat + sizeof(DOWNLOAD_QUEST_CHUNKS_HEADER); DOWNLOAD_QUEST_CHUNKS_HEADER *dat_dlchunks_header = (DOWNLOAD_QUEST_CHUNKS_HEADER*)final_dat; @@ -162,11 +178,12 @@ int main(int argc, char *argv[]) { /** write out the .qst file. chunk data is written out as interleaved 0xA7 packets containing 1024 bytes each */ + printf("Writing out %s ...\n", output_qst_filename); FILE *fp = fopen(output_qst_filename, "wb"); if (!fp) { printf("Error creating output .qst file: %s\n", output_qst_filename); - return 1; + goto error; } fwrite(&qst_bin_header, sizeof(qst_bin_header), 1, fp); @@ -210,11 +227,15 @@ int main(int argc, char *argv[]) { fclose(fp); + returncode = 0; + goto quit; +error: + returncode = 1; +quit: free(decompressed_bin); free(final_bin); free(final_dat); free(compressed_bin); free(compressed_dat); - - return 0; + return returncode; } diff --git a/gci_extract.c b/gci_extract.c index 69b3ad4..138ee5b 100644 --- a/gci_extract.c +++ b/gci_extract.c @@ -162,7 +162,8 @@ int main(int argc, char *argv[]) { /** decompress loaded quest .bin data and validate it **/ printf("Validating quest .bin data ...\n"); - result = prs_decompress_buf(bin_data, &decompressed_bin_data, bin_data_size); + //result = prs_decompress_buf(bin_data, &decompressed_bin_data, bin_data_size); + result = fuzziqer_prs_decompress_buf(bin_data, &decompressed_bin_data, bin_data_size); if (result < 0) { printf("Error code %d decompressing .bin data.\n", result); goto error; @@ -247,7 +248,7 @@ int main(int argc, char *argv[]) { printf("Writing compressed quest .bin data to %s ...\n", out_filename); result = write_file(out_filename, bin_data, bin_data_size); if (result) { - printf("Error code %d writing out file.\n", result); + printf("Error code %d writing out file: %s\n", result, get_error_message(result)); goto error; } @@ -262,7 +263,7 @@ int main(int argc, char *argv[]) { printf("Writing compressed quest .dat data to %s ...\n", out_filename); result = write_file(out_filename, dat_data, dat_data_size); if (result) { - printf("Error code %d writing out file.\n", result); + printf("Error code %d writing out file: %s\n", result, get_error_message(result)); goto error; } diff --git a/gen_qst_header.c b/gen_qst_header.c index 595c729..fdbfc95 100644 --- a/gen_qst_header.c +++ b/gen_qst_header.c @@ -9,18 +9,13 @@ #include "quests.h" #include "textconv.h" -int write_qst_header(const char *filename, const QST_HEADER *header) { - FILE *fp = fopen(filename, "wb"); - if (!fp) - return ERROR_CREATING_FILE; - - fwrite(header, sizeof(QST_HEADER), 1, fp); - fclose(fp); - - return SUCCESS; -} - int main(int argc, char *argv[]) { + int returncode, validation_result; + uint8_t *bin_data = NULL; + uint8_t *dat_data = NULL; + char *bin_hdr_file = NULL; + char *dat_hdr_file = NULL; + if (argc != 3) { printf("Usage: gen_qst_header quest.bin quest.dat\n"); return 1; @@ -30,85 +25,93 @@ int main(int argc, char *argv[]) { const char *dat_file = argv[2]; const char *bin_base_filename = path_to_filename(bin_file); - if (strlen(bin_base_filename) > 16) { + if (strlen(bin_base_filename) > QUEST_FILENAME_MAX_LENGTH) { printf("Bin filename is too long to fit in a QST header. Maximum length is 16 including file extension.\n"); - return 1; + goto error; } - const char *dat_base_filename = path_to_filename(dat_file); - if (strlen(dat_base_filename) > 16) { + if (strlen(dat_base_filename) > QUEST_FILENAME_MAX_LENGTH) { printf("Dat filename is too long to fit in a QST header. Maximum length is 16 including file extension.\n"); - return 1; + goto error; } size_t bin_compressed_size, dat_compressed_size; - if (get_filesize(bin_file, &bin_compressed_size)) { - printf("Error getting size of bin file: %s\n", bin_file); - return 1; + returncode = get_filesize(bin_file, &bin_compressed_size); + if (returncode) { + printf("Error code %d (%s) getting size of bin file: %s\n", returncode, get_error_message(returncode), bin_file); + goto error; } - if (get_filesize(dat_file, &dat_compressed_size)) { - printf("Error getting size of dat file: %s\n", dat_file); - return 1; + returncode = get_filesize(dat_file, &dat_compressed_size); + if (returncode) { + printf("Error code %d (%s) getting size of dat file: %s\n", returncode, get_error_message(returncode), dat_file); + goto error; } - uint8_t *bin_data; + size_t bin_decompressed_size = prs_decompress_file(bin_file, &bin_data); if (bin_decompressed_size < 0) { printf("Error opening and decompressing bin file: %s\n", bin_file); - return 1; + goto error; } - uint8_t *dat_data; size_t dat_decompressed_size = prs_decompress_file(dat_file, &dat_data); if (dat_decompressed_size < 0) { printf("Error opening and decompressing dat file: %s\n", dat_file); - return 1; + goto error; } QUEST_BIN_HEADER *bin_header = (QUEST_BIN_HEADER*)bin_data; - - sjis_to_utf8(bin_header->name, sizeof(bin_header->name)); - sjis_to_utf8(bin_header->short_description, sizeof(bin_header->short_description)); - sjis_to_utf8(bin_header->long_description, sizeof(bin_header->long_description)); - - if (bin_header->object_code_offset != 468) { - printf("Quest bin file invalid (unexpected object_code_offset = %d).\n", bin_header->object_code_offset); - return 1; - } - if (bin_header->bin_size != bin_decompressed_size) { - printf("Quest bin file invalid (decompressed size does not match bin_size value: %d).\n", bin_header->bin_size); - return 1; - } - if (strlen(bin_header->name) == 0) { - printf("Quest bin file invalid or missing quest name.\n"); - return 1; - } - if (bin_header->quest_number == 0) { - printf("Quest bin file invalid (quest_number is zero?).\n"); - return 1; + validation_result = validate_quest_bin(bin_header, bin_decompressed_size, true); + if (validation_result) { + printf("Aborting due to invalid quest .bin data.\n"); + goto error; } - printf("Quest: id=%d, language=0x%04x, name=%s\n", bin_header->quest_number, bin_header->language, bin_header->name); + //sjis_to_utf8(bin_header->name, sizeof(bin_header->name)); + //sjis_to_utf8(bin_header->short_description, sizeof(bin_header->short_description)); + //sjis_to_utf8(bin_header->long_description, sizeof(bin_header->long_description)); + + printf("Quest: id=%d (%d), episode=%d, download=%d, unknown=0x%02x, name=\"%s\", compressed_bin_size=%ld, compressed_dat_size=%ld\n", + bin_header->quest_number_byte, + bin_header->quest_number_word, + bin_header->episode+1, + bin_header->download, + bin_header->unknown, + bin_header->name, + bin_compressed_size, + dat_compressed_size); + QST_HEADER qst_bin_header, qst_dat_header; - generate_qst_header(bin_base_filename, bin_compressed_size, bin_header, &qst_bin_header); generate_qst_header(dat_base_filename, dat_compressed_size, bin_header, &qst_dat_header); - char *bin_hdr_file = append_string(bin_file, ".hdr"); - char *dat_hdr_file = append_string(dat_file, ".hdr"); + bin_hdr_file = append_string(bin_file, ".hdr"); + dat_hdr_file = append_string(dat_file, ".hdr"); - if (write_qst_header(bin_hdr_file, &qst_bin_header)) { - return 1; - } - if (write_qst_header(dat_hdr_file, &qst_dat_header)) { - return 1; + returncode = write_file(bin_hdr_file, &qst_bin_header, sizeof(QST_HEADER)); + if (returncode) { + printf("Error code %d (%s) writing out bin header file: %s\n", returncode, get_error_message(returncode), bin_hdr_file); + goto error; } + returncode = write_file(dat_hdr_file, &qst_dat_header, sizeof(QST_HEADER)); + if (returncode) { + printf("Error code %d (%s) writing out dat header file: %s\n", returncode, get_error_message(returncode), dat_hdr_file); + goto error; + } + + + returncode = 0; + goto quit; +error: + returncode = 1; +quit: + free(bin_hdr_file); + free(dat_hdr_file); free(bin_data); free(dat_data); - - return 0; + return returncode; } diff --git a/quests.c b/quests.c index 2a93355..1a88fb9 100644 --- a/quests.c +++ b/quests.c @@ -5,7 +5,7 @@ #include "retvals.h" #include "quests.h" -int generate_qst_header(const char *src_file, size_t src_file_size, QUEST_BIN_HEADER *bin_header, QST_HEADER *out_header) { +int generate_qst_header(const char *src_file, size_t src_file_size, const QUEST_BIN_HEADER *bin_header, QST_HEADER *out_header) { if (!src_file || !bin_header || !out_header) return ERROR_INVALID_PARAMS; @@ -39,7 +39,7 @@ int generate_qst_data_chunk(const char *base_filename, uint8_t counter, const ui return SUCCESS; } -int validate_quest_bin(QUEST_BIN_HEADER *header, uint32_t length, bool print_errors) { +int validate_quest_bin(const QUEST_BIN_HEADER *header, uint32_t length, bool print_errors) { int result = 0; // TODO: validations might need tweaking ... @@ -71,7 +71,7 @@ int validate_quest_bin(QUEST_BIN_HEADER *header, uint32_t length, bool print_err return result; } -int validate_quest_dat(uint8_t *data, uint32_t length, bool print_errors) { +int validate_quest_dat(const uint8_t *data, uint32_t length, bool print_errors) { int result = 0; int table_index = 0; @@ -92,15 +92,15 @@ int validate_quest_dat(uint8_t *data, uint32_t length, bool print_errors) { // all zeros seems to be used to indicate end of file ??? if ((offset + sizeof(QUEST_DAT_TABLE_HEADER)) == length) { if (print_errors) - printf("Quest dat file issue: empty table encountered at end of file\n"); + printf("Quest dat file warning: empty table encountered at end of file (probably normal?)\n"); result |= QUESTDAT_ERROR_EOF_EMPTY_TABLE; } else { if (print_errors) - printf("Quest dat file issue: empty table encountered at table index %d\n", table_index); + printf("Quest dat file warning: empty table encountered at table index %d\n", table_index); result |= QUESTDAT_ERROR_EMPTY_TABLE; } - } else if (table_header->table_size == (table_header->table_body_size - 16)) { + } else if (table_header->table_size == (table_header->table_body_size - sizeof(QUEST_DAT_TABLE_HEADER))) { if (print_errors) printf("Quest dat file issue: mismatching table_size (%d) and table_body_size (%d) found in table index %d\n", table_header->table_size, diff --git a/quests.h b/quests.h index 56ed7cc..624d37b 100644 --- a/quests.h +++ b/quests.h @@ -23,13 +23,15 @@ #define PACKET_ID_QUEST_CHUNK_ONLINE 0x13 #define PACKET_ID_QUEST_CHUNK_DOWNLOAD 0xa7 -// quest .bin file header (after file contents have been prs-decompressed) +#define QUEST_FILENAME_MAX_LENGTH 16 + +// decompressed quest .bin file header typedef struct _PACKED_ { uint32_t object_code_offset; uint32_t function_offset_table_offset; uint32_t bin_size; - uint32_t xffffffff; // always 0xffffffff ? - uint8_t download; + uint32_t xffffffff; // always 0xffffffff ? + uint8_t download; // must be '1' to be usable as an offline quest (played from memory card) // have seen some projects define this field as language. "newserv" just calls it unknown? i've seen multiple // values present for english language quests ... @@ -58,6 +60,7 @@ typedef struct _PACKED_ { char long_description[288]; } QUEST_BIN_HEADER; +// decompressed quest .dat file table header typedef struct _PACKED_ { uint32_t type; uint32_t table_size; @@ -65,7 +68,7 @@ typedef struct _PACKED_ { uint32_t table_body_size; } QUEST_DAT_TABLE_HEADER; -// .qst file header, for either the embedded bin or dat quest data +// .qst file header, for either the embedded bin or dat quest data (there should be two of these per .qst file). typedef struct _PACKED_ { // 0xA6 = download to memcard, 0x44 = download for online play // (quest file data chunks must then be encoded accordingly. 0xA6 = use 0xA7, and 0x44 = use 0x13) @@ -87,27 +90,31 @@ typedef struct _PACKED_ { // ... and so, this value is also probably unimportant? uint16_t flags; - char filename[16]; + char filename[QUEST_FILENAME_MAX_LENGTH]; uint32_t size; } QST_HEADER; +// .qst raw .bin/.dat file data packet. the original .bin/.dat file data is broken down into as many of these structs +// as is necessary to fit into the resulting .qst file typedef struct _PACKED_ { uint8_t pkt_id; uint8_t pkt_flags; uint16_t pkt_size; - char filename[16]; + char filename[QUEST_FILENAME_MAX_LENGTH]; uint8_t data[1024]; uint32_t size; } QST_DATA_CHUNK; +// for download/offline .qst files only. the raw .bin/.dat file data needs to be prefixed with one of these structs +// before being turned into QST_DATA_CHUNKs. only one of these is needed per each .bin/.dat file. typedef struct _PACKED_ { uint32_t decompressed_size; uint32_t crypt_key; } DOWNLOAD_QUEST_CHUNKS_HEADER; -int generate_qst_header(const char *src_file, size_t src_file_size, QUEST_BIN_HEADER *bin_header, QST_HEADER *out_header); +int generate_qst_header(const char *src_file, size_t src_file_size, const QUEST_BIN_HEADER *bin_header, QST_HEADER *out_header); int generate_qst_data_chunk(const char *base_filename, uint8_t counter, const uint8_t *src, uint32_t size, QST_DATA_CHUNK *out_chunk); -int validate_quest_bin(QUEST_BIN_HEADER *header, uint32_t length, bool print_errors); -int validate_quest_dat(uint8_t *data, uint32_t length, bool print_errors); +int validate_quest_bin(const QUEST_BIN_HEADER *header, uint32_t length, bool print_errors); +int validate_quest_dat(const uint8_t *data, uint32_t length, bool print_errors); #endif diff --git a/utils.c b/utils.c index b49cd0a..b4c6c01 100644 --- a/utils.c +++ b/utils.c @@ -49,7 +49,7 @@ int read_file(const char *filename, uint8_t** out_file_data, uint32_t *out_file_ return SUCCESS; } -int write_file(const char *filename, const uint8_t *data, size_t size) { +int write_file(const char *filename, const void *data, size_t size) { if (!filename || !data || size == 0) return ERROR_INVALID_PARAMS; diff --git a/utils.h b/utils.h index dd45a65..68f86ab 100644 --- a/utils.h +++ b/utils.h @@ -6,7 +6,7 @@ #include "retvals.h" int read_file(const char *filename, uint8_t** out_file_data, uint32_t *out_file_size); -int write_file(const char *filename, const uint8_t *data, size_t size); +int write_file(const char *filename, const void *data, size_t size); int get_filesize(const char *filename, size_t *out_size); const char* path_to_filename(const char *path); char* append_string(const char *a, const char *b);