add Ep3 trial map format

This commit is contained in:
Martin Michelsen
2023-08-15 09:00:50 -07:00
parent 102fe92c3a
commit c0f4f7af5f
7 changed files with 140 additions and 29 deletions
+56 -4
View File
@@ -1630,6 +1630,50 @@ string MapDefinition::str(const CardIndex* card_index) const {
return join(lines, "\n");
}
MapDefinitionTrial::MapDefinitionTrial(const MapDefinition& map)
: unknown_a1(map.unknown_a1),
map_number(map.map_number),
width(map.width),
height(map.height),
environment_number(map.environment_number),
num_alt_maps(map.num_alt_maps),
map_tiles(map.map_tiles),
start_tile_definitions(map.start_tile_definitions),
alt_maps1(map.alt_maps1),
alt_maps_unknown_a3(map.alt_maps_unknown_a3),
unknown_a4(map.unknown_a4),
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),
description(map.description),
map_x(map.map_x),
map_y(map.map_y),
npc_decks(map.npc_decks),
npc_chars(map.npc_chars),
unknown_a7_a(map.unknown_a7_a),
unknown_a7_b(map.unknown_a7_b),
before_message(map.before_message),
after_message(map.after_message),
dispatch_message(map.dispatch_message),
dialogue_sets(),
reward_card_ids(map.reward_card_ids),
unknown_a9_a(map.unknown_a9_a),
unknown_a9_b(map.unknown_a9_b),
unknown_a9_c(map.unknown_a9_c),
unknown_a9_d(map.unknown_a9_d),
unknown_a10(map.unknown_a10),
cyber_block_type(map.cyber_block_type),
unknown_a11(map.unknown_a11),
unknown_t12(0xFF) {
for (size_t z = 0; z < this->dialogue_sets.size(); z++) {
this->dialogue_sets[z] = map.dialogue_sets[z].sub<8>(0);
}
}
bool Rules::check_invalid_fields() const {
Rules t = *this;
return t.check_and_reset_invalid_fields();
@@ -1969,11 +2013,19 @@ MapIndex::MapEntry::MapEntry(const string& compressed, bool is_quest)
this->map = *reinterpret_cast<const MapDefinition*>(decompressed.data());
}
string MapIndex::MapEntry::compressed() const {
if (this->compressed_data.empty()) {
this->compressed_data = prs_compress(&this->map, sizeof(this->map));
const string& MapIndex::MapEntry::compressed(bool is_trial) const {
if (is_trial) {
if (this->compressed_trial_data.empty()) {
MapDefinitionTrial mdt(this->map);
this->compressed_trial_data = prs_compress(&mdt, sizeof(mdt));
}
return this->compressed_trial_data;
} else {
if (this->compressed_data.empty()) {
this->compressed_data = prs_compress(&this->map, sizeof(this->map));
}
return this->compressed_data;
}
return this->compressed_data;
}
const string& MapIndex::get_compressed_list() const {
+68 -11
View File
@@ -553,7 +553,7 @@ struct CardDefinition {
/* 00A0 */ ptext<char, 0x14> en_name;
/* 00B4 */ ptext<char, 0x0B> jp_short_name;
/* 00BF */ ptext<char, 0x08> en_short_name;
/* 00C7 */ Effect effects[3];
/* 00C7 */ parray<Effect, 3> effects;
/* 0127 */ uint8_t unused4;
/* 0128 */
@@ -573,9 +573,9 @@ struct CardDefinitionsFooter {
/* 08 */ be_uint32_t num_cards2;
/* 0C */ parray<be_uint32_t, 11> unknown_a2;
/* 38 */ be_uint32_t unknown_offset_a3;
/* 3C */ be_uint32_t unknown_a4[3];
/* 3C */ parray<be_uint32_t, 3> unknown_a4;
/* 48 */ be_uint32_t footer_offset;
/* 4C */ be_uint32_t unknown_a5[3];
/* 4C */ parray<be_uint32_t, 3> unknown_a5;
/* 58 */
} __attribute__((packed));
@@ -701,6 +701,12 @@ struct Rules {
/* 0D */ parray<uint8_t, 3> unused;
/* 10 */
// 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
// different, and the existing Server implementation can't serve Trial Edition
// clients. It'd be nice to support Trial Edition battles, but that would
// likely be more work than it's worth.
Rules();
explicit Rules(std::shared_ptr<const JSONObject> json);
std::shared_ptr<JSONObject> json() const;
@@ -838,9 +844,9 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
// - If the team has 3 players, bytes [3] through [5] are used.
/* 010C */ parray<parray<uint8_t, 6>, 2> start_tile_definitions;
/* 0118 */ parray<parray<uint8_t, 0x10>, 0x10> alt_maps1[2][0x0A];
/* 1518 */ parray<be_float, 0x12> alt_maps_unknown_a3[2][0x0A];
/* 1AB8 */ parray<be_float, 0x24> unknown_a4[3];
/* 0118 */ parray<parray<parray<parray<uint8_t, 0x10>, 0x10>, 0x0A>, 2> alt_maps1;
/* 1518 */ parray<parray<parray<be_float, 0x12>, 0x0A>, 2> alt_maps_unknown_a3;
/* 1AB8 */ parray<parray<be_float, 0x24>, 3> unknown_a4;
// In the modification_tiles array, the values are:
// 10 = blocked by rock (as if the corresponding map_tiles value was 00)
@@ -871,7 +877,8 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 18 */ parray<be_uint16_t, 0x20> card_ids; // Last one appears to always be FFFF
/* 58 */
} __attribute__((packed));
/* 1FE8 */ NPCDeck npc_decks[3]; // Unused if name[0] == 0
/* 1FE8 */ parray<NPCDeck, 3> npc_decks; // Unused if name[0] == 0
struct NPCCharacter {
/* 0000 */ parray<be_uint16_t, 2> unknown_a1;
/* 0004 */ parray<uint8_t, 4> unknown_a2;
@@ -879,7 +886,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
/* 0018 */ parray<be_uint16_t, 0x7E> unknown_a3;
/* 0114 */
} __attribute__((packed));
/* 20F0 */ NPCCharacter npc_chars[3]; // Unused if name[0] == 0
/* 20F0 */ parray<NPCCharacter, 3> npc_chars; // Unused if name[0] == 0
/* 242C */ parray<uint8_t, 8> unknown_a7_a; // Always FF?
/* 2434 */ parray<be_uint32_t, 3> unknown_a7_b; // Always FF?
@@ -896,10 +903,10 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
struct DialogueSet {
/* 0000 */ be_uint16_t unknown_a1;
/* 0002 */ be_uint16_t unknown_a2; // Always 0x0064 if valid, 0xFFFF if unused?
/* 0004 */ ptext<char, 0x40> strings[4];
/* 0004 */ parray<ptext<char, 0x40>, 4> strings;
/* 0104 */
} __attribute__((packed));
/* 28F0 */ DialogueSet dialogue_sets[3][0x10]; // Up to 0x10 per valid NPC
/* 28F0 */ parray<parray<DialogueSet, 0x10>, 3> dialogue_sets; // Up to 0x10 per valid NPC
/* 59B0 */ parray<be_uint16_t, 0x10> reward_card_ids;
@@ -958,6 +965,55 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests
std::string str(const CardIndex* card_index = nullptr) const;
} __attribute__((packed));
struct MapDefinitionTrial {
// This is the format of Episode 3 Trial Edition maps. See the comments in
// MapDefinition for what each field means.
/* 0000 */ be_uint32_t unknown_a1;
/* 0004 */ be_uint32_t map_number;
/* 0008 */ uint8_t width;
/* 0009 */ uint8_t height;
/* 000A */ uint8_t environment_number;
/* 000B */ uint8_t num_alt_maps;
/* 000C */ parray<parray<uint8_t, 0x10>, 0x10> map_tiles;
/* 010C */ parray<parray<uint8_t, 6>, 2> start_tile_definitions;
/* 0118 */ parray<parray<parray<parray<uint8_t, 0x10>, 0x10>, 0x0A>, 2> alt_maps1;
/* 1518 */ parray<parray<parray<be_float, 0x12>, 0x0A>, 2> alt_maps_unknown_a3;
/* 1AB8 */ parray<parray<be_float, 0x24>, 3> unknown_a4;
/* 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;
/* 1DE8 */ ptext<char, 0x14> name;
/* 1DFC */ ptext<char, 0x14> location_name;
/* 1E10 */ ptext<char, 0x3C> quest_name;
/* 1E4C */ ptext<char, 0x190> description;
/* 1FDC */ be_uint16_t map_x;
/* 1FDE */ be_uint16_t map_y;
/* 1FE0 */ parray<MapDefinition::NPCDeck, 3> npc_decks;
/* 20E8 */ parray<MapDefinition::NPCCharacter, 3> npc_chars;
/* 2424 */ parray<uint8_t, 8> unknown_a7_a;
/* 242C */ parray<be_uint32_t, 3> unknown_a7_b;
/* 2438 */ ptext<char, 0x190> before_message;
/* 25C8 */ ptext<char, 0x190> after_message;
/* 2758 */ ptext<char, 0x190> dispatch_message;
/* 28E8 */ parray<parray<MapDefinition::DialogueSet, 8>, 3> dialogue_sets;
/* 4148 */ parray<be_uint16_t, 0x10> reward_card_ids;
/* 4168 */ be_uint32_t unknown_a9_a;
/* 416C */ be_uint32_t unknown_a9_b;
/* 4170 */ be_uint16_t unknown_a9_c;
/* 4172 */ be_uint16_t unknown_a9_d;
/* 4174 */ uint8_t unknown_a10;
/* 4175 */ uint8_t cyber_block_type;
/* 4176 */ parray<uint8_t, 2> unknown_a11;
// TODO: This field may contain some version of unavailable_sc_cards and/or
// entry_states from MapDefinition, but the format isn't the same
/* 4178 */ parray<uint8_t, 0x28> unknown_t12;
/* 41A0 */
MapDefinitionTrial(const MapDefinition& map);
} __attribute__((packed));
struct COMDeckDefinition {
size_t index;
std::string player_name;
@@ -1000,10 +1056,11 @@ public:
MapEntry(const MapDefinition& map, bool is_quest);
MapEntry(const std::string& compressed_data, bool is_quest);
std::string compressed() const;
const std::string& compressed(bool is_trial) const;
private:
mutable std::string compressed_data;
mutable std::string compressed_trial_data;
};
const std::string& get_compressed_list() const;
+7 -5
View File
@@ -225,8 +225,9 @@ void Server::send_6xB4x46() const {
this->send(cmd46);
}
string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::MapEntry> map) {
const auto& compressed = map->compressed();
string Server::prepare_6xB6x41_map_definition(
shared_ptr<const MapIndex::MapEntry> map, bool is_trial) {
const auto& compressed = map->compressed(is_trial);
StringWriter w;
uint32_t subcommand_size = (compressed.size() + sizeof(G_MapData_GC_Ep3_6xB6x41) + 3) & (~3);
@@ -235,7 +236,7 @@ string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::MapEntr
return std::move(w.str());
}
void Server::send_commands_for_joining_spectator(Channel& c) const {
void Server::send_commands_for_joining_spectator(Channel& c, bool is_trial) const {
bool should_send_state = true;
if (this->setup_phase == SetupPhase::REGISTRATION) {
// If registration is still in progress, we only need to send the map data
@@ -248,7 +249,7 @@ void Server::send_commands_for_joining_spectator(Channel& c) const {
auto map = this->base()->last_chosen_map;
if (map) {
string data = this->prepare_6xB6x41_map_definition(map);
string data = this->prepare_6xB6x41_map_definition(map, is_trial);
c.send(0x6C, 0x00, data);
}
@@ -2167,7 +2168,8 @@ void Server::handle_6xB3x41_map_request(const string& data) {
}
base->last_chosen_map = base->map_index->definition_for_number(cmd.map_number);
auto out_cmd = this->prepare_6xB6x41_map_definition(base->last_chosen_map);
auto out_cmd = this->prepare_6xB6x41_map_definition(
base->last_chosen_map, l->flags & Lobby::Flag::IS_EP3_TRIAL);
send_command(l, 0x6C, 0x00, out_cmd);
for (auto watcher_l : l->watcher_lobbies) {
send_command_if_not_loading(watcher_l, 0x6C, 0x00, out_cmd);
+2 -2
View File
@@ -120,7 +120,7 @@ public:
}
void send(const void* data, size_t size) const;
void send_commands_for_joining_spectator(Channel& ch) const;
void send_commands_for_joining_spectator(Channel& ch, bool is_trial) const;
__attribute__((format(printf, 2, 3))) void log_debug(const char* fmt, ...) const;
@@ -233,7 +233,7 @@ public:
G_UpdateDecks_GC_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const;
G_SetPlayerNames_GC_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const;
static std::string prepare_6xB6x41_map_definition(
std::shared_ptr<const MapIndex::MapEntry> map);
std::shared_ptr<const MapIndex::MapEntry> map, bool is_trial);
G_SetTrapTileLocations_GC_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const;
std::vector<std::shared_ptr<Card>> const_cast_set_cards_v(
+3 -3
View File
@@ -916,9 +916,9 @@ static HandlerResult S_6x(shared_ptr<ServerState>,
string map_data = prs_decompress(
data.data() + sizeof(cmd), data.size() - sizeof(cmd));
save_file(filename, map_data);
if (map_data.size() != sizeof(Episode3::MapDefinition)) {
session.log.warning("Wrote %zu bytes to %s (expected %zu bytes; the file may be invalid)",
map_data.size(), filename.c_str(), sizeof(Episode3::MapDefinition));
if (map_data.size() != sizeof(Episode3::MapDefinition) && map_data.size() != sizeof(Episode3::MapDefinitionTrial)) {
session.log.warning("Wrote %zu bytes to %s (expected %zu or %zu bytes; the file may be invalid)",
map_data.size(), filename.c_str(), sizeof(Episode3::MapDefinitionTrial), sizeof(Episode3::MapDefinition));
} else {
session.log.info("Wrote %zu bytes to %s", map_data.size(), filename.c_str());
}
+3 -2
View File
@@ -1241,7 +1241,7 @@ static void on_DC_Ep3(shared_ptr<ServerState> s, shared_ptr<Client> c,
if (tourn) {
send_ep3_set_tournament_player_decks(s, l, c, l->tournament_match);
string data = Episode3::Server::prepare_6xB6x41_map_definition(
tourn->get_map());
tourn->get_map(), l->flags & Lobby::Flag::IS_EP3_TRIAL);
c->channel.send(0x6C, 0x00, data);
}
}
@@ -3525,7 +3525,8 @@ static void on_6F(shared_ptr<ServerState> s, shared_ptr<Client> c,
if (l->battle_player && (l->flags & Lobby::Flag::START_BATTLE_PLAYER_IMMEDIATELY)) {
l->battle_player->start();
} else if (watched_lobby && watched_lobby->ep3_server_base) {
watched_lobby->ep3_server_base->server->send_commands_for_joining_spectator(c->channel);
watched_lobby->ep3_server_base->server->send_commands_for_joining_spectator(
c->channel, c->flags & Client::Flag::IS_EP3_TRIAL_EDITION);
}
// If there are more players to bring in, try to do so