don't skip server data commands before battle start

This commit is contained in:
Martin Michelsen
2024-04-28 13:56:16 -07:00
parent 29f200b83e
commit 29320f0858
6 changed files with 92 additions and 24 deletions
+23 -8
View File
@@ -12,7 +12,7 @@ namespace Episode3 {
void BattleRecord::PlayerEntry::print(FILE* stream) const { void BattleRecord::PlayerEntry::print(FILE* stream) const {
// TODO: Format this nicely somehow. Maybe factor out the functions in // TODO: Format this nicely somehow. Maybe factor out the functions in
// QuestScript that format some of these structures // QuestScript that format some of these structures
print_data(stream, this, sizeof(this)); print_data(stream, this, sizeof(*this), 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
} }
BattleRecord::Event::Event(StringReader& r) { BattleRecord::Event::Event(StringReader& r) {
@@ -96,29 +96,30 @@ void BattleRecord::Event::print(FILE* stream) const {
for (const auto& player : this->players) { for (const auto& player : this->players) {
fprintf(stream, " %02" PRIX32, player.lobby_data.client_id.load()); fprintf(stream, " %02" PRIX32, player.lobby_data.client_id.load());
} }
fputc('\n', stream);
for (const auto& player : this->players) { for (const auto& player : this->players) {
player.print(stream); player.print(stream);
} }
break; break;
case Type::BATTLE_COMMAND: case Type::BATTLE_COMMAND:
fprintf(stream, "BATTLE_COMMAND\n"); fprintf(stream, "BATTLE_COMMAND\n");
print_data(stream, this->data); print_data(stream, this->data, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
break; break;
case Type::GAME_COMMAND: case Type::GAME_COMMAND:
fprintf(stream, "GAME_COMMAND\n"); fprintf(stream, "GAME_COMMAND\n");
print_data(stream, this->data); print_data(stream, this->data, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
break; break;
case Type::EP3_GAME_COMMAND: case Type::EP3_GAME_COMMAND:
fprintf(stream, "EP3_GAME_COMMAND\n"); fprintf(stream, "EP3_GAME_COMMAND\n");
print_data(stream, this->data); print_data(stream, this->data, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
break; break;
case Type::CHAT_MESSAGE: case Type::CHAT_MESSAGE:
fprintf(stream, "CHAT_MESSAGE %08" PRIX32 "\n", this->guild_card_number); fprintf(stream, "CHAT_MESSAGE %08" PRIX32 "\n", this->guild_card_number);
print_data(stream, this->data); print_data(stream, this->data, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
break; break;
case Type::SERVER_DATA_COMMAND: case Type::SERVER_DATA_COMMAND:
fprintf(stream, "SERVER_DATA_COMMAND\n"); fprintf(stream, "SERVER_DATA_COMMAND\n");
print_data(stream, this->data); print_data(stream, this->data, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
break; break;
default: default:
throw runtime_error("unknown event type in battle record"); throw runtime_error("unknown event type in battle record");
@@ -255,6 +256,20 @@ void BattleRecord::add_random_data(const void* data, size_t size) {
this->random_stream.append(reinterpret_cast<const char*>(data), size); this->random_stream.append(reinterpret_cast<const char*>(data), size);
} }
vector<string> BattleRecord::get_all_server_data_commands() const {
vector<string> ret;
for (const auto& event : this->events) {
if (event.type == Event::Type::SERVER_DATA_COMMAND) {
ret.emplace_back(event.data);
}
}
return ret;
}
const string& BattleRecord::get_random_stream() const {
return this->random_stream;
}
bool BattleRecord::is_map_definition_event(const Event& ev) { bool BattleRecord::is_map_definition_event(const Event& ev) {
if (ev.type == Event::Type::BATTLE_COMMAND) { if (ev.type == Event::Type::BATTLE_COMMAND) {
auto& header = check_size_t<G_CardBattleCommandHeader>(ev.data, 0xFFFF); auto& header = check_size_t<G_CardBattleCommandHeader>(ev.data, 0xFFFF);
@@ -331,7 +346,7 @@ void BattleRecord::set_battle_start_timestamp() {
} }
} }
for (; it != this->events.end(); it++) { for (; it != this->events.end(); it++) {
if (it->type == Event::Type::BATTLE_COMMAND) { if ((it->type == Event::Type::BATTLE_COMMAND) || (it->type == Event::Type::SERVER_DATA_COMMAND)) {
new_events.emplace_back(std::move(*it)); new_events.emplace_back(std::move(*it));
} }
} }
@@ -373,7 +388,7 @@ shared_ptr<const BattleRecord> BattleRecordPlayer::get_record() const {
return this->record; return this->record;
} }
void BattleRecordPlayer::set_lobby(std::shared_ptr<Lobby> l) { void BattleRecordPlayer::set_lobby(shared_ptr<Lobby> l) {
this->lobby = l; this->lobby = l;
} }
+3
View File
@@ -86,6 +86,9 @@ public:
void print(FILE* stream) const; void print(FILE* stream) const;
std::vector<std::string> get_all_server_data_commands() const;
const std::string& get_random_stream() const;
private: private:
static constexpr uint64_t SIGNATURE_V1 = 0x14C946D56D1DAC50; static constexpr uint64_t SIGNATURE_V1 = 0x14C946D56D1DAC50;
static constexpr uint64_t SIGNATURE_V2 = 0xD01E5EC12853C377; static constexpr uint64_t SIGNATURE_V2 = 0xD01E5EC12853C377;
+9 -3
View File
@@ -30,7 +30,7 @@ void Server::PresenceEntry::clear() {
Server::Server(shared_ptr<Lobby> lobby, Options&& options) Server::Server(shared_ptr<Lobby> lobby, Options&& options)
: lobby(lobby), : lobby(lobby),
battle_record(lobby->battle_record), battle_record(lobby ? lobby->battle_record : nullptr),
has_lobby(lobby != nullptr), has_lobby(lobby != nullptr),
options(std::move(options)), options(std::move(options)),
last_chosen_map(this->options.tournament ? this->options.tournament->get_map() : nullptr), last_chosen_map(this->options.tournament ? this->options.tournament->get_map() : nullptr),
@@ -260,7 +260,7 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
} else if ((this->options.behavior_flags & BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING) && } else if ((this->options.behavior_flags & BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING) &&
this->log().info("Generated command")) { this->log().info("Generated command")) {
print_data(stderr, data, size); print_data(stderr, data, size, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
} }
} }
@@ -1075,7 +1075,13 @@ shared_ptr<const PlayerState> Server::get_player_state(uint8_t client_id) const
} }
uint32_t Server::get_random_raw() { uint32_t Server::get_random_raw() {
le_uint32_t ret = random_from_optional_crypt(this->options.opt_rand_crypt); le_uint32_t ret;
if (this->options.opt_rand_stream) {
this->options.opt_rand_stream->readx(&ret, sizeof(ret));
} else {
ret = random_from_optional_crypt(this->options.opt_rand_crypt);
}
if (this->battle_record && this->battle_record->writable()) { if (this->battle_record && this->battle_record->writable()) {
this->battle_record->add_random_data(&ret, sizeof(ret)); this->battle_record->add_random_data(&ret, sizeof(ret));
} }
+1
View File
@@ -72,6 +72,7 @@ public:
std::shared_ptr<const CardIndex> card_index; std::shared_ptr<const CardIndex> card_index;
std::shared_ptr<const MapIndex> map_index; std::shared_ptr<const MapIndex> map_index;
uint32_t behavior_flags; uint32_t behavior_flags;
std::shared_ptr<StringReader> opt_rand_stream;
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt; std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
std::shared_ptr<const Tournament> tournament; std::shared_ptr<const Tournament> tournament;
std::array<std::vector<uint16_t>, 5> trap_card_ids; std::array<std::vector<uint16_t>, 5> trap_card_ids;
+43 -4
View File
@@ -765,7 +765,7 @@ Action a_decrypt_dcv2_executable(
If --simple is given, uses the simpler encryption method used in some\n\ If --simple is given, uses the simpler encryption method used in some\n\
community modifications of the game. In this case, --seed is not required;\n\ community modifications of the game. In this case, --seed is not required;\n\
if not given, finds the seed automatically, and prints it to stderr so you\n\ if not given, finds the seed automatically, and prints it to stderr so you\n\
will be able to use it when re-encrypting.", will be able to use it when re-encrypting.\n",
+[](Arguments& args) { +[](Arguments& args) {
string executable_filename = args.get<string>("executable", true); string executable_filename = args.get<string>("executable", true);
string executable_data = load_file(executable_filename); string executable_data = load_file(executable_filename);
@@ -785,14 +785,14 @@ Action a_decrypt_dcv2_executable(
}); });
Action a_encrypt_dcv2_executable( Action a_encrypt_dcv2_executable(
"encrypt-dcv2-executable", "\ "encrypt-dcv2-executable", "\
decrypt-dcv2-executable --executable=EXEC --indexes=INDEXES\n\ encrypt-dcv2-executable --executable=EXEC --indexes=INDEXES\n\
decrypt-dcv2-executable --executable=EXEC --simple --seed=SEED\n\ encrypt-dcv2-executable --executable=EXEC --simple --seed=SEED\n\
Encrypt a PSO DC v2 executable file. EXEC should be the path to the\n\ Encrypt a PSO DC v2 executable file. EXEC should be the path to the\n\
executable (DP_ADDRESS.JPN) and INDEXES should be the path to the index\n\ executable (DP_ADDRESS.JPN) and INDEXES should be the path to the index\n\
fixup table (KATSUO.SEA). The output is written to EXEC.enc and\n\ fixup table (KATSUO.SEA). The output is written to EXEC.enc and\n\
INDEXES.enc.\n\ INDEXES.enc.\n\
If --simple is given, uses the simpler encryption method used in some\n\ If --simple is given, uses the simpler encryption method used in some\n\
community modifications of the game. In this case, --seed is required.", community modifications of the game. In this case, --seed is required.\n",
+[](Arguments& args) { +[](Arguments& args) {
string executable_filename = args.get<string>("executable", true); string executable_filename = args.get<string>("executable", true);
string executable_data = load_file(executable_filename); string executable_data = load_file(executable_filename);
@@ -2426,6 +2426,45 @@ Action a_replay_ep3_battle_commands(
} }
}); });
Action a_replay_ep3_battle_record(
"replay-ep3-battle-record", nullptr, +[](Arguments& args) {
auto rec = make_shared<Episode3::BattleRecord>(read_input_data(args));
auto s = make_shared<ServerState>(get_config_filename(args));
s->load_ep3_cards(false);
s->load_ep3_maps(false);
bool is_trial = (get_cli_version(args, Version::GC_EP3) == Version::GC_EP3_NTE);
Episode3::Server::Options options = {
.card_index = s->ep3_card_index,
.map_index = s->ep3_map_index,
.behavior_flags = (Episode3::BehaviorFlag::IGNORE_CARD_COUNTS |
Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES |
Episode3::BehaviorFlag::DISABLE_MASKING |
Episode3::BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING),
.opt_rand_stream = make_shared<StringReader>(rec->get_random_stream()),
.tournament = nullptr,
.trap_card_ids = {},
};
if (is_trial) {
options.behavior_flags |= Episode3::BehaviorFlag::IS_TRIAL_EDITION;
}
options.behavior_flags |= Episode3::BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING;
auto server = make_shared<Episode3::Server>(nullptr, std::move(options));
server->init();
for (const auto& command : rec->get_all_server_data_commands()) {
log_info("Server data command");
print_data(stderr, command, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR | PrintDataFlags::OFFSET_16_BITS);
server->on_server_data_input(nullptr, command);
}
});
Action a_disassemble_ep3_battle_record(
"disassemble-ep3-battle-record", nullptr, +[](Arguments& args) {
Episode3::BattleRecord(read_input_data(args)).print(stdout);
});
Action a_run_server_replay_log( Action a_run_server_replay_log(
"", nullptr, +[](Arguments& args) { "", nullptr, +[](Arguments& args) {
{ {
+13 -9
View File
@@ -1588,16 +1588,20 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
bool battle_finished_before = l->ep3_server->battle_finished; bool battle_finished_before = l->ep3_server->battle_finished;
try { if (s->catch_handler_exceptions) {
l->ep3_server->on_server_data_input(c, data); try {
} catch (const exception& e) { l->ep3_server->on_server_data_input(c, data);
c->log.error("Episode 3 engine returned an error: %s", e.what()); } catch (const exception& e) {
if (l->battle_record) { c->log.error("Episode 3 engine returned an error: %s", e.what());
string filename = string_printf("system/ep3/battle-records/exc.%" PRIu64 ".mzrd", now()); if (l->battle_record) {
save_file(filename, l->battle_record->serialize()); string filename = string_printf("system/ep3/battle-records/exc.%" PRIu64 ".mzrd", now());
c->log.error("Saved partial battle record as %s", filename.c_str()); save_file(filename, l->battle_record->serialize());
c->log.error("Saved partial battle record as %s", filename.c_str());
}
throw;
} }
throw; } else {
l->ep3_server->on_server_data_input(c, data);
} }
// If the battle has finished, finalize the recording and link it to all // If the battle has finished, finalize the recording and link it to all