From 00c52cc6bea861db9f57e673521af9126f4d00e7 Mon Sep 17 00:00:00 2001 From: gered Date: Wed, 24 Mar 2021 14:22:56 -0400 Subject: [PATCH] quest validation improvements/hacks. provide quest id byte/word access it goes without saying that the hacks here for ignoring bin size mismatches are definitely _hacks_ ... --- gci_extract.c | 27 ++++++++++++++------ quests.c | 69 ++++++++++++++++++++++++++++++++++----------------- quests.h | 27 +++++++++++++++++--- 3 files changed, 89 insertions(+), 34 deletions(-) diff --git a/gci_extract.c b/gci_extract.c index 6625c10..69b3ad4 100644 --- a/gci_extract.c +++ b/gci_extract.c @@ -120,7 +120,7 @@ int get_quest_data(const char *filename, uint8_t **dest, uint32_t *dest_size, GC } int main(int argc, char *argv[]) { - int returncode; + int returncode, validation_result; int32_t result; uint8_t *bin_data = NULL; uint8_t *dat_data = NULL; @@ -170,7 +170,18 @@ int main(int argc, char *argv[]) { decompressed_bin_size = result; QUEST_BIN_HEADER *bin_header = (QUEST_BIN_HEADER*)decompressed_bin_data; - if (validate_quest_bin(bin_header, decompressed_bin_size)) { + validation_result = validate_quest_bin(bin_header, decompressed_bin_size, true); + if (validation_result == QUESTBIN_ERROR_SMALLER_BIN_SIZE) { + printf("WARNING: Decompressed .bin data is larger than expected. Proceeding using the smaller .bin header bin_size value ...\n"); + decompressed_bin_size = bin_header->bin_size; + } else if (validation_result == QUESTBIN_ERROR_LARGER_BIN_SIZE) { + if ((decompressed_bin_size + 1) == bin_header->bin_size) { + printf("WARNING: Decompressed .bin data is 1 byte smaller than the .bin header bin_size specifies. Correcting by adding a null byte ...\n"); + ++decompressed_bin_size; + decompressed_bin_data = realloc(decompressed_bin_data, decompressed_bin_size); + decompressed_bin_data[decompressed_bin_size - 1] = 0; + } + } else { printf("Aborting due to invalid quest .bin data.\n"); goto error; } @@ -186,14 +197,16 @@ int main(int argc, char *argv[]) { } decompressed_dat_size = result; - if (validate_quest_dat(decompressed_dat_data, decompressed_dat_size)) { + validation_result = validate_quest_dat(decompressed_dat_data, 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, episode=%d, download=%d, unknown=0x%02x, name=\"%s\", compressed_bin_size=%d, compressed_dat_size=%d\n", - bin_header->quest_number, + 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, @@ -229,7 +242,7 @@ int main(int argc, char *argv[]) { if (out_bin_filename) strncpy(out_filename, out_bin_filename, FILENAME_MAX-1); else - snprintf(out_filename, FILENAME_MAX-1, "q%03de%01d.bin", bin_header->quest_number, bin_header->episode+1); + snprintf(out_filename, FILENAME_MAX-1, "q%03de%01d.bin", bin_header->quest_number_byte, bin_header->episode+1); printf("Writing compressed quest .bin data to %s ...\n", out_filename); result = write_file(out_filename, bin_data, bin_data_size); @@ -244,7 +257,7 @@ int main(int argc, char *argv[]) { if (out_dat_filename) strncpy(out_filename, out_dat_filename, FILENAME_MAX-1); else - snprintf(out_filename, FILENAME_MAX-1, "q%03de%01d.dat", bin_header->quest_number, bin_header->episode+1); + snprintf(out_filename, FILENAME_MAX-1, "q%03de%01d.dat", bin_header->quest_number_byte, bin_header->episode+1); printf("Writing compressed quest .dat data to %s ...\n", out_filename); result = write_file(out_filename, dat_data, dat_data_size); diff --git a/quests.c b/quests.c index 0c0ab21..2a93355 100644 --- a/quests.c +++ b/quests.c @@ -39,57 +39,80 @@ 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) { +int validate_quest_bin(QUEST_BIN_HEADER *header, uint32_t length, bool print_errors) { + int result = 0; + // TODO: validations might need tweaking ... if (header->object_code_offset != 468) { - printf("Quest bin file invalid (unexpected object_code_offset = %d).\n", header->object_code_offset); - return 1; + if (print_errors) + printf("Quest bin file issue: unexpected object_code_offset = %d\n", header->object_code_offset); + result |= QUESTBIN_ERROR_OBJECT_CODE_OFFSET; } - if (header->bin_size != length) { - printf("Quest bin file invalid (decompressed size does not match header bin_size value: %d).\n", header->bin_size); - return 2; + if (header->bin_size < length) { + if (print_errors) + printf("Quest bin file issue: bin_size %d is smaller than the actual decompressed bin size %d\n", header->bin_size, length); + result |= QUESTBIN_ERROR_SMALLER_BIN_SIZE; + } else if (header->bin_size > length) { + if (print_errors) + printf("Quest bin file issue: bin_size %d is larger than the actual decompressed bin size %d\n", header->bin_size, length); + result |= QUESTBIN_ERROR_LARGER_BIN_SIZE; } if (strlen(header->name) == 0) { - printf("Quest bin file invalid or missing quest name.\n"); - return 3; + if (print_errors) + printf("Quest bin file issue: blank quest name\n"); + result |= QUESTBIN_ERROR_NAME; } - if (header->quest_number == 0) { - printf("Quest bin file invalid (quest_number is zero).\n"); - return 4; + if (header->quest_number_word == 0) { + if (print_errors) + printf("Quest bin file issue: quest_number is zero\n"); + result |= QUESTBIN_ERROR_NAME; } - return 0; + return result; } -int validate_quest_dat(uint8_t *data, uint32_t length) { - // TODO: validations might need tweaking ... - if (!data || length == 0) { - // printf("Invalid") - } +int validate_quest_dat(uint8_t *data, uint32_t length, bool print_errors) { + int result = 0; + int table_index = 0; + // TODO: validations might need tweaking ... uint32_t offset = 0; while (offset < length) { QUEST_DAT_TABLE_HEADER *table_header = (QUEST_DAT_TABLE_HEADER*)(data + offset); if (table_header->type > 5) { - printf("Invalid table type value found (type = %d)\n", table_header->type); - return 1; + if (print_errors) + printf("Quest dat file issue: invalid table type value %d found in table index %d\n", table_header->type, table_index); + result |= QUESTDAT_ERROR_TYPE; } if (table_header->type == 0 && table_header->table_size == 0 && table_header->area == 0 && table_header->table_body_size == 0) { // all zeros seems to be used to indicate end of file ??? - // just ignore this and move on ... + if ((offset + sizeof(QUEST_DAT_TABLE_HEADER)) == length) { + if (print_errors) + printf("Quest dat file issue: empty table encountered at end of file\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); + result |= QUESTDAT_ERROR_EMPTY_TABLE; + } } else if (table_header->table_size == (table_header->table_body_size - 16)) { - printf("Invalid table_body_size found (table_size = %d, table_body_size = %d)\n", table_header->table_size, table_header->table_body_size); - return 2; + 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, + table_header->table_body_size, + table_index); + result |= QUESTDAT_ERROR_TABLE_BODY_SIZE; } offset += sizeof(QUEST_DAT_TABLE_HEADER); offset += table_header->table_body_size; + ++table_index; } - return 0; + return result; } diff --git a/quests.h b/quests.h index 75f0d16..56ed7cc 100644 --- a/quests.h +++ b/quests.h @@ -3,9 +3,21 @@ #include #include +#include #include "defs.h" +#define QUESTBIN_ERROR_OBJECT_CODE_OFFSET 1 +#define QUESTBIN_ERROR_LARGER_BIN_SIZE 2 +#define QUESTBIN_ERROR_SMALLER_BIN_SIZE 4 +#define QUESTBIN_ERROR_NAME 8 +#define QUESTBIN_ERROR_NUMBER 16 + +#define QUESTDAT_ERROR_TYPE 1 +#define QUESTDAT_ERROR_TABLE_BODY_SIZE 2 +#define QUESTDAT_ERROR_EOF_EMPTY_TABLE 4 // more of a warning i guess? maybe this is totally normal? +#define QUESTDAT_ERROR_EMPTY_TABLE 8 + #define PACKET_ID_QUEST_INFO_ONLINE 0x44 #define PACKET_ID_QUEST_INFO_DOWNLOAD 0xa6 #define PACKET_ID_QUEST_CHUNK_ONLINE 0x13 @@ -28,8 +40,15 @@ typedef struct _PACKED_ { // is *probably* better when dealing with non-custom quests. however, some custom quests (which are mostly of // dubious quality anyway) clearly were created using a tool which had quest_number as a 16-bit value ... // ... so .... i dunno! i guess i'll just leave it like this ... - uint8_t quest_number; - uint8_t episode; + union { + struct { + uint8_t quest_number_byte; + uint8_t episode; + }; + struct { + uint16_t quest_number_word; + }; + }; // some sources say these strings are all UTF-16LE, but i'm not sure that is really the case for gamecube data? // for gamecube-format quest .bin files, it instead looks like SHIFT-JIS probably ... ? @@ -88,7 +107,7 @@ typedef struct _PACKED_ { 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_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); -int validate_quest_dat(uint8_t *data, uint32_t length); +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); #endif