From 95c1b4b6e8677a65168c20aab34ac8c49727466f Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Fri, 10 Feb 2023 10:51:13 -0800 Subject: [PATCH] add support for decoding download QST files --- README.md | 3 ++- src/Quest.cc | 39 ++++++++++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 49aa69b3..fbcecbcb 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,8 @@ There are multiple PSO quest formats out there; newserv supports most of them. I | Ep3 GCI | .bin.gci or .mnm.gci | Download only | decode-gci | | Encrypted DLQ | .bin.dlq and .dat.dlq | Yes | decode-dlq | | Ep3 DLQ | .bin.dlq or .mnm.dlq | Download only | decode-dlq | -| QST | .qst | Yes | decode-qst | +| Online QST | .qst | Yes | decode-qst | +| Download QST | .qst | Yes | decode-qst | *Notes:* 1. *This is the default format. You can convert these to uncompressed format by running `newserv decompress-prs FILENAME.bin FILENAME.bind` (and similarly for .dat -> .datd)* diff --git a/src/Quest.cc b/src/Quest.cc index 5919c532..b101e6d3 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -859,6 +859,7 @@ static pair decode_qst_t(FILE* f) { string internal_dat_filename; uint32_t bin_file_size = 0; uint32_t dat_file_size = 0; + Quest::FileFormat subformat = Quest::FileFormat::QST; // Stand-in for unknown while (!r.eof()) { // Handle BB's implicit 8-byte command alignment static constexpr size_t alignment = sizeof(HeaderT); @@ -869,7 +870,22 @@ static pair decode_qst_t(FILE* f) { } const auto& header = r.get(); - if (header.command == 0x44) { + + if (header.command == 0x44 || header.command == 0x13) { + if (subformat == Quest::FileFormat::QST) { + subformat = Quest::FileFormat::BIN_DAT; + } else if (subformat != Quest::FileFormat::BIN_DAT) { + throw runtime_error("QST file contains mixed download and non-download commands"); + } + } else if (header.command == 0xA6 || header.command == 0xA7) { + if (subformat == Quest::FileFormat::QST) { + subformat = Quest::FileFormat::BIN_DAT_DLQ; + } else if (subformat != Quest::FileFormat::BIN_DAT_DLQ) { + throw runtime_error("QST file contains mixed download and non-download commands"); + } + } + + if (header.command == 0x44 || header.command == 0xA6) { if (header.size != sizeof(HeaderT) + sizeof(OpenFileT)) { throw runtime_error("qst open file command has incorrect size"); } @@ -896,7 +912,7 @@ static pair decode_qst_t(FILE* f) { throw runtime_error("qst contains non-bin, non-dat file"); } - } else if (header.command == 0x13) { + } else if (header.command == 0x13 || header.command == 0xA7) { if (header.size != sizeof(HeaderT) + sizeof(S_WriteFile_13_A7)) { throw runtime_error("qst write file command has incorrect size"); } @@ -935,25 +951,30 @@ static pair decode_qst_t(FILE* f) { throw runtime_error("dat file does not match expected size"); } + if (subformat == Quest::FileFormat::BIN_DAT_DLQ) { + bin_contents = Quest::decode_dlq(bin_contents); + dat_contents = Quest::decode_dlq(dat_contents); + } + return make_pair(bin_contents, dat_contents); } pair Quest::decode_qst(const string& filename) { auto f = fopen_unique(filename, "rb"); - // qst files start with an open file command, but the format differs depending + // QST files start with an open file command, but the format differs depending // on the PSO version that the qst file is for. We can detect the format from // the first 4 bytes in the file: - // - BB: 58 00 44 00 - // - PC: 3C ?? 44 00 - // - DC/V3: 44 ?? 3C 00 + // - BB: 58 00 44 00 or 58 00 A6 00 + // - PC: 3C 00 44 ?? or 3C 00 A6 ?? + // - DC/V3: 44 ?? 3C 00 or A6 ?? 3C 00 uint32_t signature = freadx(f.get()); fseek(f.get(), 0, SEEK_SET); - if (signature == 0x58004400) { + if (signature == 0x58004400 || signature == 0x5800A600) { return decode_qst_t(f.get()); - } else if ((signature & 0xFF00FFFF) == 0x3C004400) { + } else if ((signature & 0xFFFFFF00) == 0x3C004400 || (signature & 0xFFFFFF00) == 0x3C00A600) { return decode_qst_t(f.get()); - } else if ((signature & 0xFF00FFFF) == 0x44003C00) { + } else if ((signature & 0xFF00FFFF) == 0x44003C00 || (signature & 0xFF00FFFF) == 0xA6003C00) { return decode_qst_t(f.get()); } else { throw runtime_error("invalid qst file format");