diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index c9629950..545b6bb9 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -4256,16 +4256,79 @@ struct G_Unknown_6x6A { } __packed__; // 6x6B: Sync enemy state (used while loading into game; same header format as 6E) -// 6x6C: Sync object state (used while loading into game; same header format as 6E) -// 6x6D: Sync item state (used while loading into game; same header format as 6E) -// 6x6E: Sync flag state (used while loading into game) struct G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E { G_ExtendedHeader header; - le_uint32_t subcommand_size; le_uint32_t decompressed_size; le_uint32_t compressed_size; // Must be <= subcommand_size - 0x10 - // BC0-compressed data follows here (use bc0_decompress from Compression.hh) + uint8_t data[0]; // BC0-compressed data follows here (see bc0_decompress) +} __packed__; + +// Decompressed format is a list of these +struct G_SyncEnemyState_6x6B_Entry_Decompressed { + le_uint32_t unknown_a1; // Possibly some kind of flags + // enemy_index is not the same as enemy_id, unfortunately - the enemy_id sent + // in the 6x76 command when an enemy is killed does not match enemy_index + le_uint16_t enemy_index; // FFFF = enemy is dead + le_uint16_t damage_taken; + uint8_t unknown_a4; + uint8_t unknown_a5; + uint8_t unknown_a6; + uint8_t unknown_a7; +} __packed__; + +// 6x6C: Sync object state (used while loading into game; same header format as 6E) +// Compressed format is the same as 6x6B. + +// Decompressed format is a list of these +struct G_SyncObjectState_6x6C_Entry_Decompressed { + le_uint16_t flags; + le_uint16_t object_index; +} __packed__; + +// 6x6D: Sync item state (used while loading into game; same header format as 6E) +// Compressed format is the same as 6x6B. + +struct G_SyncItemState_6x6D_Decompressed { + // Note: 16 vs. 15 is not a bug here - there really is an extra field in the + // total drop count vs. the floor item count. Despite this, Pioneer 2 or Lab + // (area 0) isn't included in total_items_dropped_per_area (so Forest 1 is [0] + // in that array) but it is included in floor_item_count_per_area (so Forest 1 + // is [1] there). + parray total_items_dropped_per_area; + // Only [0]-[3] in this array are ever actually used in normal gameplay, but + // the client fills in all 12 of these with reasonable values. + parray next_item_id_per_player; + parray floor_item_count_per_area; + struct FloorItem { + le_uint16_t area; + le_uint16_t unknown_a1; + le_float x; + le_float z; + le_uint16_t unknown_a2; + // The drop number is scoped to the area and increments by 1 each time an + // item is dropped. The last item dropped in each area has drop_number equal + // to total_items_dropped_per_area[area - 1] - 1. + le_uint16_t drop_number; + ItemData data; + } __packed__; + // Variable-length field follows: + // FloorItem items[sum(floor_item_count_per_area)]; +} __packed__; + +// 6x6E: Sync flag state (used while loading into game) +// Compressed format is the same as 6x6B. + +struct G_SyncFlagState_6x6E_Decompressed { + // The three unknowns here are the sizes (in bytes) of three fields + // immediately following this structure. It is currently unknown what these + // fields represent. The three unknown fields always sum to the size field. + le_uint16_t size; + le_uint16_t unknown_a1; + le_uint16_t unknown_a2; + le_uint16_t unknown_a3; + // Three variable-length fields follow here. They are in the same order as the + // unknown fields above. } __packed__; // 6x6F: Unknown (used while loading into game) @@ -4278,10 +4341,8 @@ struct G_Unknown_6x6F { // 6x70: Sync player disp data and inventory (used while loading into game) // Annoyingly, they didn't use the same format as the 65/67/68 commands here, // and instead rearranged a bunch of things. -// TODO: Some missing fields should be easy to find in the future (e.g. when the -// sending player doesn't have 0 meseta, for example) -struct G_Unknown_6x70 { +struct G_SyncPlayerDispAndInventory_6x70 { G_ExtendedHeader header; // Offsets in this struct are relative to the overall command header /* 000C */ parray unknown_a1; @@ -5101,7 +5162,7 @@ struct G_BankAction_BB_6xBD { } __packed__; // 6xBE: Sound chat (Episode 3; not Trial Edition) -// This appears to be the only subcommand ever sent with the CB command. +// This is the only subcommand ever sent with the CB command. struct G_SoundChat_GC_Ep3_6xBE { G_UnusedHeader header; diff --git a/src/Compression.cc b/src/Compression.cc index f48a7595..aad18e04 100644 --- a/src/Compression.cc +++ b/src/Compression.cc @@ -602,7 +602,11 @@ string bc0_compress( // output pointer to any arbitrary address. string bc0_decompress(const string& data) { - StringReader r(data); + return bc0_decompress(data.data(), data.size()); +} + +string bc0_decompress(const void* data, size_t size) { + StringReader r(data, size); StringWriter w; // Unlike PRS, BC0 uses a memo which "rolls over" every 0x1000 bytes. The @@ -621,7 +625,7 @@ string bc0_decompress(const string& data) { // The low byte of this value contains the control stream data; the high bits // specify which low bits are valid. When the last 1 is shifted out of the - // high bit, we need to read a new control stream byte to get the next set of + // high byte, we need to read a new control stream byte to get the next set of // control bits. uint16_t control_stream_bits = 0x0000; diff --git a/src/Compression.hh b/src/Compression.hh index 48406635..368c2c9c 100644 --- a/src/Compression.hh +++ b/src/Compression.hh @@ -150,3 +150,4 @@ void prs_disassemble(FILE* stream, const std::string& data); // Compresses and decompresses data using the BC0 algorithm. std::string bc0_compress(const std::string& data, std::function progress_fn = nullptr); std::string bc0_decompress(const std::string& data); +std::string bc0_decompress(const void* data, size_t size); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 5e48905b..860a055a 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -7,6 +7,7 @@ #include #include "Client.hh" +#include "Compression.hh" #include "Items.hh" #include "Lobby.hh" #include "Loggers.hh" @@ -169,6 +170,22 @@ static void on_forward_check_game(shared_ptr, forward_subcommand(l, c, command, flag, data); } +static void on_forward_sync_game_state(shared_ptr, + shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, + const string& data) { + if (!l->is_game() || !l->any_client_loading()) { + return; + } + + const auto& cmd = check_size_sc( + data, sizeof(G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E), 0xFFFF); + if (cmd.compressed_size > data.size() - sizeof(cmd)) { + throw runtime_error("compressed end offset is beyond end of command"); + } + + forward_subcommand(l, c, command, flag, data); +} + static void on_forward_check_game_loading(shared_ptr, shared_ptr l, shared_ptr c, uint8_t command, uint8_t flag, const string& data) { @@ -1462,10 +1479,10 @@ subcommand_handler_t subcommand_handlers[0x100] = { /* 68 */ on_forward_check_size_game, // Telepipe/Ryuker /* 69 */ on_forward_check_size_game, /* 6A */ on_forward_check_size_game, - /* 6B */ on_forward_check_game_loading, - /* 6C */ on_forward_check_game_loading, - /* 6D */ on_forward_check_game_loading, - /* 6E */ on_forward_check_game_loading, + /* 6B */ on_forward_sync_game_state, + /* 6C */ on_forward_sync_game_state, + /* 6D */ on_forward_sync_game_state, + /* 6E */ on_forward_sync_game_state, /* 6F */ on_forward_check_game_loading, /* 70 */ on_forward_check_game_loading, /* 71 */ on_forward_check_game_loading,