add learnings from Ep3 Trial Edition download quest

This commit is contained in:
Martin Michelsen
2023-09-28 14:48:26 -07:00
parent 5c5da8e10b
commit fbdfdb085a
11 changed files with 269 additions and 90 deletions
-4
View File
@@ -2831,7 +2831,6 @@ struct S_GameInformation_GC_Ep3_E1 {
/* 0024 */ parray<PlayerEntry, 4> player_entries;
/* 00E4 */ parray<uint8_t, 0x20> unknown_a3;
/* 0104 */ Episode3::Rules rules;
/* 0114 */ parray<uint8_t, 4> unknown_a4;
/* 0118 */ parray<PlayerEntry, 8> spectator_entries;
} __packed__;
@@ -2927,8 +2926,6 @@ struct S_TournamentGameDetails_GC_Ep3_E3 {
/* 0024/034C */ ptext<char, 0x20> map_name;
/* 0044/036C */ Episode3::Rules rules;
/* 0054/037C */ parray<uint8_t, 4> unknown_a1;
// This field is used only if the bracket pane is shown
struct BracketEntry {
le_uint16_t win_count = 0;
@@ -6252,7 +6249,6 @@ struct G_SetPlayerSubstatus_GC_Ep3_6xB5x3C {
struct G_SetTournamentPlayerDecks_GC_Ep3_6xB4x3D {
G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetTournamentPlayerDecks_GC_Ep3_6xB4x3D) / 4, 0, 0x3D, 0, 0, 0};
Episode3::Rules rules;
parray<uint8_t, 4> unknown_a1;
struct Entry {
uint8_t type = 0; // 0 = no player, 1 = human, 2 = COM
ptext<char, 0x10> player_name;
+26 -13
View File
@@ -794,7 +794,8 @@ string prs_compress_indexed(const string& data, ProgressCallback progress_fn) {
return prs_compress_indexed(data.data(), data.size(), progress_fn);
}
PRSDecompressResult prs_decompress_with_meta(const void* data, size_t size, size_t max_output_size) {
PRSDecompressResult prs_decompress_with_meta(
const void* data, size_t size, size_t max_output_size, bool allow_unterminated) {
// PRS is an LZ77-based compression algorithm. Compressed data is split into
// two streams: a control stream and a data stream. The control stream is read
// one bit at a time, and the data stream is read one byte at a time. The
@@ -839,7 +840,11 @@ PRSDecompressResult prs_decompress_with_meta(const void* data, size_t size, size
// Control 1 = literal byte
if (cr.read()) {
if (max_output_size && w.size() == max_output_size) {
throw runtime_error("maximum output size exceeded");
if (allow_unterminated) {
return {std::move(w.str()), r.where()};
} else {
throw runtime_error("maximum output size exceeded");
}
}
w.put_u8(r.get_u8());
@@ -882,7 +887,11 @@ PRSDecompressResult prs_decompress_with_meta(const void* data, size_t size, size
}
for (size_t z = 0; z < count; z++) {
if (max_output_size && w.size() == max_output_size) {
throw runtime_error("maximum output size exceeded");
if (allow_unterminated) {
return {std::move(w.str()), r.where()};
} else {
throw out_of_range("maximum output size exceeded");
}
}
w.put_u8(w.str()[read_offset + z]);
}
@@ -892,21 +901,21 @@ PRSDecompressResult prs_decompress_with_meta(const void* data, size_t size, size
return {std::move(w.str()), r.where()};
}
PRSDecompressResult prs_decompress_with_meta(const string& data, size_t max_output_size) {
return prs_decompress_with_meta(data.data(), data.size(), max_output_size);
PRSDecompressResult prs_decompress_with_meta(const string& data, size_t max_output_size, bool allow_unterminated) {
return prs_decompress_with_meta(data.data(), data.size(), max_output_size, allow_unterminated);
}
string prs_decompress(const void* data, size_t size, size_t max_output_size) {
auto ret = prs_decompress_with_meta(data, size, max_output_size);
string prs_decompress(const void* data, size_t size, size_t max_output_size, bool allow_unterminated) {
auto ret = prs_decompress_with_meta(data, size, max_output_size, allow_unterminated);
return std::move(ret.data);
}
string prs_decompress(const string& data, size_t max_output_size) {
auto ret = prs_decompress_with_meta(data.data(), data.size(), max_output_size);
string prs_decompress(const string& data, size_t max_output_size, bool allow_unterminated) {
auto ret = prs_decompress_with_meta(data.data(), data.size(), max_output_size, allow_unterminated);
return std::move(ret.data);
}
size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size) {
size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size, bool allow_unterminated) {
size_t ret = 0;
StringReader r(data, size);
ControlStreamReader cr(r);
@@ -943,15 +952,19 @@ size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size
}
if (max_output_size && ret > max_output_size) {
throw runtime_error("maximum output size exceeded");
if (allow_unterminated) {
return max_output_size;
} else {
throw out_of_range("maximum output size exceeded");
}
}
}
return ret;
}
size_t prs_decompress_size(const string& data, size_t max_output_size) {
return prs_decompress_size(data.data(), data.size(), max_output_size);
size_t prs_decompress_size(const string& data, size_t max_output_size, bool allow_unterminated) {
return prs_decompress_size(data.data(), data.size(), max_output_size, allow_unterminated);
}
void prs_disassemble(FILE* stream, const void* data, size_t size) {
+6 -6
View File
@@ -184,15 +184,15 @@ struct PRSDecompressResult {
std::string data;
size_t input_bytes_used;
};
PRSDecompressResult prs_decompress_with_meta(const void* data, size_t size, size_t max_output_size = 0);
PRSDecompressResult prs_decompress_with_meta(const std::string& data, size_t max_output_size = 0);
std::string prs_decompress(const void* data, size_t size, size_t max_output_size = 0);
std::string prs_decompress(const std::string& data, size_t max_output_size = 0);
PRSDecompressResult prs_decompress_with_meta(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
PRSDecompressResult prs_decompress_with_meta(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
std::string prs_decompress(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
std::string prs_decompress(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
// Returns the decompressed size of PRS-compressed data, without actually
// decompressing it.
size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size = 0);
size_t prs_decompress_size(const std::string& data, size_t max_output_size = 0);
size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size = 0, bool allow_unterminated = false);
size_t prs_decompress_size(const std::string& data, size_t max_output_size = 0, bool allow_unterminated = false);
// Prints the command stream from a PRS-compressed buffer.
void prs_disassemble(FILE* stream, const void* data, size_t size);
+127 -6
View File
@@ -1256,8 +1256,7 @@ void Rules::clear() {
this->dice_exchange_mode = DiceExchangeMode::HIGH_ATK;
this->disable_dice_boost = 0;
this->def_dice_range = 0;
this->unused1 = 0;
this->unused2 = 0;
this->unused.clear(0);
}
string Rules::str() const {
@@ -1396,6 +1395,40 @@ string Rules::str() const {
return "Rules[" + join(tokens, ", ") + "]";
}
RulesTrial::RulesTrial(const Rules& r)
: overall_time_limit(r.overall_time_limit),
phase_time_limit(r.phase_time_limit),
allowed_cards(r.allowed_cards),
atk_dice_max(r.max_dice),
def_dice_max(r.max_dice),
disable_deck_shuffle(r.disable_deck_shuffle),
disable_deck_loop(r.disable_deck_loop),
char_hp(r.char_hp),
hp_type(r.hp_type),
no_assist_cards(r.no_assist_cards),
disable_dialogue(r.disable_dialogue),
dice_exchange_mode(r.dice_exchange_mode) {}
RulesTrial::operator Rules() const {
Rules ret;
ret.overall_time_limit = this->overall_time_limit;
ret.phase_time_limit = this->phase_time_limit;
ret.allowed_cards = this->allowed_cards;
ret.min_dice = 1;
ret.max_dice = this->atk_dice_max;
ret.disable_deck_shuffle = this->disable_deck_shuffle;
ret.disable_deck_loop = this->disable_deck_loop;
ret.char_hp = this->char_hp;
ret.hp_type = this->hp_type;
ret.no_assist_cards = this->no_assist_cards;
ret.disable_dialogue = this->disable_dialogue;
ret.dice_exchange_mode = this->dice_exchange_mode;
ret.disable_dice_boost = 0;
ret.def_dice_range = 0x10 | (this->def_dice_max ? this->def_dice_max : 0x06);
ret.unused.clear(0);
return ret;
}
StateFlags::StateFlags() {
this->clear();
}
@@ -1518,9 +1551,6 @@ string MapDefinition::str(const CardIndex* card_index) const {
" a5[0x70:0x74]=%02hhX %02hhX %02hhX %02hhX",
this->unknown_a5[0x70], this->unknown_a5[0x71], this->unknown_a5[0x72], this->unknown_a5[0x73]));
lines.emplace_back(" default_rules: " + this->default_rules.str());
lines.emplace_back(string_printf(
" a6=%02hhX %02hhX %02hhX %02hhX",
this->unknown_a6[0], this->unknown_a6[1], this->unknown_a6[2], this->unknown_a6[3]));
lines.emplace_back(" name: " + string(this->name));
lines.emplace_back(" location_name: " + string(this->location_name));
lines.emplace_back(" quest_name: " + string(this->quest_name));
@@ -1715,7 +1745,6 @@ MapDefinitionTrial::MapDefinitionTrial(const MapDefinition& map)
modification_tiles(map.modification_tiles),
unknown_a5(map.unknown_a5),
default_rules(map.default_rules),
unknown_a6(map.unknown_a6),
name(map.name),
location_name(map.location_name),
quest_name(map.quest_name),
@@ -1744,6 +1773,98 @@ MapDefinitionTrial::MapDefinitionTrial(const MapDefinition& map)
}
}
MapDefinitionTrial::operator MapDefinition() const {
MapDefinition ret;
ret.unknown_a1 = this->unknown_a1;
ret.map_number = this->map_number;
ret.width = this->width;
ret.height = this->height;
ret.environment_number = this->environment_number;
ret.num_camera_zones = this->num_camera_zones;
ret.map_tiles = this->map_tiles;
ret.start_tile_definitions = this->start_tile_definitions;
ret.camera_zone_maps = this->camera_zone_maps;
ret.camera_zone_specs = this->camera_zone_specs;
ret.overview_specs = this->overview_specs;
ret.modification_tiles = this->modification_tiles;
ret.unknown_a5.clear(0xFF);
ret.unknown_a5 = this->unknown_a5;
ret.default_rules = this->default_rules;
ret.name = this->name;
ret.location_name = this->location_name;
ret.quest_name = this->quest_name;
ret.description = this->description;
ret.map_x = this->map_x;
ret.map_y = this->map_y;
ret.npc_decks = this->npc_decks;
ret.npc_ai_params = this->npc_ai_params;
ret.unknown_a7 = this->unknown_a7;
ret.npc_ai_params_entry_index = this->npc_ai_params_entry_index;
ret.before_message = this->before_message;
ret.after_message = this->after_message;
ret.dispatch_message = this->dispatch_message;
for (size_t z = 0; z < ret.dialogue_sets.size(); z++) {
ret.dialogue_sets[z].sub<8>(0) = this->dialogue_sets[z];
for (size_t x = 8; x < ret.dialogue_sets[z].size(); x++) {
ret.dialogue_sets[z][x].unknown_a1 = 0xFFFF;
ret.dialogue_sets[z][x].unknown_a2 = 0xFFFF;
for (size_t w = 0; w < 4; w++) {
ret.dialogue_sets[z][x].strings[w].clear(0xFF);
ret.dialogue_sets[z][x].strings[w][0] = 0x00;
}
}
}
ret.reward_card_ids = this->reward_card_ids;
ret.win_level_override = this->win_level_override;
ret.loss_level_override = this->loss_level_override;
ret.field_offset_x = this->field_offset_x;
ret.field_offset_y = this->field_offset_y;
ret.map_category = this->map_category;
ret.cyber_block_type = this->cyber_block_type;
ret.unknown_a11 = this->unknown_a11;
ret.unavailable_sc_cards.clear(0xFFFF);
// The trial edition doesn't seem to have entry_states at all, so we have to
// guess and fill in the field appropriately here.
size_t num_npc_decks = 0;
for (size_t z = 0; z < ret.npc_decks.size(); z++) {
if (ret.npc_decks[z].name[0]) {
num_npc_decks++;
}
}
for (size_t z = 0; z < 4; z++) {
ret.entry_states[z].deck_type = 0xFF;
}
switch (num_npc_decks) {
case 0: // No NPCs; it's a free battle map
ret.entry_states[0].player_type = 0xFF;
ret.entry_states[1].player_type = 0xFF;
ret.entry_states[2].player_type = 0xFF;
ret.entry_states[3].player_type = 0xFF;
break;
case 1: // One NPC; assume it's a 1v1 quest (Player vs. COM)
ret.entry_states[0].player_type = 0x00;
ret.entry_states[1].player_type = 0x04;
ret.entry_states[2].player_type = 0x03;
ret.entry_states[3].player_type = 0x04;
break;
case 2: // Two NPCs; assume it's a 2v2 quest (Player+Player/COM vs. COM+COM)
ret.entry_states[0].player_type = 0x00;
ret.entry_states[1].player_type = 0x02;
ret.entry_states[2].player_type = 0x03;
ret.entry_states[3].player_type = 0x03;
break;
case 3: // Three NPCs; assume it's a 2v2 quest (Player+COM vs. COM+COM)
ret.entry_states[0].player_type = 0x00;
ret.entry_states[1].player_type = 0x03;
ret.entry_states[2].player_type = 0x03;
ret.entry_states[3].player_type = 0x03;
break;
default: // Should be impossible
throw logic_error("too many NPC decks in trial map definition");
}
return ret;
}
bool Rules::check_invalid_fields() const {
Rules t = *this;
return t.check_and_reset_invalid_fields();
+27 -7
View File
@@ -879,9 +879,8 @@ struct Rules {
// NOTE: The following fields are unused in PSO's implementation, but newserv
// uses them to implement extended rules.
/* 0D */ uint8_t def_dice_range = 0; // High 4 bits = min, low 4 = max
/* 0E */ uint8_t unused1 = 0;
/* 0F */ uint8_t unused2 = 0;
/* 10 */
/* 0E */ parray<uint8_t, 6> unused;
/* 14 */
// Annoyingly, this structure is a different size in Episode 3 Trial Edition.
// This means that many command formats, as well as the map format, are
@@ -906,6 +905,28 @@ struct Rules {
std::string str() const;
} __attribute__((packed));
struct RulesTrial {
// The fields here have the same meaning as in the final version. The only
// difference is that Dice Boost does not exist in the trial version.
/* 00 */ uint8_t overall_time_limit = 0;
/* 01 */ uint8_t phase_time_limit = 0;
/* 02 */ AllowedCards allowed_cards = AllowedCards::ALL;
/* 03 */ uint8_t atk_dice_max = 1;
/* 04 */ uint8_t def_dice_max = 6;
/* 05 */ uint8_t disable_deck_shuffle = 0;
/* 06 */ uint8_t disable_deck_loop = 0;
/* 07 */ uint8_t char_hp = 15;
/* 08 */ HPType hp_type = HPType::DEFEAT_PLAYER;
/* 09 */ uint8_t no_assist_cards = 0;
/* 0A */ uint8_t disable_dialogue = 0;
/* 0B */ DiceExchangeMode dice_exchange_mode = DiceExchangeMode::HIGH_ATK;
/* 0C */
RulesTrial() = default;
explicit RulesTrial(const Rules&);
operator Rules() const;
} __attribute__((packed));
struct StateFlags {
/* 00 */ le_uint16_t turn_num;
/* 02 */ BattlePhase battle_phase;
@@ -1090,7 +1111,6 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 1D68 */ parray<uint8_t, 0x74> unknown_a5;
/* 1DDC */ Rules default_rules;
/* 1DEC */ parray<uint8_t, 4> unknown_a6;
/* 1DF0 */ ptext<char, 0x14> name;
/* 1E04 */ ptext<char, 0x14> location_name;
@@ -1261,9 +1281,8 @@ struct MapDefinitionTrial {
/* 1518 */ parray<parray<MapDefinition::CameraSpec, 10>, 2> camera_zone_specs;
/* 1AB8 */ parray<parray<MapDefinition::CameraSpec, 2>, 3> overview_specs;
/* 1C68 */ parray<parray<uint8_t, 0x10>, 0x10> modification_tiles;
/* 1D68 */ parray<uint8_t, 0x6C> unknown_a5;
/* 1DD4 */ Rules default_rules;
/* 1DE4 */ parray<uint8_t, 4> unknown_a6;
/* 1D68 */ parray<uint8_t, 0x74> unknown_a5;
/* 1DD4 */ RulesTrial default_rules;
/* 1DE8 */ ptext<char, 0x14> name;
/* 1DFC */ ptext<char, 0x14> location_name;
/* 1E10 */ ptext<char, 0x3C> quest_name;
@@ -1292,6 +1311,7 @@ struct MapDefinitionTrial {
/* 41A0 */
MapDefinitionTrial(const MapDefinition& map);
operator MapDefinition() const;
} __attribute__((packed));
struct COMDeckDefinition {
-1
View File
@@ -47,7 +47,6 @@ void MapAndRulesState::clear() {
this->map_number = 0;
this->unused4 = 0;
this->rules.clear();
this->unused5 = 0;
}
bool MapAndRulesState::loc_is_within_bounds(uint8_t x, uint8_t y) const {
-1
View File
@@ -34,7 +34,6 @@ struct MapAndRulesState {
le_uint32_t map_number;
uint32_t unused4;
Rules rules;
uint32_t unused5;
MapAndRulesState();
void clear();
+3 -3
View File
@@ -756,7 +756,7 @@ int main(int argc, char** argv) {
data = prs_compress(data, compression_level, progress_fn);
}
} else if ((behavior == Behavior::DECOMPRESS_PRS) || (behavior == Behavior::DECOMPRESS_PR2)) {
data = prs_decompress(data);
data = prs_decompress(data, bytes, (bytes != 0));
} else if (behavior == Behavior::COMPRESS_BC0) {
if (compress_optimal) {
data = bc0_compress_optimal(data.data(), data.size(), optimal_progress_fn);
@@ -1323,11 +1323,11 @@ int main(int argc, char** argv) {
string output_filename_base = input_filename;
if (quest_file_type == Quest::FileFormat::BIN_DAT_GCI) {
int64_t dec_seed = seed.empty() ? -1 : stoul(seed, nullptr, 16);
auto decoded = Quest::decode_gci_file(input_filename, num_threads, dec_seed);
auto decoded = Quest::decode_gci_file(input_filename, num_threads, dec_seed, skip_checksum);
save_file(output_filename_base + ".dec", decoded);
} else if (quest_file_type == Quest::FileFormat::BIN_DAT_VMS) {
int64_t dec_seed = seed.empty() ? -1 : stoul(seed, nullptr, 16);
auto decoded = Quest::decode_vms_file(input_filename, num_threads, dec_seed);
auto decoded = Quest::decode_vms_file(input_filename, num_threads, dec_seed, skip_checksum);
save_file(output_filename_base + ".dec", decoded);
} else if (quest_file_type == Quest::FileFormat::BIN_DAT_DLQ) {
auto decoded = Quest::decode_dlq_file(input_filename);
+74 -46
View File
@@ -83,7 +83,8 @@ struct PSOGCIDLQFileEncryptedHeader : PSOMemCardDLQFileEncryptedHeader<true> {
} __attribute__((packed));
template <bool IsBigEndian>
string decrypt_download_quest_data_section(const void* data_section, size_t size, uint32_t seed) {
string decrypt_download_quest_data_section(
const void* data_section, size_t size, uint32_t seed, bool skip_checksum = false, bool is_ep3_trial = false) {
string decrypted = decrypt_data_section<IsBigEndian>(data_section, size, seed);
size_t orig_size = decrypted.size();
@@ -99,41 +100,67 @@ string decrypt_download_quest_data_section(const void* data_section, size_t size
round2_crypt.encrypt_t<IsBigEndian>(
decrypted.data() + 4, (decrypted.size() - 4));
if (header->decompressed_size & 0xFFF00000) {
throw runtime_error(string_printf(
"decompressed_size too large (%08" PRIX32 ")", header->decompressed_size.load()));
if (is_ep3_trial) {
StringReader r(decrypted);
r.skip(16);
if (r.readx(15) != "SONICTEAM,SEGA.") {
throw runtime_error("Episode 3 GCI file is not a quest");
}
r.skip(9);
// Some Ep3 trial download quests don't have a stop opcode in the PRS
// stream; it seems the client just automatically stops when the correct
// amount of data has been produced. To handle this, we allow the PRS stream
// to be unterminated here.
size_t decompressed_size = prs_decompress_size(
r.getv(r.remaining(), false), r.remaining(), sizeof(Episode3::MapDefinitionTrial), true);
if (decompressed_size < sizeof(Episode3::MapDefinitionTrial)) {
throw runtime_error(string_printf(
"decompressed size (%zu) does not match expected size (%zu)",
decompressed_size, sizeof(Episode3::MapDefinitionTrial)));
}
return decrypted.substr(0x28);
} else {
if (header->decompressed_size & 0xFFF00000) {
throw runtime_error(string_printf(
"decompressed_size too large (%08" PRIX32 ")", header->decompressed_size.load()));
}
if (!skip_checksum) {
uint32_t expected_crc = header->checksum;
header->checksum = 0;
uint32_t actual_crc = crc32(decrypted.data(), orig_size);
header->checksum = expected_crc;
if (expected_crc != actual_crc && expected_crc != bswap32(actual_crc)) {
throw runtime_error(string_printf(
"incorrect decrypted data section checksum: expected %08" PRIX32 "; received %08" PRIX32,
expected_crc, actual_crc));
}
}
// Unlike the above rounds, round 3 is always little-endian (it corresponds to
// the round of encryption done on the server before sending the file to the
// client in the first place)
PSOV2Encryption(header->round3_seed).decrypt(decrypted.data() + sizeof(HeaderT), decrypted.size() - sizeof(HeaderT));
decrypted.resize(orig_size);
// Some download quest GCI files have decompressed_size fields that are 8
// bytes smaller than the actual decompressed size of the data. They seem to
// work fine, so we accept both cases as correct.
size_t decompressed_size = prs_decompress_size(
decrypted.data() + sizeof(HeaderT),
decrypted.size() - sizeof(HeaderT));
size_t expected_decompressed_size = header->decompressed_size.load();
if ((decompressed_size != expected_decompressed_size) &&
(decompressed_size != expected_decompressed_size - 8)) {
throw runtime_error(string_printf(
"decompressed size (%zu) does not match expected size (%zu)",
decompressed_size, expected_decompressed_size));
}
return decrypted.substr(sizeof(HeaderT));
}
uint32_t expected_crc = header->checksum;
header->checksum = 0;
uint32_t actual_crc = crc32(decrypted.data(), orig_size);
header->checksum = expected_crc;
if (expected_crc != actual_crc && expected_crc != bswap32(actual_crc)) {
throw runtime_error(string_printf(
"incorrect decrypted data section checksum: expected %08" PRIX32 "; received %08" PRIX32,
expected_crc, actual_crc));
}
// Unlike the above rounds, round 3 is always little-endian (it corresponds to
// the round of encryption done on the server before sending the file to the
// client in the first place)
PSOV2Encryption(header->round3_seed).decrypt(decrypted.data() + sizeof(HeaderT), decrypted.size() - sizeof(HeaderT));
decrypted.resize(orig_size);
// Some download quest GCI files have decompressed_size fields that are 8
// bytes smaller than the actual decompressed size of the data. They seem to
// work fine, so we accept both cases as correct.
size_t decompressed_size = prs_decompress_size(
decrypted.data() + sizeof(HeaderT),
decrypted.size() - sizeof(HeaderT));
if ((decompressed_size != header->decompressed_size) &&
(decompressed_size != header->decompressed_size - 8)) {
throw runtime_error(string_printf(
"decompressed size (%zu) does not match size in header (%" PRId32 ")",
decompressed_size, header->decompressed_size.load()));
}
return decrypted.substr(sizeof(HeaderT));
}
string decrypt_vms_v1_data_section(const void* data_section, size_t size) {
@@ -160,16 +187,17 @@ string decrypt_vms_v1_data_section(const void* data_section, size_t size) {
template <bool IsBigEndian>
string find_seed_and_decrypt_download_quest_data_section(
const void* data_section, size_t size, size_t num_threads) {
const void* data_section, size_t size, bool skip_checksum, bool is_ep3_trial, size_t num_threads) {
mutex result_lock;
string result;
uint64_t result_seed = parallel_range<uint64_t>([&](uint64_t seed, size_t) {
try {
string ret = decrypt_download_quest_data_section<IsBigEndian>(data_section, size, seed);
string ret = decrypt_download_quest_data_section<IsBigEndian>(
data_section, size, seed, skip_checksum, is_ep3_trial);
lock_guard<mutex> g(result_lock);
result = std::move(ret);
return true;
} catch (const runtime_error&) {
} catch (const runtime_error& e) {
return false;
}
},
@@ -474,7 +502,7 @@ shared_ptr<const string> Quest::dat_contents() const {
}
string Quest::decode_gci_file(
const string& filename, ssize_t find_seed_num_threads, int64_t known_seed) {
const string& filename, ssize_t find_seed_num_threads, int64_t known_seed, bool skip_checksum) {
string data = load_file(filename);
StringReader r(data);
@@ -489,11 +517,11 @@ string Quest::decode_gci_file(
if (dlq_header.round2_seed || dlq_header.checksum || dlq_header.round3_seed) {
if (known_seed >= 0) {
return decrypt_download_quest_data_section<true>(
r.getv(header.data_size), header.data_size, known_seed);
r.getv(header.data_size), header.data_size, known_seed, skip_checksum, false);
} else if (header.embedded_seed != 0) {
return decrypt_download_quest_data_section<true>(
r.getv(header.data_size), header.data_size, header.embedded_seed);
r.getv(header.data_size), header.data_size, header.embedded_seed, skip_checksum, false);
} else {
if (find_seed_num_threads < 0) {
@@ -503,7 +531,7 @@ string Quest::decode_gci_file(
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);
r.getv(header.data_size), header.data_size, skip_checksum, false, find_seed_num_threads);
}
} else { // Unencrypted GCI format
@@ -525,7 +553,7 @@ string Quest::decode_gci_file(
if (header.is_trial()) {
if (known_seed >= 0) {
return decrypt_download_quest_data_section<true>(
r.getv(header.data_size), header.data_size, known_seed);
r.getv(header.data_size), header.data_size, known_seed, true, true);
} else {
if (find_seed_num_threads < 0) {
throw runtime_error("file is encrypted");
@@ -534,7 +562,7 @@ string Quest::decode_gci_file(
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);
r.getv(header.data_size), header.data_size, true, true, find_seed_num_threads);
}
} else {
@@ -575,7 +603,7 @@ string Quest::decode_gci_file(
}
string Quest::decode_vms_file(
const string& filename, ssize_t find_seed_num_threads, int64_t known_seed) {
const string& filename, ssize_t find_seed_num_threads, int64_t known_seed, bool skip_checksum) {
string data = load_file(filename);
StringReader r(data);
@@ -603,7 +631,7 @@ string Quest::decode_vms_file(
find_seed_num_threads = thread::hardware_concurrency();
}
return find_seed_and_decrypt_download_quest_data_section<false>(
data_section, header.data_size, find_seed_num_threads);
data_section, header.data_size, skip_checksum, 0, find_seed_num_threads);
}
}
+4 -2
View File
@@ -93,11 +93,13 @@ public:
static std::string decode_gci_file(
const std::string& filename,
ssize_t find_seed_num_threads = -1,
int64_t known_seed = -1);
int64_t known_seed = -1,
bool skip_checksum = false);
static std::string decode_vms_file(
const std::string& filename,
ssize_t find_seed_num_threads = -1,
int64_t known_seed = -1);
int64_t known_seed = -1,
bool skip_checksum = false);
static std::string decode_dlq_file(const std::string& filename);
static std::string decode_dlq_data(const std::string& filename);
static std::pair<std::string, std::string> decode_qst_file(const std::string& filename);