pso_gc_tools/gen_qst_header/main.c

211 lines
5.6 KiB
C
Raw Normal View History

2021-03-20 11:32:30 -04:00
#include <stdio.h>
#include <stdint.h>
#include <malloc.h>
#include <string.h>
#include <iconv.h>
#include <sylverant/prs.h>
typedef struct __attribute__((packed)) {
uint32_t object_code_offset;
uint32_t function_offset_table_offset;
uint32_t bin_size;
uint32_t xffffffff;
uint16_t language;
uint16_t quest_number;
char name[32];
char short_description[128];
char long_description[288];
} QUEST_BIN_HEADER;
typedef struct __attribute__((packed)) {
uint8_t pkt_id;
uint8_t pkt_flags;
uint16_t pkt_size;
char name[32];
uint16_t unused;
uint16_t flags;
char filename[16];
uint32_t size;
} QST_HEADER;
int sjis_to_utf8(char *s, size_t length) {
iconv_t conv;
size_t in_size, out_size;
char *outbuf = malloc(length);
in_size = length;
out_size = length;
char *in = s;
char *out = outbuf;
conv = iconv_open("SHIFT_JIS", "UTF-8");
iconv(conv, &in, &in_size, &out, &out_size);
iconv_close(conv);
memset(s, 0, length);
memcpy(s, outbuf, length);
free(outbuf);
return 0;
}
int get_filesize(const char *filename, size_t *out_size) {
FILE *fp = fopen(filename, "rb");
if (!fp)
return 1;
fseek(fp, 0, SEEK_END);
*out_size = ftell(fp);
fclose(fp);
return 0;
}
const char* path_to_filename(const char *path) {
const char *pos = strrchr(path, '/');
if (pos) {
return pos+1;
} else {
return path;
}
}
char* append_file_extension(const char *filename, const char *extension) {
char *new_filename = malloc(strlen(filename) + strlen(extension));
strcpy(new_filename, filename);
strcat(new_filename, extension);
return new_filename;
}
void generate_qst_header(const char *src_file, size_t src_file_size, QUEST_BIN_HEADER *bin_header, QST_HEADER *out_header) {
memset(out_header, 0, sizeof(QST_HEADER));
// 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)
out_header->pkt_id = 0xa6;
out_header->pkt_size = sizeof(QST_HEADER);
// khyller sets .dat header value to 0xC9, .bin header value to 0x88
// newserv sets both to 0x00
// sylverant appears to set it differently per quest, logic/reasoning is unknown to me
// ... so, this value is probably unimportant
out_header->pkt_flags = 0;
// khyller sets .dat header value to 0x02, .bin header value to 0x00
// newserv sets both to 0x02
// sylverant sets both to 0x00
// ... and so, this value is also probably unimportant
out_header->flags = 0;
out_header->size = src_file_size;
memcpy(out_header->name, bin_header->name, sizeof(out_header->name));
memcpy(out_header->filename, src_file, strlen(src_file));
}
int write_qst_header(const char *src_file, QST_HEADER *header) {
char *header_file = append_file_extension(src_file, ".hdr");
FILE *fp = fopen(header_file, "wb");
if (!fp) {
printf("Error creating header file: %s\n", header_file);
free(header_file);
return 1;
}
fwrite(header, sizeof(QST_HEADER), 1, fp);
fclose(fp);
free(header_file);
}
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("Usage: gen_qst_header quest.bin quest.dat\n");
return 1;
}
const char *bin_file = argv[1];
const char *dat_file = argv[2];
const char *bin_base_filename = path_to_filename(bin_file);
if (strlen(bin_base_filename) > 16) {
printf("Bin filename is too long to fit in a QST header. Maximum length is 16 including file extension.\n");
return 1;
}
const char *dat_base_filename = path_to_filename(dat_file);
if (strlen(dat_base_filename) > 16) {
printf("Dat filename is too long to fit in a QST header. Maximum length is 16 including file extension.\n");
return 1;
}
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;
}
if (get_filesize(dat_file, &dat_compressed_size)) {
printf("Error getting size of dat file: %s\n", dat_file);
return 1;
}
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;
}
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;
}
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;
}
printf("Quest: id=%d, language=0x%04x, name=%s\n", bin_header->quest_number, bin_header->language, bin_header->name);
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);
if (write_qst_header(bin_file, &qst_bin_header)) {
return 1;
}
if (write_qst_header(dat_file, &qst_dat_header)) {
return 1;
}
free(bin_data);
free(dat_data);
return 0;
}