numerous cleanups

This commit is contained in:
Gered 2021-03-24 16:30:32 -04:00
parent 00c52cc6be
commit b4d661b6ee
7 changed files with 164 additions and 132 deletions

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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,

View file

@ -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;
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

View file

@ -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;

View file

@ -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);