refine many subcommand formats

This commit is contained in:
Martin Michelsen
2025-01-26 09:47:19 -08:00
parent 65a1b97093
commit 78b7bfac70
9 changed files with 528 additions and 321 deletions
+143 -132
View File
@@ -3834,7 +3834,7 @@ struct S_SetShutdownCommand_BB_01EF {
// are laid out similarly as above. These structs start with G_ to indicate that
// they are (usually) bidirectional, and are (usually) generated by clients and
// consumed by clients. Generally in newserv source, these commands are referred
// to as (for example) 6x02, etc., referencing the fact that they are almost
// to as (for example) 6x0B, etc., referencing the fact that they are almost
// always sent via a command starting with the hex digit 6.
// All game subcommands have the same header format, which is one of:
@@ -3849,11 +3849,15 @@ struct S_SetShutdownCommand_BB_01EF {
// Multiple subcommands may be sent in the same 6x command. It seems the client
// never sends commands like this, but newserv generates commands containing
// multiple subcommands in some situations (for example, the implementation of
// infinite HP does this).
// infinite HP does this). All versions of the client support this behavior.
// If any subcommand or group thereof is longer than 0x400 bytes, the 6C or 6D
// commands must be used. The 60 and 62 commands exhibit undefined behavior if
// command must be used. The 60 and 62 commands exhibit undefined behavior if
// this limit is exceeded.
// Many subcommands have different numbers on DC NTE and 11/2000, or simply
// don't exist on those versions. See the subcommand_definitions table in
// ReceiveSubcommands.cc for a full listing.
// Some subcommands are "protected" on V3 and later (not including GC NTE);
// these commands are blocked by the client if they affect the local player. If
// a V3 or later client receives a protected subcommand that would affect its
@@ -3994,23 +3998,27 @@ struct G_SymbolChat_6x07 {
// 6x08: Invalid subcommand
// 6x09: Unknown
// header.entity_id is expected to be an enemy ID.
// header.entity_id is expected to be an enemy ID, but is also expected to be
// in the range [0x00, 0x80) since it writes to an array of 0x80 entries. This
// duality makes no sense because enemy IDs are greater than or equal to
// 0x1000, so any valid enemy ID would be far outside the array's range, and
// the write is not bounds-checked. For this reason, newserv unconditionally
// blocks this command.
struct G_Unknown_6x09 {
G_EntityIDHeader header;
} __packed_ws__(G_Unknown_6x09, 4);
// 6x0A: Update enemy state
// In Ultimate mode, the low 7 bits of game_flags are ignored, and 6x9C is used
// to update those instead.
template <bool BE>
struct G_UpdateEnemyStateT_6x0A {
G_EntityIDHeader header;
le_uint16_t enemy_index = 0; // [0, 0xB50)
le_uint16_t total_damage = 0;
// Flags:
// 00000400 - should play hit animation
// 00000800 - is dead
typename std::conditional_t<BE, be_uint32_t, le_uint32_t> flags = 0;
typename std::conditional_t<BE, be_uint32_t, le_uint32_t> game_flags = 0;
} __packed__;
using G_UpdateEnemyState_GC_6x0A = G_UpdateEnemyStateT_6x0A<true>;
using G_UpdateEnemyState_DC_PC_XB_BB_6x0A = G_UpdateEnemyStateT_6x0A<false>;
@@ -4060,25 +4068,25 @@ struct G_RemoveStatusEffect_6x0D {
le_uint32_t unused = 0;
} __packed_ws__(G_RemoveStatusEffect_6x0D, 0x0C);
// 6x0E: Unknown (protected on V3/V4)
// 6x0E: Clear all negative status effects (protected on V3/V4)
// It seems that the client never sends this command.
struct G_Unknown_6x0E {
struct G_ClearNegativeStatusEffects_6x0E {
G_ClientIDHeader header;
} __packed_ws__(G_Unknown_6x0E, 4);
} __packed_ws__(G_ClearNegativeStatusEffects_6x0E, 4);
// 6x0F: Invalid subcommand
// 6x10: Unknown (not valid on Episode 3)
// 6x10: Dragon boss actions (not valid on Episode 3)
// 6x11: Dragon boss actions (not valid on Episode 3)
// It seems that the client never sends these commands.
struct G_Unknown_6x10_6x11_6x14 {
struct G_DragonBossActions_6x10_6x11 {
G_EntityIDHeader header;
le_uint16_t unknown_a2 = 0;
le_uint16_t unknown_a3 = 0;
le_uint32_t unknown_a4 = 0;
} __packed_ws__(G_Unknown_6x10_6x11_6x14, 0x0C);
// 6x11: Unknown (not valid on Episode 3)
// Same format as 6x10
} __packed_ws__(G_DragonBossActions_6x10_6x11, 0x0C);
// 6x12: Dragon boss actions (not valid on Episode 3)
@@ -4105,7 +4113,13 @@ struct G_DeRolLeBossActions_6x13 {
} __packed_ws__(G_DeRolLeBossActions_6x13, 8);
// 6x14: De Rol Le boss actions (not valid on Episode 3)
// Same format as 6x10
struct G_DeRolLeBossActions_6x14 {
G_EntityIDHeader header;
le_uint16_t unknown_a2 = 0;
le_uint16_t unknown_a3 = 0;
le_uint32_t unknown_a4 = 0;
} __packed_ws__(G_DeRolLeBossActions_6x14, 0x0C);
// 6x15: Vol Opt boss actions (not valid on Episode 3)
@@ -4188,7 +4202,7 @@ struct G_SetPosition_6x20 {
G_ClientIDHeader header;
le_int32_t floor = 0;
VectorXYZF pos;
le_uint32_t unknown_a1 = 0;
le_uint32_t angle_y = 0;
} __packed_ws__(G_SetPosition_6x20, 0x18);
// 6x21: Inter-level warp (protected on V3/V4)
@@ -4210,7 +4224,7 @@ struct G_SetPlayerVisibility_6x22_6x23 {
struct G_TeleportPlayer_6x24 {
G_ClientIDHeader header;
le_uint32_t unknown_a1 = 0;
le_uint32_t angle_y = 0;
VectorXYZF pos;
} __packed_ws__(G_TeleportPlayer_6x24, 0x14);
@@ -4266,8 +4280,9 @@ struct G_DropItem_6x2A {
VectorXYZF pos;
} __packed_ws__(G_DropItem_6x2A, 0x18);
// 6x2B: Create item in inventory (e.g. via tekker or bank withdraw) (protected on V3/V4)
// 6x2B: Create item in inventory (tekker/bank) (protected on V3/V4)
// On BB, the 6xBE command is used instead of 6x2B to create inventory items.
// If equip_item is nonzero, the item is equipped immediately.
struct G_CreateInventoryItem_DC_6x2B {
G_ClientIDHeader header;
@@ -4276,19 +4291,22 @@ struct G_CreateInventoryItem_DC_6x2B {
struct G_CreateInventoryItem_PC_V3_BB_6x2B : G_CreateInventoryItem_DC_6x2B {
uint8_t unused1 = 0;
uint8_t unknown_a2 = 0; // Does something with equipped items, but logic isn't the same as 6x25
uint8_t equip_item = 0;
parray<uint8_t, 2> unused2 = 0;
} __packed_ws__(G_CreateInventoryItem_PC_V3_BB_6x2B, 0x1C);
// 6x2C: Talk to NPC (protected on V3/V4)
// This updates G_6x70_NPCTalkState in the TObjPlayer struct, but the format is
// not the same. The names here match the fields in G_6x70_NPCTalkState, hence
// the unusual numbering.
struct G_TalkToNPC_6x2C {
G_ClientIDHeader header;
le_uint16_t unknown_a1 = 0;
le_uint16_t unknown_a2 = 0;
le_float unknown_a3 = 0.0f;
le_float unknown_a4 = 0.0f;
le_float unknown_a5 = 0.0f;
le_float unknown_a6 = 0.0f;
le_float unknown_a4 = 0.0f;
} __packed_ws__(G_TalkToNPC_6x2C, 0x14);
// 6x2D: Done talking to NPC (protected on V3/V4)
@@ -4308,9 +4326,9 @@ struct G_SetOrClearPlayerFlags_6x2E {
// 6x2F: Change player HP
struct G_ChangePlayerHP_6x2F {
G_UnusedHeader header;
le_uint32_t type = 0; // 0 = set HP, 1 = add/subtract HP, 2 = add/sub fixed HP
le_uint16_t amount = 0;
G_ClientIDHeader header;
le_uint32_t type = 0; // 0 = set HP, 1 = add/subtract HP, 2 = reset to max HP
le_int16_t amount = 0;
le_uint16_t client_id = 0;
} __packed_ws__(G_ChangePlayerHP_6x2F, 0x0C);
@@ -4355,12 +4373,19 @@ struct G_RevivePlayer_6x33 {
} __packed_ws__(G_RevivePlayer_6x33, 8);
// 6x34: Unknown
// This subcommand is completely ignored (at least, by PSO GC).
// This subcommand is ignored by all versions of PSO.
// 6x35: Invalid subcommand
// 6x36: Unknown (supported; game only)
// This subcommand is completely ignored (at least, by PSO GC).
// This subcommand is completely ignored on V3.
// TODO: It is not ignored on V2, and presumably earlier versions as well.
// Figure out what it does. It's something related to PBs.
struct G_Unknown_6x36 {
// The parameter seems to be expected to be in the range [0, 12).
G_ParameterHeader header;
} __packed_ws__(G_Unknown_6x36, 4);
// 6x37: Photon blast (protected on V3/V4)
@@ -4379,22 +4404,26 @@ struct G_DonateToPhotonBlast_6x38 {
} __packed_ws__(G_DonateToPhotonBlast_6x38, 8);
// 6x39: Photon blast ready (protected on V3/V4)
// This is sent when a player's PB meter reaches 100.
struct G_PhotonBlastReady_6x38 {
struct G_SetPhotonBlastReadyFlag_6x39 {
G_ClientIDHeader header;
} __packed_ws__(G_PhotonBlastReady_6x38, 4);
} __packed_ws__(G_SetPhotonBlastReadyFlag_6x39, 4);
// 6x3A: Unknown (supported; game only) (protected on V3/V4)
// 6x3A: Clear photon blast ready flag (protected on V3/V4)
// This is sent when a player's PB meter drops below 100. This can happen if
// a player donates to a PB instead of joining it.
struct G_Unknown_6x3A {
struct G_ClearPhotonBlastReadyFlag_6x3A {
G_ClientIDHeader header;
} __packed_ws__(G_Unknown_6x3A, 4);
} __packed_ws__(G_ClearPhotonBlastReadyFlag_6x3A, 4);
// 6x3B: Unknown (supported; lobby & game) (protected on V3/V4)
// 6x3B: Clear temporary photon blast state flags (protected on V3/V4)
// This is usually sent when a player changes floors.
struct G_Unknown_6x3B {
struct G_ClearTemporaryPhotonBlastStateFlags_6x3B {
G_ClientIDHeader header;
} __packed_ws__(G_Unknown_6x3B, 4);
} __packed_ws__(G_ClearTemporaryPhotonBlastStateFlags_6x3B, 4);
// 6x3C: Unknown (DCv1 and earlier)
// This command has a handler, but it does nothing, even on DC NTE.
@@ -4405,7 +4434,7 @@ struct G_Unknown_6x3B {
struct G_StopAtPosition_6x3E {
G_ClientIDHeader header;
le_uint16_t unknown_a1 = 0;
le_uint16_t is_near_enemy = 0; // Sets game_flag 0x00100000 (v3)
le_uint16_t angle = 0;
le_int16_t floor = 0;
le_int16_t room = 0;
@@ -4416,7 +4445,7 @@ struct G_StopAtPosition_6x3E {
struct G_SetPosition_6x3F {
G_ClientIDHeader header;
le_uint16_t unknown_a1 = 0;
le_uint16_t is_near_enemy = 0; // Sets game_flag 0x00100000 (v3)
le_uint16_t angle = 0;
le_int16_t floor = 0;
le_int16_t room = 0;
@@ -4480,9 +4509,9 @@ struct G_CastTechnique_6x47 {
uint8_t unused = 0; // Must not be negative
// Note: The level here isn't the actual tech level that was cast, if the
// actual level is > 15. In that case, a 6x8D is sent first, which contains
// the additional level which is added to this level at cast time. They
// probably did this for legacy reasons when dealing with v1/v2
// compatibility, and never cleaned it up.
// the additional level which is added to this level at cast time. Sega
// probably did this as part of implementing v1/v2 compatibility and never
// cleaned it up.
uint8_t level = 0;
uint8_t target_count = 0; // Must be in [0, 10]
// The client may send a shorter command if not all of these are used.
@@ -4533,7 +4562,7 @@ struct G_HitByEnemy_6x4B_6x4C {
struct G_PlayerDied_6x4D {
G_ClientIDHeader header;
le_uint32_t unknown_a1 = 0;
le_uint32_t death_flags = 0; // Same as 6x70's death_flags field
} __packed_ws__(G_PlayerDied_6x4D, 8);
// 6x4E: Player is dead can be revived (protected on V3/V4)
@@ -4553,7 +4582,7 @@ struct G_PlayerRevived_6x4F {
struct G_SwitchInteraction_6x50 {
G_ClientIDHeader header;
le_uint32_t unknown_a1 = 0;
le_uint32_t angle_y = 0;
} __packed_ws__(G_SwitchInteraction_6x50, 8);
// 6x51: Set player angle
@@ -4597,8 +4626,8 @@ struct G_Unknown_6x54 {
struct G_IntraMapWarp_6x55 {
G_ClientIDHeader header;
le_uint32_t unknown_a1 = 0;
VectorXYZF pos1;
VectorXYZF pos2;
VectorXYZF pos;
VectorXYZF unknown_a2;
} __packed_ws__(G_IntraMapWarp_6x55, 0x20);
// 6x56: Set player position and angle (protected on V3/V4)
@@ -4656,7 +4685,7 @@ struct G_PickUpItemRequest_6x5A {
struct G_DropStackedItem_DC_6x5D {
G_ClientIDHeader header;
le_uint16_t floor = 0;
le_uint16_t unknown_a2 = 0; // Corresponds to FloorItem::unknown_a2
le_uint16_t room_id = 0;
VectorXZF pos;
ItemData item_data;
} __packed_ws__(G_DropStackedItem_DC_6x5D, 0x24);
@@ -4679,7 +4708,7 @@ struct FloorItem {
/* 01 */ uint8_t source_type = 0; // 1 = enemy, 2 = box
/* 02 */ le_uint16_t entity_index = 0; // < 0x0B50 if source_type == 1; otherwise < 0x0BA0
/* 04 */ VectorXZF pos;
/* 0C */ le_uint16_t unknown_a2 = 0;
/* 0C */ le_uint16_t room_id = 0;
// The drop number is scoped to the floor and increments by 1 each time an
// item is dropped. The last item dropped in each floor has drop_number equal
// to total_items_dropped_per_floor[floor - 1] - 1.
@@ -4763,13 +4792,7 @@ struct G_TriggerSetEvent_6x67 {
struct G_SetTelepipeState_6x68 {
G_UnusedHeader header;
le_uint16_t client_id2 = 0;
le_uint16_t floor = 0;
le_uint16_t unknown_b1 = 0;
uint8_t unknown_b2 = 0;
uint8_t unknown_b3 = 0;
VectorXYZF pos;
le_uint32_t unknown_a3 = 0;
TelepipeState state;
} __packed_ws__(G_SetTelepipeState_6x68, 0x1C);
// 6x69: NPC control
@@ -4915,7 +4938,7 @@ struct G_SetQuestFlags_V2_V3_6x6F {
struct G_SetQuestFlags_BB_6x6F {
G_UnusedHeader header;
QuestFlags quest_flags;
// If use_apply_mask is 1, only the flags set in bb_quest_flag_apply_mask
// If use_apply_mask is 1, only the flags set in BB_QUEST_FLAG_APPLY_MASK
// (in PlayerSubordinates.cc) are overwritten on the receiving client's end.
// The client always sends this with use_apply_mask = 1.
le_uint32_t use_apply_mask = 1;
@@ -4927,33 +4950,11 @@ struct G_SetQuestFlags_BB_6x6F {
// structure also includes transient state (e.g. current HP).
struct G_6x70_Sub_Telepipe {
/* 00 */ le_uint16_t owner_client_id = 0xFFFF;
/* 02 */ le_uint16_t floor = 0;
/* 04 */ le_uint32_t unknown_a1 = 0;
/* 08 */ VectorXYZF pos;
/* 14 */ le_uint32_t unknown_a3 = 0;
/* 00 */ TelepipeState state;
/* 18 */ le_uint32_t unknown_a4 = 0x0000FFFF;
/* 1C */
} __packed_ws__(G_6x70_Sub_Telepipe, 0x1C);
struct G_6x70_Sub_UnknownA1 {
// This is used in all versions of this command except DCNTE and 11/2000.
/* 00 */ le_uint16_t unknown_a1 = 0;
/* 02 */ le_uint16_t unknown_a2 = 0;
/* 04 */ le_uint32_t unknown_a3 = 0;
/* 08 */ le_float unknown_a4 = 0.0f;
/* 0C */ le_uint32_t unknown_a5 = 0;
/* 10 */ le_uint32_t unknown_a6 = 0;
/* 14 */
} __packed_ws__(G_6x70_Sub_UnknownA1, 0x14);
struct G_6x70_StatusEffectState {
/* 00 */ le_uint32_t effect_type = 0;
/* 04 */ le_float multiplier = 0.0f;
/* 08 */ le_uint32_t remaining_frames = 0;
/* 0C */
} __packed_ws__(G_6x70_StatusEffectState, 0x0C);
struct G_6x70_Base_DCNTE {
/* 0000 */ le_uint16_t client_id = 0;
/* 0002 */ le_uint16_t room_id = 0;
@@ -4974,10 +4975,10 @@ struct G_SyncPlayerDispAndInventory_DCNTE_6x70 {
/* 0030 */ le_uint32_t unknown_a5 = 0;
/* 0034 */ le_uint32_t unknown_a6 = 0;
/* 0038 */ G_6x70_Sub_Telepipe telepipe;
/* 0054 */ le_uint32_t unknown_a8 = 0;
/* 0058 */ parray<uint8_t, 0x10> unknown_a9;
/* 0054 */ le_uint32_t death_flags = 0;
/* 0058 */ NPCTalkState_DCProtos npc_talk_state;
/* 0068 */ le_uint32_t area = 0;
/* 006C */ le_uint32_t flags2 = 0;
/* 006C */ le_uint32_t game_flags = 0;
/* 0070 */ PlayerVisualConfig visual;
/* 00C0 */ PlayerStats stats;
/* 00E4 */ le_uint32_t num_items = 0;
@@ -4993,10 +4994,10 @@ struct G_SyncPlayerDispAndInventory_DC112000_6x70 {
/* 0032 */ le_uint16_t bonus_tp_from_materials = 0;
/* 0034 */ parray<uint8_t, 0x10> unknown_a5;
/* 0044 */ G_6x70_Sub_Telepipe telepipe;
/* 0060 */ le_uint32_t unknown_a8 = 0;
/* 0064 */ parray<uint8_t, 0x10> unknown_a9;
/* 0060 */ le_uint32_t death_flags = 0;
/* 0064 */ NPCTalkState_DCProtos npc_talk_state;
/* 0074 */ le_uint32_t area = 0;
/* 0078 */ le_uint32_t flags2 = 0;
/* 0078 */ le_uint32_t game_flags = 0;
/* 007C */ PlayerVisualConfig visual;
/* 00CC */ PlayerStats stats;
/* 00F0 */ le_uint32_t num_items = 0;
@@ -5008,21 +5009,21 @@ struct G_6x70_Base_V1 {
/* 0000 */ G_6x70_Base_DCNTE base;
/* 0024 */ le_uint16_t bonus_hp_from_materials = 0;
/* 0026 */ le_uint16_t bonus_tp_from_materials = 0;
/* 0028 */ G_6x70_StatusEffectState permanent_status_effect;
/* 0034 */ G_6x70_StatusEffectState temporary_status_effect;
/* 0040 */ G_6x70_StatusEffectState attack_status_effect;
/* 004C */ G_6x70_StatusEffectState defense_status_effect;
/* 0058 */ G_6x70_StatusEffectState unused_status_effect;
/* 0028 */ StatusEffectState permanent_status_effect;
/* 0034 */ StatusEffectState temporary_status_effect;
/* 0040 */ StatusEffectState attack_status_effect;
/* 004C */ StatusEffectState defense_status_effect;
/* 0058 */ StatusEffectState unused_status_effect;
/* 0064 */ le_uint32_t language = 0;
/* 0068 */ le_uint32_t player_tag = 0;
/* 006C */ le_uint32_t guild_card_number = 0;
/* 0070 */ le_uint32_t unknown_a6 = 0; // Probably battle-related (assigned together with battle_team_number)
/* 0074 */ le_uint32_t battle_team_number = 0;
/* 0078 */ G_6x70_Sub_Telepipe telepipe;
/* 0094 */ le_uint32_t unknown_a8 = 0;
/* 0098 */ G_6x70_Sub_UnknownA1 unknown_a9;
/* 0094 */ le_uint32_t death_flags = 0; // Only a few bits are used. 4 = player is dead
/* 0098 */ NPCTalkState npc_talk_state;
/* 00AC */ le_uint32_t area = 0;
/* 00B0 */ le_uint32_t flags2 = 0;
/* 00B0 */ le_uint32_t game_flags = 0;
/* 00B4 */ parray<uint8_t, 0x14> technique_levels_v1 = 0xFF; // Last byte is uninitialized
/* 00C8 */ PlayerVisualConfig visual;
/* 0118 */
@@ -5166,23 +5167,22 @@ struct G_GogoBall_6x79 {
G_UnusedHeader header;
le_uint32_t unknown_a1 = 0;
le_uint32_t unknown_a2 = 0;
le_float unknown_a3 = 0.0f;
le_float unknown_a4 = 0.0f;
VectorXZF ball_pos;
uint8_t unknown_a5 = 0;
parray<uint8_t, 3> unused;
} __packed_ws__(G_GogoBall_6x79, 0x18);
// 6x7A: Unknown (protected on V3/V4)
// 6x7A: Enable Stealth Suit effect (protected on V3/V4)
struct G_Unknown_6x7A {
struct G_EnableStealthSuitEffect_6x7A {
G_ClientIDHeader header;
} __packed_ws__(G_Unknown_6x7A, 4);
} __packed_ws__(G_EnableStealthSuitEffect_6x7A, 4);
// 6x7B: Unknown (protected on V3/V4)
// 6x7B: Disable Stealth Suit effect (protected on V3/V4)
struct G_Unknown_6x7B {
struct G_DisableStealthSuitEffect_6x7B {
G_ClientIDHeader header;
} __packed_ws__(G_Unknown_6x7B, 4);
} __packed_ws__(G_DisableStealthSuitEffect_6x7B, 4);
// 6x7C: Set Challenge records (not valid on Episode 3)
@@ -5225,7 +5225,7 @@ struct G_SetBattleModeData_6x7D {
} __packed_ws__(G_SetBattleModeData_6x7D, 0x18);
// 6x7E: Unknown (not valid on Episode 3)
// This subcommand is completely ignored (at least, by PSO GC).
// This subcommand is valid on DC 08/2001 (v2) and later, but it does nothing.
// 6x7F: Battle scores and places (not valid on Episode 3)
@@ -5292,26 +5292,29 @@ struct G_VolOptBossActions_6x84 {
le_uint16_t unused = 0;
} __packed_ws__(G_VolOptBossActions_6x84, 0x10);
// 6x85: Unknown (supported; game only; not valid on Episode 3)
// 6x85: Vol Opt phase 2 boss actions (not valid on Episode 3)
struct G_Unknown_6x85 {
struct G_VolOptPhase2BossActions_6x85 {
G_UnusedHeader header;
le_uint16_t unknown_a1 = 0; // Command is ignored unless this is 0
parray<le_uint16_t, 7> unknown_a2; // Only the first 3 appear to be used
} __packed_ws__(G_Unknown_6x85, 0x14);
} __packed_ws__(G_VolOptPhase2BossActions_6x85, 0x14);
// 6x86: Hit destructible object (not valid on Episode 3)
// This command only has an effect for TOVS2Wall01 objects.
struct G_HitDestructibleObject_6x86 : G_UpdateObjectState_6x0B {
le_uint16_t unknown_a3 = 0;
le_uint16_t unknown_a4 = 0;
le_uint16_t is_destroyed = 0;
le_uint16_t remaining_hits = 0;
} __packed_ws__(G_HitDestructibleObject_6x86, 0x10);
// 6x87: Shrink player (protected on V3/V4)
// This command is sent when the F838 quest opcode is executed.
struct G_ShrinkPlayer_6x87 {
G_ClientIDHeader header;
le_float unknown_a1 = 0.0f;
// This field contains the value set by the preceding F8A7 quest opcode.
le_float size = 1.0f;
} __packed_ws__(G_ShrinkPlayer_6x87, 8);
// 6x88: Restore shrunken player (protected on V3/V4)
@@ -5381,24 +5384,24 @@ struct G_SetPlayerBattleTeam_6x90 {
le_uint32_t team_number = 0; // 0 or 1
} __packed_ws__(G_SetPlayerBattleTeam_6x90, 8);
// 6x91: Unknown (supported; game only)
// TODO: Deals with TOAttackableCol objects. Figure out exactly what it does.
// 6x91: Hit destructible object
// This command only has an effect for TOAttackableCol objects.
struct G_UpdateAttackableColState_6x91 : G_UpdateObjectState_6x0B {
le_uint16_t unknown_a3 = 0;
le_uint16_t unknown_a4 = 0;
le_uint16_t is_destroyed = 0;
le_uint16_t remaining_hits = 0;
le_uint16_t switch_flag_num = 0;
uint8_t should_set = 0; // The switch flag is only set if this is equal to 1; otherwise it's cleared
uint8_t should_set = 0; // The switch flag is only set if this is equal to 1
uint8_t floor = 0;
} __packed_ws__(G_UpdateAttackableColState_6x91, 0x14);
// 6x92: Unknown (not valid on Episode 3)
// This does something with the TObjOnlineEndingHexMove object. TODO: Figure
// out exactly what.
// TODO: It looks like this sets the heights of players in the online victory
// screen? Figure out if this is actually what it does.
struct G_Unknown_6x92 {
G_UnusedHeader header;
le_uint32_t unknown_a1 = 0;
le_uint32_t client_id = 0;
le_float unknown_a2 = 0.0f;
} __packed_ws__(G_Unknown_6x92, 0x0C);
@@ -5480,14 +5483,16 @@ struct G_LevelUpAllTechniques_6x9B {
parray<uint8_t, 3> unused;
} __packed_ws__(G_LevelUpAllTechniques_6x9B, 8);
// 6x9C: Unknown (supported; game only; not valid on Episode 3)
// This command only has an effect in Ultimate mode.
// TODO: Figure out what this does.
// 6x9C: Set enemy low game flags (not valid on Episode 3)
// This command only has an effect in Ultimate mode. It sets the low 7 bits of
// game_flags (those that match 0x0000003F).
struct G_Unknown_6x9C {
struct G_SetEnemyLowGameFlagsUltimate_6x9C {
G_EntityIDHeader header;
le_uint32_t unknown_a1 = 0;
} __packed_ws__(G_Unknown_6x9C, 8);
// A virtual function is called on the enemy if low_game_flags is equal to
// any of 0x02, 0x04, 0x10, or 0x20.
le_uint32_t low_game_flags = 0;
} __packed_ws__(G_SetEnemyLowGameFlagsUltimate_6x9C, 8);
// 6x9D: Set dead flag (Challenge mode; not valid on Episode 3)
// This command causes the specified client to appear in the dead players list
@@ -5641,13 +5646,12 @@ struct G_Unknown_GCNTE_6xAB {
struct G_CreateLobbyChair_6xAB {
G_ClientIDHeader header;
le_uint16_t unknown_a1 = 0;
le_uint16_t unknown_a2 = 0;
le_uint16_t unused = 0;
le_uint16_t flags = 0; // Only the low two bits are used
} __packed_ws__(G_CreateLobbyChair_6xAB, 8);
// 6xAC: Unknown (not valid on pre-V3) (protected on V3/V4)
// This command appears to be different on GC NTE than on any other version. It
// also seems that no version (except perhaps GC NTE) ever sends this command.
// 6xAC: Unknown (not valid on pre-V3)
// This command appears to be different on GC NTE than on any other version.
struct G_Unknown_GCNTE_6xAC {
G_EntityIDHeader header;
@@ -5656,11 +5660,17 @@ struct G_Unknown_GCNTE_6xAC {
le_uint32_t unknown_a3 = 0;
} __packed_ws__(G_Unknown_GCNTE_6xAC, 0x0C);
struct G_Unknown_6xAC {
// 6xAC: Delete multiple inventory items (protected on V3/V4)
// This command appears to delete multiple items from a player's inventory.
// It's not clear when or why this would be used; the client never sends it.
// The disassembly also is somewhat confusing, and it's not clear that it even
// works properly.
struct G_DeleteMultipleInventoryItems_6xAC {
G_ClientIDHeader header;
le_uint32_t num_items = 0;
parray<le_uint32_t, 0x1E> item_ids;
} __packed_ws__(G_Unknown_6xAC, 0x80);
} __packed_ws__(G_DeleteMultipleInventoryItems_6xAC, 0x80);
// 6xAD: Olga Flow subordinate boss actions (not valid on pre-V3, Episode 3, or
// GC Trial Edition)
@@ -5704,14 +5714,15 @@ struct G_MoveLobbyChair_6xB0 {
// 6xB2: Play sound from player (not valid on pre-V3 or GC Trial Edition)
// This command is sent when a snapshot is taken on PSO GC, but it can be used
// to play any sound, centered on the local player. If localize is FFFF, then
// the sound is not centered on the local player and is just played globally.
// to play any sound. The sound is centered on the local player (even if the
// local player does not match client_id), or if client_id is FFFF, it is not
// localized at all and it just played globally.
struct G_PlaySoundFromPlayer_6xB2 {
G_UnusedHeader header;
uint8_t floor = 0;
uint8_t unused = 0;
le_uint16_t localize = 0;
le_uint16_t client_id = 0;
le_uint32_t sound_id = 0; // 0x00051720 = camera shutter sound
} __packed_ws__(G_PlaySoundFromPlayer_6xB2, 0x0C);