add support for decoding download QST files

This commit is contained in:
Martin Michelsen
2023-02-10 10:51:13 -08:00
parent 3bb061951d
commit 95c1b4b6e8
2 changed files with 32 additions and 10 deletions
+2 -1
View File
@@ -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)*
+30 -9
View File
@@ -859,6 +859,7 @@ static pair<string, string> 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<string, string> decode_qst_t(FILE* f) {
}
const auto& header = r.get<HeaderT>();
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<string, string> 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<string, string> 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<string, string> 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<be_uint32_t>(f.get());
fseek(f.get(), 0, SEEK_SET);
if (signature == 0x58004400) {
if (signature == 0x58004400 || signature == 0x5800A600) {
return decode_qst_t<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(f.get());
} else if ((signature & 0xFF00FFFF) == 0x3C004400) {
} else if ((signature & 0xFFFFFF00) == 0x3C004400 || (signature & 0xFFFFFF00) == 0x3C00A600) {
return decode_qst_t<PSOCommandHeaderPC, S_OpenFile_PC_V3_44_A6>(f.get());
} else if ((signature & 0xFF00FFFF) == 0x44003C00) {
} else if ((signature & 0xFF00FFFF) == 0x44003C00 || (signature & 0xFF00FFFF) == 0xA6003C00) {
return decode_qst_t<PSOCommandHeaderDCV3, S_OpenFile_PC_V3_44_A6>(f.get());
} else {
throw runtime_error("invalid qst file format");