add decoder for Ep3 trial download quests
This commit is contained in:
+47
-31
@@ -165,8 +165,7 @@ string find_seed_and_decrypt_download_quest_data_section(
|
|||||||
string result;
|
string result;
|
||||||
uint64_t result_seed = parallel_range<uint64_t>([&](uint64_t seed, size_t) {
|
uint64_t result_seed = parallel_range<uint64_t>([&](uint64_t seed, size_t) {
|
||||||
try {
|
try {
|
||||||
string ret = decrypt_download_quest_data_section<IsBigEndian>(
|
string ret = decrypt_download_quest_data_section<IsBigEndian>(data_section, size, seed);
|
||||||
data_section, size, seed);
|
|
||||||
lock_guard<mutex> g(result_lock);
|
lock_guard<mutex> g(result_lock);
|
||||||
result = std::move(ret);
|
result = std::move(ret);
|
||||||
return true;
|
return true;
|
||||||
@@ -522,36 +521,53 @@ string Quest::decode_gci_file(
|
|||||||
return compressed_data;
|
return compressed_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (header.game_id[2] == 'S') { // Episode 3
|
} else if (header.is_ep3()) {
|
||||||
// The first 0x10 bytes in the data segment appear to be unused. In most
|
if (header.is_trial()) {
|
||||||
// files I've seen, the last half of it (8 bytes) are duplicates of the
|
if (known_seed >= 0) {
|
||||||
// first 8 bytes of the unscrambled, compressed data, though this is the
|
return decrypt_download_quest_data_section<true>(
|
||||||
// result of an uninitialized memory bug when the client encodes the file
|
r.getv(header.data_size), header.data_size, known_seed);
|
||||||
// and not an actual constraint on what should be in these 8 bytes.
|
} else {
|
||||||
r.skip(16);
|
if (find_seed_num_threads < 0) {
|
||||||
// The game treats this field as a 16-byte string (including the \0). The 8
|
throw runtime_error("file is encrypted");
|
||||||
// bytes after it appear to be completely unused.
|
}
|
||||||
if (r.readx(15) != "SONICTEAM,SEGA.") {
|
if (find_seed_num_threads == 0) {
|
||||||
throw runtime_error("Episode 3 GCI file is not a quest");
|
find_seed_num_threads = thread::hardware_concurrency();
|
||||||
|
}
|
||||||
|
return find_seed_and_decrypt_download_quest_data_section<true>(
|
||||||
|
r.getv(header.data_size), header.data_size, find_seed_num_threads);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// The first 0x10 bytes in the data segment appear to be unused. In most
|
||||||
|
// files I've seen, the last half of it (8 bytes) are duplicates of the
|
||||||
|
// first 8 bytes of the unscrambled, compressed data, though this is the
|
||||||
|
// result of an uninitialized memory bug when the client encodes the file
|
||||||
|
// and not an actual constraint on what should be in these 8 bytes.
|
||||||
|
r.skip(16);
|
||||||
|
// The game treats this field as a 16-byte string (including the \0). The 8
|
||||||
|
// bytes after it appear to be completely unused.
|
||||||
|
if (r.readx(15) != "SONICTEAM,SEGA.") {
|
||||||
|
throw runtime_error("Episode 3 GCI file is not a quest");
|
||||||
|
}
|
||||||
|
r.skip(9);
|
||||||
|
|
||||||
|
data = r.readx(header.data_size - 40);
|
||||||
|
|
||||||
|
// For some reason, Sega decided not to encrypt Episode 3 quest files in the
|
||||||
|
// same way as Episodes 1&2 quest files (see above). Instead, they just
|
||||||
|
// wrote a fairly trivial XOR loop over the first 0x100 bytes, leaving the
|
||||||
|
// remaining bytes completely unencrypted (but still compressed).
|
||||||
|
size_t unscramble_size = min<size_t>(0x100, data.size());
|
||||||
|
decrypt_trivial_gci_data(data.data(), unscramble_size, 0);
|
||||||
|
|
||||||
|
size_t decompressed_size = prs_decompress_size(data);
|
||||||
|
if (decompressed_size != sizeof(Episode3::MapDefinition)) {
|
||||||
|
throw runtime_error(string_printf(
|
||||||
|
"decompressed quest is 0x%zX bytes; expected 0x%zX bytes",
|
||||||
|
decompressed_size, sizeof(Episode3::MapDefinition)));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
r.skip(9);
|
|
||||||
|
|
||||||
data = r.readx(header.data_size - 40);
|
|
||||||
|
|
||||||
// For some reason, Sega decided not to encrypt Episode 3 quest files in the
|
|
||||||
// same way as Episodes 1&2 quest files (see above). Instead, they just
|
|
||||||
// wrote a fairly trivial XOR loop over the first 0x100 bytes, leaving the
|
|
||||||
// remaining bytes completely unencrypted (but still compressed).
|
|
||||||
size_t unscramble_size = min<size_t>(0x100, data.size());
|
|
||||||
decrypt_trivial_gci_data(data.data(), unscramble_size, 0);
|
|
||||||
|
|
||||||
size_t decompressed_size = prs_decompress_size(data);
|
|
||||||
if (decompressed_size != sizeof(Episode3::MapDefinition)) {
|
|
||||||
throw runtime_error(string_printf(
|
|
||||||
"decompressed quest is 0x%zX bytes; expected 0x%zX bytes",
|
|
||||||
decompressed_size, sizeof(Episode3::MapDefinition)));
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("unknown game name in GCI header");
|
throw runtime_error("unknown game name in GCI header");
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ void PSOGCIFileHeader::check() const {
|
|||||||
if (this->developer_id[0] != '8' || this->developer_id[1] != 'P') {
|
if (this->developer_id[0] != '8' || this->developer_id[1] != 'P') {
|
||||||
throw runtime_error("GCI file is not for a Sega game");
|
throw runtime_error("GCI file is not for a Sega game");
|
||||||
}
|
}
|
||||||
if (this->game_id[0] != 'G') {
|
if ((this->game_id[0] != 'G') && (this->game_id[0] != 'D')) {
|
||||||
throw runtime_error("GCI file is not for a GameCube game");
|
throw runtime_error("GCI file is not for a GameCube game");
|
||||||
}
|
}
|
||||||
if (this->game_id[1] != 'P') {
|
if (this->game_id[1] != 'P') {
|
||||||
@@ -106,6 +106,10 @@ bool PSOGCIFileHeader::is_ep3() const {
|
|||||||
return (this->game_id[2] == 'S');
|
return (this->game_id[2] == 'S');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PSOGCIFileHeader::is_trial() const {
|
||||||
|
return (this->game_id[0] == 'D');
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t compute_psogc_timestamp(
|
uint32_t compute_psogc_timestamp(
|
||||||
uint16_t year,
|
uint16_t year,
|
||||||
uint8_t month,
|
uint8_t month,
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ struct PSOGCIFileHeader {
|
|||||||
|
|
||||||
bool is_ep12() const;
|
bool is_ep12() const;
|
||||||
bool is_ep3() const;
|
bool is_ep3() const;
|
||||||
|
bool is_trial() const;
|
||||||
} __attribute__((packed));
|
} __attribute__((packed));
|
||||||
|
|
||||||
struct PSOGCSystemFile {
|
struct PSOGCSystemFile {
|
||||||
|
|||||||
Reference in New Issue
Block a user