add formats for state sync commands

This commit is contained in:
Martin Michelsen
2023-06-11 08:51:35 -07:00
parent 65c08667cc
commit 0c12e6c4bc
4 changed files with 98 additions and 15 deletions
+70 -9
View File
@@ -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<G_UnusedHeader> 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<le_uint16_t, 16> 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<le_uint32_t, 12> next_item_id_per_player;
parray<le_uint32_t, 15> 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<G_UnusedHeader> header;
// Offsets in this struct are relative to the overall command header
/* 000C */ parray<le_uint16_t, 2> 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;
+6 -2
View File
@@ -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;
+1
View File
@@ -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<void(size_t, size_t)> progress_fn = nullptr);
std::string bc0_decompress(const std::string& data);
std::string bc0_decompress(const void* data, size_t size);
+21 -4
View File
@@ -7,6 +7,7 @@
#include <phosg/Strings.hh>
#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<ServerState>,
forward_subcommand(l, c, command, flag, data);
}
static void on_forward_sync_game_state(shared_ptr<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> 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<G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E>(
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<ServerState>,
shared_ptr<Lobby> l, shared_ptr<Client> 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,