diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index b2d2a6d2..1c8cf648 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -2066,7 +2066,7 @@ static void server_command_infinite_hp(shared_ptr c, const std::string&) bool enabled = c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED); send_text_message_printf(c, "$C6Infinite HP %s", enabled ? "enabled" : "disabled"); if (enabled && l->is_game()) { - send_remove_conditions(c); + send_remove_negative_conditions(c); } } @@ -2077,8 +2077,8 @@ static void proxy_command_infinite_hp(shared_ptr ses bool enabled = ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED); send_text_message_printf(ses->client_channel, "$C6Infinite HP %s", enabled ? "enabled" : "disabled"); if (enabled && ses->is_in_game) { - send_remove_conditions(ses->client_channel, ses->lobby_client_id); - send_remove_conditions(ses->server_channel, ses->lobby_client_id); + send_remove_negative_conditions(ses->client_channel, ses->lobby_client_id); + send_remove_negative_conditions(ses->server_channel, ses->lobby_client_id); } } diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 50b95fa9..c5d6f585 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -182,7 +182,7 @@ struct C_Login_Patch_04 { // 06 (S->C): Open file for writing struct S_OpenFile_Patch_06 { - le_uint32_t unknown_a1 = 0; + le_uint32_t unused = 0; le_uint32_t size = 0; pstring filename; } __packed_ws__(S_OpenFile_Patch_06, 0x38); @@ -343,6 +343,7 @@ struct S_ServerInitWithAfterMessageT_DC_PC_V3_02_17_91_9B { // 03 (C->S): Legacy register (non-BB) // Internal name: SndRegist +// TODO: Are the DCv1 and DCv2 formats the same as this structure? struct C_LegacyLogin_PC_V3_03 { /* 00 */ le_uint64_t unused = 0; // Same as unused field in 9D/9E @@ -395,9 +396,10 @@ struct S_ServerInitWithAfterMessageT_BB_03_9B { // Internal name: SndLogin2 // Curiously, there is a SndLogin3 function, but it does not send anything. // See comments on non-BB 03 (S->C). This is likely a relic of an older, -// now-unused sequence. Like 03, this command isn't used by any PSO version that -// newserv supports. +// now-unused sequence. Like 03, this command isn't used by any known PSO +// version. // header.flag is nonzero, but it's not clear what it's used for. +// TODO: Are the DCv1 and DCv2 formats the same as this structure? struct C_LegacyLogin_PC_V3_04 { /* 00 */ le_uint64_t unused1 = 0; // Same as unused field in 9D/9E @@ -3889,19 +3891,27 @@ struct G_ExtendedHeaderT { // 6x01: Invalid subcommand // 6x02: Unknown -// This subcommand is completely ignored on V3. -// TODO: It is not ignored on V1 and V2. Figure out what it does and document it. - // 6x03: Unknown -// This subcommand is completely ignored on V3. -// TODO: It is not ignored on V1 and V2. Figure out what it does and document it. +// These subcommands are completely ignored on V3 and later. +// On all known DC versions (NTE through V2), these commands writes their +// contents to a global array, but nothing reads from this array. + +struct G_Unknown_6x02_6x03 { + G_ClientIDHeader header; + le_uint16_t unknown_a1; + le_uint16_t unknown_a2; + le_uint32_t unknown_a3; + le_float unknown_a4; + le_float unknown_a5; +} __packed_ws__(G_Unknown_6x02_6x03, 0x14); // 6x04: Unknown +// TODO: This does something with TObjDoorKey objects struct G_Unknown_6x04 { G_ClientIDHeader header; - le_uint16_t unknown_a1 = 0; - le_uint16_t unknown_a2 = 0; + le_uint16_t door_key_token = 0; + le_uint16_t unused = 0; } __packed_ws__(G_Unknown_6x04, 8); // 6x05: Switch state changed @@ -4007,14 +4017,40 @@ struct G_UpdateObjectState_6x0B { le_uint32_t object_index = 0; } __packed_ws__(G_UpdateObjectState_6x0B, 0x0C); -// 6x0C: Add condition (poison/slow/etc.) (protected on V3/V4) -// 6x0D: Remove condition (poison/slow/etc.) (protected on V3/V4) +// 6x0C: Add status effect (poison/slow/etc.) (protected on V3/V4) -struct G_AddOrRemoveCondition_6x0C_6x0D { +struct G_AddStatusEffect_6x0C { G_ClientIDHeader header; - le_uint32_t unknown_a1 = 0; // Probably condition type - le_uint32_t unknown_a2 = 0; -} __packed_ws__(G_AddOrRemoveCondition_6x0C_6x0D, 0x0C); + // Each status effect has an assigned slot; there are 5 slots and each slot + // may only hold one effect at a time. (The last slot, slot 4, is unused.) + // If a new status effect is added to a slot that already contains one, the + // existing status effect is replaced. Non-technique status effects have + // fixed or indefinite durations; technique-based effects have durations + // based on the technique's level. + // Values for effect_type: + // 02 = Freeze (slot 1; 5 seconds) + // 03 = Shock (slot 1; 10 seconds) + // 07 = Clears negative status effects (healing ring?) + // 09 = Shifta (slot 2; ((level * 10) + 30) seconds; ATP (8.3 + 1.3 * level)%) + // 0A = Deband (slot 3; ((level * 10) + 30) seconds; DFP (8.3 + 1.3 * level)%) + // 0B = Jellen (slot 2; ((level * 10) + 30) seconds; ATP (8.3 + 1.3 * level)%) + // 0C = Zalure (slot 3; ((level * 10) + 30) seconds; DFP (8.3 + 1.3 * level)%) + // 0F = Poison (slot 0) + // 10 = Paralysis (slot 0; 7 seconds for enemies, indefinite for players) + // 11 = Slow (slot 1; 7 seconds) + // 12 = Confuse (slot 1; 10 seconds) + // Anything else = command is ignored + le_uint32_t effect_type = 0; + le_uint32_t level = 0; // Only used for Shifta/Deband/Jellen/Zalure +} __packed_ws__(G_AddStatusEffect_6x0C, 0x0C); + +// 6x0D: Clear status effect slot (protected on V3/V4) + +struct G_RemoveStatusEffect_6x0D { + G_ClientIDHeader header; + le_uint32_t slot = 0; // See 6x0C description for slot values + le_uint32_t unused = 0; +} __packed_ws__(G_RemoveStatusEffect_6x0D, 0x0C); // 6x0E: Unknown (protected on V3/V4) @@ -4220,7 +4256,7 @@ struct G_DeleteInventoryItem_6x29 { struct G_DropItem_6x2A { G_ClientIDHeader header; - le_uint16_t unknown_a1 = 0; // Should be 1... maybe amount? + le_uint16_t amount = 0; le_uint16_t floor = 0; le_uint32_t item_id = 0; le_float x = 0.0f; @@ -4238,7 +4274,7 @@ 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; + uint8_t unknown_a2 = 0; // Does something with equipped items, but logic isn't the same as 6x25 parray unused2 = 0; } __packed_ws__(G_CreateInventoryItem_PC_V3_BB_6x2B, 0x1C); @@ -4276,13 +4312,15 @@ struct G_ChangePlayerHP_6x2F { le_uint16_t client_id = 0; } __packed_ws__(G_ChangePlayerHP_6x2F, 0x0C); -// 6x30: Level up +// 6x30: Change player level +// On DC NTE, the updated stats aren't sent, and the client may only gain a +// single level at once. On other versions, this is not the case. -struct G_LevelUp_DCNTE_6x30 { +struct G_ChangePlayerLevel_DCNTE_6x30 { G_ClientIDHeader header; -} __packed_ws__(G_LevelUp_DCNTE_6x30, 4); +} __packed_ws__(G_ChangePlayerLevel_DCNTE_6x30, 4); -struct G_LevelUp_6x30 { +struct G_ChangePlayerLevel_6x30 { G_ClientIDHeader header; le_uint16_t atp = 0; le_uint16_t mst = 0; @@ -4291,22 +4329,22 @@ struct G_LevelUp_6x30 { le_uint16_t dfp = 0; le_uint16_t ata = 0; le_uint16_t level = 0; - le_uint16_t unknown_a1 = 0; // Must be 0 or 1 -} __packed_ws__(G_LevelUp_6x30, 0x14); + le_uint16_t is_level_down = 0; // If == 1, the text "Level Down" appears instead of "Level Up" +} __packed_ws__(G_ChangePlayerLevel_6x30, 0x14); -// 6x31: Resurrect player (protected on V3/V4) +// 6x31: Use medical center (protected on V3/V4) struct G_UseMedicalCenter_6x31 { G_ClientIDHeader header; } __packed_ws__(G_UseMedicalCenter_6x31, 4); -// 6x32: Resurrect player (Medical Center) +// 6x32: Revive player (Medical Center) -struct G_Unknown_6x32 { +struct G_MedicalCenterRevivePlayer_6x32 { G_UnusedHeader header; -} __packed_ws__(G_Unknown_6x32, 4); +} __packed_ws__(G_MedicalCenterRevivePlayer_6x32, 4); -// 6x33: Resurrect player (with Moon Atomizer) (protected on V3/V4) +// 6x33: Revive player (with Moon Atomizer) (protected on V3/V4) struct G_RevivePlayer_6x33 { G_ClientIDHeader header; @@ -4620,7 +4658,7 @@ struct G_PickUpItemRequest_6x5A { le_uint16_t unused = 0; } __packed_ws__(G_PickUpItemRequest_6x5A, 0x0C); -// 6x5B: Unknown (DCv1 and earlier) +// 6x5B: Unused (DCv1 and earlier) // This command has a handler, but it does nothing, even on DC NTE. // 6x5C: Destroy floor item @@ -4705,7 +4743,7 @@ struct G_ActivateMagEffect_6x61 { le_uint32_t effect_number = 0; } __packed_ws__(G_ActivateMagEffect_6x61, 0x0C); -// 6x62: Unknown +// 6x62: Unused // This command has a handler, but it does nothing even on DC NTE. // 6x63: Destroy floor item @@ -4718,10 +4756,10 @@ struct G_DestroyFloorItem_6x5C_6x63 { le_uint32_t floor = 0; } __packed_ws__(G_DestroyFloorItem_6x5C_6x63, 0x0C); -// 6x64: Unknown (not valid on Episode 3) +// 6x64: Unused (not valid on Episode 3) // This command has a handler, but it does nothing even on DC NTE. -// 6x65: Unknown (not valid on Episode 3) +// 6x65: Unused (not valid on Episode 3) // This command has a handler, but it does nothing even on DC NTE. // 6x66: Use star atomizer @@ -4918,7 +4956,7 @@ struct G_SetQuestFlags_BB_6x6F { // and instead rearranged a bunch of things. This is presumably because this // structure also includes transient state (e.g. current HP). -struct Telepipe6x70 { +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; @@ -4927,9 +4965,10 @@ struct Telepipe6x70 { /* 10 */ le_float z = 0.0f; /* 14 */ le_uint32_t unknown_a3 = 0; /* 18 */ le_uint32_t unknown_a4 = 0x0000FFFF; -} __packed_ws__(Telepipe6x70, 0x1C); + /* 1C */ +} __packed_ws__(G_6x70_Sub_Telepipe, 0x1C); -struct G_Unknown_6x70_SubA1 { +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; @@ -4937,18 +4976,19 @@ struct G_Unknown_6x70_SubA1 { /* 08 */ le_float unknown_a4 = 0.0f; /* 0C */ le_uint32_t unknown_a5 = 0; /* 10 */ le_uint32_t unknown_a6 = 0; -} __packed_ws__(G_Unknown_6x70_SubA1, 0x14); + /* 14 */ +} __packed_ws__(G_6x70_Sub_UnknownA1, 0x14); -struct G_Unknown_6x70_SubA2 { - // This is used in all versions of this command except DCNTE and 11/2000. - /* 00 */ le_uint32_t unknown_a1 = 0; - /* 04 */ le_float unknown_a2 = 0.0f; - /* 08 */ le_uint32_t unknown_a3 = 0; -} __packed_ws__(G_Unknown_6x70_SubA2, 0x0C); +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_SyncPlayerDispAndInventory_BaseDCNTE { +struct G_6x70_Base_DCNTE { /* 0000 */ le_uint16_t client_id = 0; - /* 0002 */ le_uint16_t unknown_a1 = 0; + /* 0002 */ le_uint16_t room_id = 0; /* 0004 */ le_uint32_t flags1 = 0; /* 0008 */ le_float x = 0.0f; /* 000C */ le_float y = 0.0f; @@ -4958,16 +4998,16 @@ struct G_SyncPlayerDispAndInventory_BaseDCNTE { /* 001C */ le_uint32_t angle_z = 0; /* 0020 */ le_uint16_t unknown_a3a = 0; /* 0022 */ le_uint16_t current_hp = 0; -} __packed_ws__(G_SyncPlayerDispAndInventory_BaseDCNTE, 0x24); +} __packed_ws__(G_6x70_Base_DCNTE, 0x24); struct G_SyncPlayerDispAndInventory_DCNTE_6x70 { // Offsets in this struct are relative to the overall command header /* 0004 */ G_ExtendedHeaderT header = {{0x60, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DCNTE_6x70)}; - /* 000C */ G_SyncPlayerDispAndInventory_BaseDCNTE base; + /* 000C */ G_6x70_Base_DCNTE base; // The following two fields appear to contain uninitialized data /* 0030 */ le_uint32_t unknown_a5 = 0; /* 0034 */ le_uint32_t unknown_a6 = 0; - /* 0038 */ Telepipe6x70 telepipe; + /* 0038 */ G_6x70_Sub_Telepipe telepipe; /* 0054 */ le_uint32_t unknown_a8 = 0; /* 0058 */ parray unknown_a9; /* 0068 */ le_uint32_t area = 0; @@ -4982,11 +5022,11 @@ struct G_SyncPlayerDispAndInventory_DCNTE_6x70 { struct G_SyncPlayerDispAndInventory_DC112000_6x70 { // Offsets in this struct are relative to the overall command header /* 0004 */ G_ExtendedHeaderT header = {{0x67, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC112000_6x70)}; - /* 000C */ G_SyncPlayerDispAndInventory_BaseDCNTE base; + /* 000C */ G_6x70_Base_DCNTE base; /* 0030 */ le_uint16_t bonus_hp_from_materials = 0; /* 0032 */ le_uint16_t bonus_tp_from_materials = 0; /* 0034 */ parray unknown_a5; - /* 0044 */ Telepipe6x70 telepipe; + /* 0044 */ G_6x70_Sub_Telepipe telepipe; /* 0060 */ le_uint32_t unknown_a8 = 0; /* 0064 */ parray unknown_a9; /* 0074 */ le_uint32_t area = 0; @@ -4998,30 +5038,34 @@ struct G_SyncPlayerDispAndInventory_DC112000_6x70 { /* 043C */ } __packed_ws__(G_SyncPlayerDispAndInventory_DC112000_6x70, 0x438); -struct G_SyncPlayerDispAndInventory_BaseV1 { - /* 0000 */ G_SyncPlayerDispAndInventory_BaseDCNTE base; +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 */ parray unknown_a4; + /* 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; /* 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; + /* 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 */ Telepipe6x70 telepipe; + /* 0078 */ G_6x70_Sub_Telepipe telepipe; /* 0094 */ le_uint32_t unknown_a8 = 0; - /* 0098 */ G_Unknown_6x70_SubA1 unknown_a9; + /* 0098 */ G_6x70_Sub_UnknownA1 unknown_a9; /* 00AC */ le_uint32_t area = 0; /* 00B0 */ le_uint32_t flags2 = 0; /* 00B4 */ parray technique_levels_v1 = 0xFF; // Last byte is uninitialized /* 00C8 */ PlayerVisualConfig visual; /* 0118 */ -} __packed_ws__(G_SyncPlayerDispAndInventory_BaseV1, 0x118); +} __packed_ws__(G_6x70_Base_V1, 0x118); struct G_SyncPlayerDispAndInventory_DC_PC_6x70 { // Offsets in this struct are relative to the overall command header /* 0004 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC_PC_6x70)}; - /* 000C */ G_SyncPlayerDispAndInventory_BaseV1 base; + /* 000C */ G_6x70_Base_V1 base; /* 0124 */ PlayerStats stats; /* 0148 */ le_uint32_t num_items = 0; /* 014C */ parray items; @@ -5032,7 +5076,7 @@ struct G_SyncPlayerDispAndInventory_DC_PC_6x70 { struct G_SyncPlayerDispAndInventory_GC_6x70 { // Offsets in this struct are relative to the overall command header /* 0004 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_GC_6x70)}; - /* 000C */ G_SyncPlayerDispAndInventory_BaseV1 base; + /* 000C */ G_6x70_Base_V1 base; /* 0124 */ PlayerStats stats; /* 0148 */ le_uint32_t num_items = 0; /* 014C */ parray items; @@ -5043,7 +5087,7 @@ struct G_SyncPlayerDispAndInventory_GC_6x70 { struct G_SyncPlayerDispAndInventory_XB_6x70 { // Offsets in this struct are relative to the overall command header /* 0004 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_XB_6x70)}; - /* 000C */ G_SyncPlayerDispAndInventory_BaseV1 base; + /* 000C */ G_6x70_Base_V1 base; /* 0124 */ PlayerStats stats; /* 0148 */ le_uint32_t num_items = 0; /* 014C */ parray items; @@ -5057,7 +5101,7 @@ struct G_SyncPlayerDispAndInventory_XB_6x70 { struct G_SyncPlayerDispAndInventory_BB_6x70 { // Offsets in this struct are relative to the overall command header /* 0008 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_BB_6x70)}; - /* 0010 */ G_SyncPlayerDispAndInventory_BaseV1 base; + /* 0010 */ G_6x70_Base_V1 base; /* 0128 */ pstring name; /* 0148 */ PlayerStats stats; /* 016C */ le_uint32_t num_items = 0; @@ -5179,7 +5223,7 @@ struct G_Unknown_6x7B { struct G_SetChallengeRecordsBase_6x7C { G_UnusedHeader header; le_uint16_t client_id = 0; - parray unknown_a1; + parray unused; } __packed_ws__(G_SetChallengeRecordsBase_6x7C, 8); struct G_SetChallengeRecords_DC_6x7C : G_SetChallengeRecordsBase_6x7C { @@ -5201,12 +5245,12 @@ struct G_SetBattleModeData_6x7D { G_UnusedHeader header; // Values for what (0-6; values 7 and above are not valid): // 0 = Unknown (params[0] and [1] are used) - // 1 = Does nothing // 2 = Unknown (no params are used) // 3 = Set player score (params[0] = client ID, [1] = score) // 4 = Unknown (params[0] = client ID) // 5 = Unknown (no params are used) // 6 = Unknown (all params are used) + // Anything else = command is ignored uint8_t what = 0; uint8_t unknown_a1 = 0; // Only used when what == 0 uint8_t unused = 0; @@ -5365,11 +5409,12 @@ struct G_Unknown_6x90 { } __packed_ws__(G_Unknown_6x90, 8); // 6x91: Unknown (supported; game only) +// TODO: Deals with TOAttackableCol objects. Figoure out exactly what it does. struct G_Unknown_6x91 { G_ObjectIDHeader header; - le_uint32_t unknown_a1 = 0; - le_uint32_t unknown_a2 = 0; + le_uint32_t object_flags = 0; + le_uint32_t object_index = 0; // < 0xBA0 le_uint16_t unknown_a3 = 0; le_uint16_t unknown_a4 = 0; le_uint16_t switch_flag_num = 0; @@ -5378,6 +5423,8 @@ struct G_Unknown_6x91 { } __packed_ws__(G_Unknown_6x91, 0x14); // 6x92: Unknown (not valid on Episode 3) +// This does something with the TObjOnlineEndingHexMove object. TODO: Figure +// out exactly what. struct G_Unknown_6x92 { G_UnusedHeader header; @@ -5403,15 +5450,16 @@ struct G_InterLevelWarp_6x94 { parray unused; } __packed_ws__(G_InterLevelWarp_6x94, 8); -// 6x95: Unknown (not valid on Episode 3) +// 6x95: Set challenge time (not valid on Episode 3) +// This command sets the time returned by the F88E quest opcode. -struct G_Unknown_6x95 { +struct G_SetChallengeTime_6x95 { G_UnusedHeader header; le_uint32_t client_id = 0; ChallengeTime challenge_time; le_uint32_t unused1 = 0; le_uint32_t unused2 = 0; -} __packed_ws__(G_Unknown_6x95, 0x14); +} __packed_ws__(G_SetChallengeTime_6x95, 0x14); // 6x96: Unknown (not valid on Episode 3) // This command has a handler, but it does nothing. @@ -5427,10 +5475,10 @@ struct G_SelectChallengeModeFailureOption_6x97 { } __packed_ws__(G_SelectChallengeModeFailureOption_6x97, 0x14); // 6x98: Unknown -// This subcommand is completely ignored (at least, by PSO GC). +// This subcommand is completely ignored. // 6x99: Unknown -// This subcommand is completely ignored (at least, by PSO GC). +// This subcommand is completely ignored. // 6x9A: Update player stat (not valid on Episode 3) @@ -5458,18 +5506,22 @@ struct G_LevelUpAllTechniques_6x9B { } __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. struct G_Unknown_6x9C { G_EnemyIDHeader header; le_uint32_t unknown_a1 = 0; } __packed_ws__(G_Unknown_6x9C, 8); -// 6x9D: Unknown (not valid on Episode 3) +// 6x9D: Set dead flag (Challenge mode; not valid on Episode 3) +// This command causes the specified client to appear in the dead players list +// when the Challenge mode retry menu appears. -struct G_Unknown_6x9D { +struct G_SetChallengeDeadFlag_6x9D { G_UnusedHeader header; le_uint32_t client_id = 0; -} __packed_ws__(G_Unknown_6x9D, 8); +} __packed_ws__(G_SetChallengeDeadFlag_6x9D, 8); // 6x9E: Play camera shutter sound // This subcommand is only used on PSO PC and PC NTE. It is not implemented (and @@ -5548,11 +5600,22 @@ struct G_OlgaFlowPhase2BossActions_6xA5 { struct G_ModifyTradeProposal_6xA6 { G_ClientIDHeader header; - uint8_t unknown_a1 = 0; // Must be < 8 - uint8_t unknown_a2 = 0; - parray unknown_a3; - le_uint32_t unknown_a4 = 0; - le_uint32_t unknown_a5 = 0; + // Values for what: + // 0 = Propose (result = 0), accept (result = 2), or reject (result = 3) + // TODO: amount is used when what=0; what is it used for? + // 1 = Add item + // 2 = Remove item + // 3 = First confirm (result = 0) or cancel first confirm (result = 4) + // 4 = Second confirm (result = 0) or cancel second confirm (result = 4) + // 5 = Cancel trade proposal (result = 4) + // 6 = Reject proposal (currently trading with another player) + // 7 = ??? (TODO) + // Anything else = command is ignored + uint8_t what = 0; + uint8_t result = 0; + be_uint16_t unknown_a1; // Only used if what = 7 + le_uint32_t item_id = 0; // Only used if what = 1 or 2 + le_uint32_t amount = 0; // Only used if what = 1 or 2 } __packed_ws__(G_ModifyTradeProposal_6xA6, 0x10); // 6xA7: Unknown (not valid on pre-V3) @@ -5594,8 +5657,8 @@ struct G_BarbaRayBossActions_6xAA { } __packed_ws__(G_BarbaRayBossActions_6xAA, 0x0C); // 6xAB: Create lobby chair (not valid on pre-V3) (protected on V3/V4) -// This command's appears to be different on GC NTE than on any other version. -// It's not known what it does. +// This command appears to be different on GC NTE than on any other version. +// It's not known what it does on GC NTE. struct G_Unknown_GCNTE_6xAB { G_EnemyIDHeader header; @@ -5642,9 +5705,11 @@ struct G_OlgaFlowSubordinateBossActions_6xAD { struct G_SetLobbyChairState_6xAE { G_ClientIDHeader header; le_uint16_t unknown_a1 = 0; - le_uint16_t unknown_a2 = 0; - le_uint32_t unknown_a3 = 0; - le_uint32_t unknown_a4 = 0; + le_uint16_t unused = 0; + // This field contains the flags field on the sender's TObjPlayer object. + // Only the bits 5C000000 are used by the receiver. + le_uint32_t flags = 0; + le_float unknown_a4 = 0; } __packed_ws__(G_SetLobbyChairState_6xAE, 0x10); // 6xAF: Turn lobby chair (not valid on pre-V3 or GC Trial Edition) (protected on V3/V4) @@ -5807,7 +5872,7 @@ struct G_BuyShopItem_BB_6xB7 { uint8_t shop_type = 0; uint8_t item_index = 0; uint8_t amount = 0; - uint8_t unknown_a1 = 0; // TODO: Probably actually unused; verify this + uint8_t unused = 0; } __packed_ws__(G_BuyShopItem_BB_6xB7, 0x0C); // 6xB8: Alias for 6xB4 (Episode 3 Trial Edition) @@ -5917,18 +5982,14 @@ struct G_BankContentsHeader_BB_6xBC { // Note: This structure does not have a normal header - the client ID field is // big-endian! -struct G_WordSelectDuringBattle_Ep3_6xBD { +struct G_PrivateWordSelect_Ep3_6xBD { G_ClientIDHeader header; - le_uint16_t unknown_a1 = 0; - le_uint16_t unknown_a2 = 0; - parray entries; - le_uint32_t unknown_a4 = 0; - le_uint32_t unknown_a5 = 0; + WordSelectMessage message; // This field has the same meaning as the first byte in an 06 command's // message when sent during an Episode 3 battle. uint8_t private_flags = 0; parray unused; -} __packed_ws__(G_WordSelectDuringBattle_Ep3_6xBD, 0x24); +} __packed_ws__(G_PrivateWordSelect_Ep3_6xBD, 0x24); // 6xBD: BB bank action (take/deposit meseta/item) (handled by the server) @@ -5987,7 +6048,7 @@ struct G_TeamInvitationAction_BB_6xC1_6xC2_6xCD_6xCE { G_ClientIDHeader header; le_uint32_t guild_card_number = 0; le_uint32_t action = 0; // 0 or 1 for 6xC1, 2 (or not 2) for 6xC2 - parray unknown_a1; + parray unknown_a1; // TODO } __packed_ws__(G_TeamInvitationAction_BB_6xC1_6xC2_6xCD_6xCE, 0x60); // 6xC3: Split stacked item (BB; handled by the server) @@ -6118,6 +6179,7 @@ struct G_SetQuestCounter_BB_6xD2 { // 6xD3: Invalid subcommand // 6xD4: Unknown (BB) +// Related to object type 0340. struct G_Unknown_BB_6xD4 { G_UnusedHeader header; @@ -6373,8 +6435,6 @@ struct G_UpdateShortStatuses_Ep3_6xB4x04 { } __packed_ws__(G_UpdateShortStatuses_Ep3_6xB4x04, 0x10C); // 6xB4x05: Update map state -// TODO: This structure is different on Ep3 NTE because the Rules structure is -// shorter. Define an appropriate structure for this. struct G_UpdateMap_Ep3NTE_6xB4x05 { /* 0000 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateMap_Ep3NTE_6xB4x05) / 4, 0, 0x05, 0, 0, 0}; @@ -6414,7 +6474,7 @@ struct G_UpdateDecks_Ep3_6xB4x07 { struct G_SetActionState_Ep3_6xB4x09 { /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_SetActionState_Ep3_6xB4x09) / 4, 0, 0x09, 0, 0, 0}; /* 08 */ le_uint16_t client_id = 0; - /* 0A */ parray unknown_a1; + /* 0A */ parray unused; /* 0C */ Episode3::ActionState state; /* 70 */ } __packed_ws__(G_SetActionState_Ep3_6xB4x09, 0x70); @@ -6641,7 +6701,7 @@ struct G_Unknown_Ep3_6xB5x20 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x20) / 4, 0, 0x20, 0, 0, 0}; le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; - uint8_t client_id = 0; + uint8_t client_id = 0; // Not bounds checked! Should be < 0x0C parray unused; } __packed_ws__(G_Unknown_Ep3_6xB5x20, 0x14); @@ -6654,8 +6714,8 @@ struct G_EndBattle_Ep3_CAx21 { // 6xB4x22: Unknown // This command appears to be completely unused. The client's handler for this -// command sets a flag on some data structure if it exists, but it appears that -// that data structure is never allocated. +// command sets a variable on some data structure if it exists, but it appears +// that that data structure is never allocated. struct G_Unknown_Ep3_6xB4x22 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x22) / 4, 0, 0x22, 0, 0, 0}; @@ -6678,8 +6738,6 @@ struct G_Unknown_Ep3_6xB4x23 { struct G_Unknown_Ep3_6xB5x27 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x27) / 4, 0, 0x27, 0, 0, 0}; - // Note: This command uses header_b1 as well, which looks like another client - // ID (it must be < 4, though it does not always match unknown_a1 below). le_uint32_t unknown_a1 = 0; // Probably client ID (must be < 4) le_uint32_t unknown_a2 = 0; // Must be < 0x10 le_uint32_t unknown_a3 = 0; @@ -6712,9 +6770,9 @@ struct G_UpdateAttackTargets_Ep3_6xB4x29 { } __packed_ws__(G_UpdateAttackTargets_Ep3_6xB4x29, 0x70); // 6xB4x2A: Unknown -// This appears to be unused, even on DC NTE. It writes an entry into an array -// of four 6-byte structures (doing nothing if there are already 4 present), -// but nothing reads from this array. +// This appears to be unused, even on NTE. It writes an entry into an array of +// four 6-byte structures (doing nothing if there are already 4 present), but +// nothing reads from this array. struct G_Unknown_Ep3_6xB4x2A { G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x2A) / 4, 0, 0x2A, 0, 0, 0}; @@ -6733,93 +6791,111 @@ struct G_ExecLegacyCard_Ep3_CAx2B { parray unused3; } __packed_ws__(G_ExecLegacyCard_Ep3_CAx2B, 0x14); -// 6xB4x2C: Unknown +// 6xB4x2C: Enqueue animation // This is used for playing the trap and teleport animations (with change_type // = 1). It's also used for playing the discard entire hand animation (with // change_type = 3). -struct G_Unknown_Ep3_6xB4x2C { - /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_Unknown_Ep3_6xB4x2C) / 4, 0, 0x2C, 0, 0, 0}; +struct G_EnqueueAnimation_Ep3_6xB4x2C { + /* 00 */ G_CardBattleCommandHeader header = {0xB4, sizeof(G_EnqueueAnimation_Ep3_6xB4x2C) / 4, 0, 0x2C, 0, 0, 0}; /* 08 */ uint8_t change_type = 0; /* 09 */ uint8_t client_id = 0; /* 0A */ parray card_refs; /* 10 */ Episode3::Location loc; /* 14 */ parray unknown_a2; /* 1C */ -} __packed_ws__(G_Unknown_Ep3_6xB4x2C, 0x1C); +} __packed_ws__(G_EnqueueAnimation_Ep3_6xB4x2C, 0x1C); -// 6xB5x2D: Unknown -// TODO: Document this from Episode 3 client/server disassembly +// 6xB5x2D: Recreate multiple players -struct G_Unknown_Ep3_6xB5x2D { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x2D) / 4, 0, 0x2D, 0, 0, 0}; +struct G_RecreateMultiplePlayers_Ep3_6xB5x2D { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_RecreateMultiplePlayers_Ep3_6xB5x2D) / 4, 0, 0x2D, 0, 0, 0}; // This array is indexed by client ID. When a client receives this command // and its corresponding entry in this array is not zero, it sends a 6x70 // command to itself containing its own player data. It's not clear what the // function of this is intended to be. // TODO: Figure out if tournament fast loading can be implemented using this // to fix the stuck-in-wall glitch. - parray unknown_a1; -} __packed_ws__(G_Unknown_Ep3_6xB5x2D, 0x0C); + parray client_ids_to_recreate; +} __packed_ws__(G_RecreateMultiplePlayers_Ep3_6xB5x2D, 0x0C); // 6xB5x2E: Notify other players that battle is about to end struct G_BattleEndNotification_Ep3_6xB5x2E { G_CardBattleCommandHeader header = {0xB5, sizeof(G_BattleEndNotification_Ep3_6xB5x2E) / 4, 0, 0x2E, 0, 0, 0}; - uint8_t unknown_a1 = 0; // Command ignored unless this is 0 or 1 + // Values for end_type: + // 0 = Battle results screen + // 1 = Go directly to Morgue? (TODO: test this) + // Anything else = command is ignored + uint8_t end_type = 0; parray unused; } __packed_ws__(G_BattleEndNotification_Ep3_6xB5x2E, 0x0C); // 6xB5x2F: Set deck in battle setup menu +struct Ep3CounterPlayerEntry { + /* 00 */ uint8_t is_valid; + /* 01 */ uint8_t entry_type; + /* 02 */ uint8_t client_id; + /* 03 */ uint8_t client_id2; + /* 04 */ pstring player_name; + /* 18 */ pstring deck_name; + /* 31 */ uint8_t deck_type; + /* 32 */ uint8_t unknown_a1a; + /* 33 */ uint8_t unknown_a1b; + /* 34 */ be_uint16_t unknown_a3; + /* 36 */ le_uint16_t unknown_a2; + /* 38 */ parray card_ids; + /* 76 */ parray unused; + /* 78 */ le_uint32_t unknown_a5; + /* 7C */ be_uint16_t unknown_a6; + /* 7E */ be_uint16_t unknown_a7; + /* 80 */ +} __packed_ws__(Ep3CounterPlayerEntry, 0x80); + struct G_SetDeckInBattleSetupMenu_Ep3_6xB5x2F { G_CardBattleCommandHeader header = {0xB5, sizeof(G_SetDeckInBattleSetupMenu_Ep3_6xB5x2F) / 4, 0, 0x2F, 0, 0, 0}; parray unknown_a1; - parray unknown_a2; - pstring deck_name; - parray unknown_a3; - le_uint16_t unknown_a4 = 0; - parray card_ids; - parray unused; - le_uint32_t unknown_a5 = 0; - le_uint16_t unknown_a6 = 0; - le_uint16_t unknown_a7 = 0; + Ep3CounterPlayerEntry entry; } __packed_ws__(G_SetDeckInBattleSetupMenu_Ep3_6xB5x2F, 0x8C); -// 6xB5x30: Unknown +// 6xB5x30: Unused // The client never sends this command, and when the client receives this -// command, it does nothing. +// command, it does nothing. It's likely that there was some commented-out code +// in the original source, since the handler byteswaps the command and calls +// is_online() and local_client_is_leader(), then ignores the results and +// returns immediately. -struct G_Unknown_Ep3_6xB5x30 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unknown_Ep3_6xB5x30) / 4, 0, 0x30, 0, 0, 0}; +struct G_Unused_Ep3_6xB5x30 { + G_CardBattleCommandHeader header = {0xB5, sizeof(G_Unused_Ep3_6xB5x30) / 4, 0, 0x30, 0, 0, 0}; // No arguments -} __packed_ws__(G_Unknown_Ep3_6xB5x30, 8); +} __packed_ws__(G_Unused_Ep3_6xB5x30, 8); // 6xB5x31: Confirm deck selection struct G_ConfirmDeckSelection_Ep3_6xB5x31 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_ConfirmDeckSelection_Ep3_6xB5x31) / 4, 0, 0x31, 0, 0, 0}; - // Note: This command uses header_b1 for... something. uint8_t unknown_a1 = 0; // Must be 0 or 1 uint8_t unknown_a2 = 0; // Must be < 4 - uint8_t unknown_a3 = 0; // Must be < 4 + uint8_t unknown_a3 = 0; // Must be 0xFF or < 4 uint8_t unknown_a4 = 0; // Must be < 0x14 - uint8_t unknown_a5 = 0; // Used as an array index + uint8_t menu_type = 0; // Not bounds-checked; should be < 0x15 parray unused; } __packed_ws__(G_ConfirmDeckSelection_Ep3_6xB5x31, 0x10); // 6xB5x32: Move shared menu cursor struct G_MoveSharedMenuCursor_Ep3_6xB5x32 { - G_CardBattleCommandHeader header = {0xB5, sizeof(G_MoveSharedMenuCursor_Ep3_6xB5x32) / 4, 0, 0x32, 0, 0, 0}; - le_uint16_t selected_item_index = 0xFFFF; - le_uint16_t chosen_item_index = 0xFFFF; - uint8_t unknown_a1 = 0; - uint8_t unknown_a2 = 0; - uint8_t unknown_a3 = 0; - uint8_t unknown_a4 = 0; - uint8_t unknown_a5 = 0; - parray unused; + /* 00 */ G_CardBattleCommandHeader header = {0xB5, sizeof(G_MoveSharedMenuCursor_Ep3_6xB5x32) / 4, 0, 0x32, 0, 0, 0}; + /* 08 */ le_int16_t selected_item_index = -1; // Must not be < -1 + /* 0A */ le_int16_t chosen_item_index = -1; // Must not be < -1 + /* 0C */ uint8_t unknown_a1 = 0; // Must be 0, 1, or 2 + /* 0D */ uint8_t unknown_a2 = 0; // Must be less than 0x14 + /* 0E */ uint8_t unknown_a3 = 0; // Must be less than 0x14 + /* 0F */ uint8_t unknown_a4 = 0; // Must be 0 or 1 + /* 10 */ uint8_t menu_type = 0; // Not bounds-checked; should be < 0x15 + /* 11 */ parray unused; + /* 14 */ } __packed_ws__(G_MoveSharedMenuCursor_Ep3_6xB5x32, 0x14); // 6xB4x33: Subtract ally ATK points (e.g. for photon blast) @@ -6971,7 +7047,6 @@ check_struct_size(G_SetTournamentPlayerDecks_Ep3_6xB4x3D, 0x1CC); struct G_MakeCardAuctionBid_Ep3_6xB5x3E { G_CardBattleCommandHeader header = {0xB5, sizeof(G_MakeCardAuctionBid_Ep3_6xB5x3E) / 4, 0, 0x3E, 0, 0, 0}; - // Note: This command uses header.unknown_a1 for the bidder's client ID. uint8_t card_index = 0; // Index of card in EF command uint8_t bid_value = 0; // 1-99 parray unused; @@ -6985,10 +7060,10 @@ struct G_MakeCardAuctionBid_Ep3_6xB5x3E { struct G_OpenBlockingMenu_Ep3_6xB5x3F { G_CardBattleCommandHeader header = {0xB5, sizeof(G_OpenBlockingMenu_Ep3_6xB5x3F) / 4, 0, 0x3F, 0, 0, 0}; // Menu type should be one of these values: + // 0xFF = close all menus? (TODO: verify this) // 0x01/0x02 = battle prep menu // 0x11 = card auction counter menu (join or cancel) // 0x12 = go directly to card auction state (client sends EF command) - // Other values will likely crash the client. int8_t menu_type = 0; // Must be in the range [-1, 0x14] uint8_t client_id = 0; parray unused1; @@ -7024,7 +7099,6 @@ struct G_MapDataRequest_Ep3_CAx41 { struct G_InitiateCardAuction_Ep3_6xB5x42 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_InitiateCardAuction_Ep3_6xB5x42) / 4, 0, 0x42, 0, 0, 0}; - // This command uses header.unknown_a1 (probably for the client's ID). } __packed_ws__(G_InitiateCardAuction_Ep3_6xB5x42, 8); // 6xB5x43: Unknown @@ -7048,7 +7122,6 @@ struct G_Unknown_Ep3_6xB5x43 { struct G_CardAuctionBidSummary_Ep3_6xB5x44 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardAuctionBidSummary_Ep3_6xB5x44) / 4, 0, 0x44, 0, 0, 0}; - // Note: This command uses header.unknown_a1 for the bidder's client ID. parray bids; // In same order as cards in the EF command } __packed_ws__(G_CardAuctionBidSummary_Ep3_6xB5x44, 0x18); @@ -7056,7 +7129,6 @@ struct G_CardAuctionBidSummary_Ep3_6xB5x44 { struct G_CardAuctionResults_Ep3_6xB5x45 { G_CardBattleCommandHeader header = {0xB5, sizeof(G_CardAuctionResults_Ep3_6xB5x45) / 4, 0, 0x45, 0, 0, 0}; - // Note: This command uses header.unknown_a1 for the sender's client ID. // This array is indexed by [card_index][client_id], and contains the final // bid for each player on each card (or 0 if they did not bid on that card). parray, 8> bids_by_player; @@ -7332,8 +7404,8 @@ struct G_RejectBattleStartRequest_Ep3_6xB4x53 { //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// -// These commands are not part of the official protocol; newserv uses these to -// implement extended functionality. +// These commands are not part of the official protocol; newserv uses these +// along with client functions to implement extended functionality. // 30 (C->S): Extended player info // Requested with the GetExtendedPlayerInfo patch. Format depends on version: diff --git a/src/Episode3/Card.cc b/src/Episode3/Card.cc index fc315cc9..948a45bb 100644 --- a/src/Episode3/Card.cc +++ b/src/Episode3/Card.cc @@ -714,7 +714,7 @@ int32_t Card::move_to_location(const Location& loc) { uint8_t trap_type = s->overlay_state.tiles[loc.y][loc.x] & 0x0F; uint16_t trap_card_id = s->overlay_state.trap_card_ids_nte[trap_type]; if (other_ps->replace_assist_card_by_id(trap_card_id)) { - G_Unknown_Ep3_6xB4x2C cmd; + G_EnqueueAnimation_Ep3_6xB4x2C cmd; cmd.change_type = 1; cmd.client_id = other_ps->client_id; cmd.unknown_a2[0] = trap_card_id; @@ -728,7 +728,7 @@ int32_t Card::move_to_location(const Location& loc) { for (size_t warp_end = 0; warp_end < 2; warp_end++) { if ((s->warp_positions[warp_type][warp_end][0] == this->loc.x) && (s->warp_positions[warp_type][warp_end][1] == this->loc.y)) { - G_Unknown_Ep3_6xB4x2C cmd; + G_EnqueueAnimation_Ep3_6xB4x2C cmd; cmd.loc.x = this->loc.x; cmd.loc.y = this->loc.y; this->loc.x = s->warp_positions[warp_type][warp_end ^ 1][0]; diff --git a/src/Episode3/PlayerState.cc b/src/Episode3/PlayerState.cc index 4e21b40c..79af6e3c 100644 --- a/src/Episode3/PlayerState.cc +++ b/src/Episode3/PlayerState.cc @@ -613,7 +613,7 @@ void PlayerState::discard_and_redraw_hand() { } if (!s->options.is_nte()) { - G_Unknown_Ep3_6xB4x2C cmd; + G_EnqueueAnimation_Ep3_6xB4x2C cmd; cmd.change_type = 3; cmd.client_id = this->client_id; cmd.card_refs.clear(0xFFFF); @@ -717,7 +717,7 @@ bool PlayerState::do_mulligan() { } if (!s->options.is_nte()) { - G_Unknown_Ep3_6xB4x2C cmd; + G_EnqueueAnimation_Ep3_6xB4x2C cmd; cmd.change_type = 3; cmd.client_id = this->client_id; cmd.card_refs.clear(0xFFFF); diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index 370e25d8..39623f81 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -1157,7 +1157,7 @@ void Server::move_phase_after() { (abs(sc_card->loc.x - trap_x) < 2) && (abs(sc_card->loc.y - trap_y) < 2) && ps->replace_assist_card_by_id(trap_card_id)) { - G_Unknown_Ep3_6xB4x2C cmd; + G_EnqueueAnimation_Ep3_6xB4x2C cmd; cmd.change_type = 0x01; cmd.client_id = client_id; cmd.card_refs.clear(0xFFFF); diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 75ce3960..f92a23c1 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1174,12 +1174,27 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, } else if ((data[0] == 0x60) || (static_cast(data[0]) == 0xA2)) { return SC_6x60_6xA2(ses, data); - } else if ((static_cast(data[0]) == 0xB5) && is_ep3(ses->version()) && (data.size() > 4)) { + } else if ((static_cast(data[0]) == 0xB5) && is_ep3(ses->version()) && (data.size() >= 8)) { + set_mask_for_ep3_game_command(data.data(), data.size(), 0); if (data[4] == 0x1A) { return HandlerResult::Type::SUPPRESS; + } else if (data[4] == 0x20) { + auto& cmd = check_size_t(data); + if (cmd.client_id >= 12) { + return HandlerResult::Type::SUPPRESS; + } + } else if (data[4] == 0x31) { + auto& cmd = check_size_t(data); + if (cmd.menu_type >= 0x15) { + return HandlerResult::Type::SUPPRESS; + } + } else if (data[4] == 0x32) { + auto& cmd = check_size_t(data); + if (cmd.menu_type >= 0x15) { + return HandlerResult::Type::SUPPRESS; + } } else if (data[4] == 0x36) { auto& cmd = check_size_t(data); - set_mask_for_ep3_game_command(&cmd, sizeof(cmd), 0); if (ses->is_in_game && (cmd.client_id >= 4)) { return HandlerResult::Type::SUPPRESS; } @@ -1197,7 +1212,7 @@ static HandlerResult S_6x(shared_ptr ses, uint16_t, } else if ((static_cast(data[0]) == 0xBD) && ses->config.check_flag(Client::Flag::PROXY_EP3_UNMASK_WHISPERS) && is_ep3(ses->version())) { - auto& cmd = check_size_t(data); + auto& cmd = check_size_t(data); if (cmd.private_flags & (1 << ses->lobby_client_id)) { cmd.private_flags &= ~(1 << ses->lobby_client_id); modified = true; @@ -2039,8 +2054,8 @@ HandlerResult C_6x(shared_ptr ses, uint16_t, u } else if (data[0] == 0x0C) { if (ses->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) { - send_remove_conditions(ses->client_channel, ses->lobby_client_id); - send_remove_conditions(ses->server_channel, ses->lobby_client_id); + send_remove_negative_conditions(ses->client_channel, ses->lobby_client_id); + send_remove_negative_conditions(ses->server_channel, ses->lobby_client_id); } } else if (data[0] == 0x2F || data[0] == 0x4B || data[0] == 0x4C) { diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 49220d05..0d54656f 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -994,7 +994,7 @@ void Parsed6x70Data::clear_dc_protos_unused_item_fields() { } Parsed6x70Data::Parsed6x70Data( - const G_SyncPlayerDispAndInventory_BaseV1& base, + const G_6x70_Base_V1& base, uint32_t guild_card_number, Version from_version, bool from_client_customization) @@ -1004,7 +1004,11 @@ Parsed6x70Data::Parsed6x70Data( base(base.base), bonus_hp_from_materials(base.bonus_hp_from_materials), bonus_tp_from_materials(base.bonus_tp_from_materials), - unknown_a4_final(base.unknown_a4), + permanent_status_effect(base.permanent_status_effect), + temporary_status_effect(base.temporary_status_effect), + attack_status_effect(base.attack_status_effect), + defense_status_effect(base.defense_status_effect), + unused_status_effect(base.unused_status_effect), language(base.language), player_tag(base.player_tag), guild_card_number(guild_card_number), // Ignore the client's GC# @@ -1018,12 +1022,16 @@ Parsed6x70Data::Parsed6x70Data( technique_levels_v1(base.technique_levels_v1), visual(base.visual) {} -G_SyncPlayerDispAndInventory_BaseV1 Parsed6x70Data::base_v1() const { - G_SyncPlayerDispAndInventory_BaseV1 ret; +G_6x70_Base_V1 Parsed6x70Data::base_v1() const { + G_6x70_Base_V1 ret; ret.base = this->base; ret.bonus_hp_from_materials = this->bonus_hp_from_materials; ret.bonus_tp_from_materials = this->bonus_tp_from_materials; - ret.unknown_a4 = this->unknown_a4_final; + ret.permanent_status_effect = this->permanent_status_effect; + ret.temporary_status_effect = this->temporary_status_effect; + ret.attack_status_effect = this->attack_status_effect; + ret.defense_status_effect = this->defense_status_effect; + ret.unused_status_effect = this->unused_status_effect; ret.language = this->language; ret.player_tag = this->player_tag; ret.guild_card_number = this->guild_card_number; @@ -1186,6 +1194,21 @@ static void on_ep3_battle_subs(shared_ptr c, uint8_t command, uint8_t fl if (header.subsubcommand == 0x1A) { return; + } else if (header.subsubcommand == 0x20) { + const auto& cmd = check_size_t(data, size); + if (cmd.client_id >= 12) { + return; + } + } else if (header.subsubcommand == 0x31) { + const auto& cmd = check_size_t(data, size); + if (cmd.menu_type >= 0x15) { + return; + } + } else if (header.subsubcommand == 0x32) { + const auto& cmd = check_size_t(data, size); + if (cmd.menu_type >= 0x15) { + return; + } } else if (header.subsubcommand == 0x36) { const auto& cmd = check_size_t(data, size); if (l->is_game() && (cmd.client_id >= 4)) { @@ -1512,7 +1535,7 @@ static void on_received_condition(shared_ptr c, uint8_t command, uint8_t if (cmd.client_id == c->lobby_client_id) { bool player_cheats_enabled = l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)); if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) { - send_remove_conditions(c); + send_remove_negative_conditions(c); } } } @@ -2480,16 +2503,13 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr c, uint } } else if (is_ep3(c->version())) { + const auto& cmd = check_size_t(data, size); + s->word_select_table->validate(cmd.message, c->version()); - const auto& cmd = check_size_t(data, size); - G_WordSelectDuringBattle_Ep3_6xBD masked_cmd = { - {0xBD, sizeof(G_WordSelectDuringBattle_Ep3_6xBD) >> 2, cmd.header.client_id}, - 0x0001, - 0x0001, + G_PrivateWordSelect_Ep3_6xBD masked_cmd = { + {0xBD, sizeof(G_PrivateWordSelect_Ep3_6xBD) >> 2, cmd.header.client_id}, // "Please use the Whispers function." - {0x00C1, 0x02C7, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF}, - 0x0000, - 0x0000, + {0x0001, 0x0001, {0x00C1, 0x02C7, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF}, 0x0000, 0x0000}, cmd.private_flags, {0, 0, 0}}; @@ -3393,7 +3413,7 @@ static void on_level_up(shared_ptr c, uint8_t command, uint8_t flag, voi // increments the player's level by 1. auto p = c->character(); if (is_pre_v1(c->version())) { - check_size_t(data, size); + check_size_t(data, size); auto s = c->require_server_state(); auto level_table = s->level_table(c->version()); const auto& level_incrs = level_table->stats_delta_for_level(p->disp.visual.char_class, p->disp.stats.level + 1); @@ -3406,7 +3426,7 @@ static void on_level_up(shared_ptr c, uint8_t command, uint8_t flag, voi p->disp.stats.char_stats.lck += level_incrs.lck; p->disp.stats.level++; } else { - const auto& cmd = check_size_t(data, size); + const auto& cmd = check_size_t(data, size); p->disp.stats.char_stats.atp = cmd.atp; p->disp.stats.char_stats.mst = cmd.mst; p->disp.stats.char_stats.evp = cmd.evp; @@ -4186,7 +4206,6 @@ static void on_challenge_update_records(shared_ptr c, uint8_t command, u dc_cmd.header = cmd.header; dc_cmd.header.size = sizeof(G_SetChallengeRecords_DC_6x7C) >> 2; dc_cmd.client_id = cmd.client_id; - dc_cmd.unknown_a1 = cmd.unknown_a1; dc_cmd.records = p->challenge_records; } data_to_send = dc_data.data(); @@ -4198,7 +4217,6 @@ static void on_challenge_update_records(shared_ptr c, uint8_t command, u pc_cmd.header = cmd.header; pc_cmd.header.size = sizeof(G_SetChallengeRecords_PC_6x7C) >> 2; pc_cmd.client_id = cmd.client_id; - pc_cmd.unknown_a1 = cmd.unknown_a1; pc_cmd.records = p->challenge_records; } data_to_send = pc_data.data(); @@ -4210,7 +4228,6 @@ static void on_challenge_update_records(shared_ptr c, uint8_t command, u v3_cmd.header = cmd.header; v3_cmd.header.size = sizeof(G_SetChallengeRecords_V3_6x7C) >> 2; v3_cmd.client_id = cmd.client_id; - v3_cmd.unknown_a1 = cmd.unknown_a1; v3_cmd.records = p->challenge_records; } data_to_send = v3_data.data(); @@ -4222,7 +4239,6 @@ static void on_challenge_update_records(shared_ptr c, uint8_t command, u bb_cmd.header = cmd.header; bb_cmd.header.size = sizeof(G_SetChallengeRecords_BB_6x7C) >> 2; bb_cmd.client_id = cmd.client_id; - bb_cmd.unknown_a1 = cmd.unknown_a1; bb_cmd.records = p->challenge_records; } data_to_send = bb_data.data(); diff --git a/src/ReceiveSubcommands.hh b/src/ReceiveSubcommands.hh index 96be660d..d614cfc7 100644 --- a/src/ReceiveSubcommands.hh +++ b/src/ReceiveSubcommands.hh @@ -43,22 +43,26 @@ public: bool from_client_customization; Version item_version; - G_SyncPlayerDispAndInventory_BaseDCNTE base; + G_6x70_Base_DCNTE base; uint32_t unknown_a5_nte = 0; uint32_t unknown_a6_nte = 0; uint16_t bonus_hp_from_materials = 0; uint16_t bonus_tp_from_materials = 0; parray unknown_a5_112000; - parray unknown_a4_final; + G_6x70_StatusEffectState permanent_status_effect; + G_6x70_StatusEffectState temporary_status_effect; + G_6x70_StatusEffectState attack_status_effect; + G_6x70_StatusEffectState defense_status_effect; + G_6x70_StatusEffectState unused_status_effect; uint32_t language = 0; uint32_t player_tag = 0; uint32_t guild_card_number = 0; uint32_t unknown_a6 = 0; uint32_t battle_team_number = 0; - Telepipe6x70 telepipe; + G_6x70_Sub_Telepipe telepipe; uint32_t unknown_a8 = 0; parray unknown_a9_nte_112000; - G_Unknown_6x70_SubA1 unknown_a9_final; + G_6x70_Sub_UnknownA1 unknown_a9_final; uint32_t area = 0; uint32_t flags2 = 0; parray technique_levels_v1 = 0xFF; @@ -116,11 +120,11 @@ public: protected: Parsed6x70Data( - const G_SyncPlayerDispAndInventory_BaseV1& base, + const G_6x70_Base_V1& base, uint32_t guild_card_number, Version from_version, bool from_client_customization); - G_SyncPlayerDispAndInventory_BaseV1 base_v1() const; + G_6x70_Base_V1 base_v1() const; }; bool validate_6xBB(G_SyncCardTradeServerState_Ep3_6xBB& cmd); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 276d0fac..0c22bada 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2545,26 +2545,20 @@ void send_player_stats_change(Channel& ch, uint16_t client_id, PlayerStatsChange send_command_vt(ch, (subs.size() > 0x400 / sizeof(G_UpdatePlayerStat_6x9A)) ? 0x6C : 0x60, 0x00, subs); } -void send_remove_conditions(shared_ptr c) { - parray cmds; - for (size_t z = 0; z < 4; z++) { - auto& cmd = cmds[z]; - cmd.header = {0x0D, sizeof(G_AddOrRemoveCondition_6x0C_6x0D) >> 2, c->lobby_client_id}; - cmd.unknown_a1 = z; - cmd.unknown_a2 = 0; - } - send_protected_command(c, &cmds, sizeof(cmds), true); +void send_remove_negative_conditions(shared_ptr c) { + G_AddStatusEffect_6x0C cmd; + cmd.header = {0x0C, sizeof(G_AddStatusEffect_6x0C) >> 2, c->lobby_client_id}; + cmd.effect_type = 7; // Healing ring + cmd.level = 0; + send_protected_command(c, &cmd, sizeof(cmd), true); } -void send_remove_conditions(Channel& ch, uint16_t client_id) { - parray cmds; - for (size_t z = 0; z < 4; z++) { - auto& cmd = cmds[z]; - cmd.header = {0x0D, sizeof(G_AddOrRemoveCondition_6x0C_6x0D) >> 2, client_id}; - cmd.unknown_a1 = z; - cmd.unknown_a2 = 0; - } - ch.send(0x60, 0x00, &cmds, sizeof(cmds)); +void send_remove_negative_conditions(Channel& ch, uint16_t client_id) { + G_AddStatusEffect_6x0C cmd; + cmd.header = {0x0C, sizeof(G_AddStatusEffect_6x0C) >> 2, client_id}; + cmd.effect_type = 7; // Healing ring + cmd.level = 0; + ch.send(0x60, 0x00, &cmd, sizeof(cmd)); } void send_warp(Channel& ch, uint8_t client_id, uint32_t floor, bool is_private) { @@ -3091,8 +3085,8 @@ void send_level_up(shared_ptr c) { } uint8_t subcommand = get_pre_v1_subcommand(c->version(), 0x2C, 0x2E, 0x30); - G_LevelUp_6x30 cmd = { - {subcommand, sizeof(G_LevelUp_6x30) / 4, c->lobby_client_id}, + G_ChangePlayerLevel_6x30 cmd = { + {subcommand, sizeof(G_ChangePlayerLevel_6x30) / 4, c->lobby_client_id}, stats.atp + (mag ? ((mag->data1w[3] / 100) * 2) : 0), stats.mst + (mag ? ((mag->data1w[5] / 100) * 2) : 0), stats.evp, diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 8865ec06..d816a23c 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -326,8 +326,8 @@ enum PlayerStatsChange { void send_player_stats_change(std::shared_ptr c, PlayerStatsChange stat, uint32_t amount); void send_player_stats_change(Channel& ch, uint16_t client_id, PlayerStatsChange stat, uint32_t amount); -void send_remove_conditions(std::shared_ptr c); -void send_remove_conditions(Channel& ch, uint16_t client_id); +void send_remove_negative_conditions(std::shared_ptr c); +void send_remove_negative_conditions(Channel& ch, uint16_t client_id); void send_warp(Channel& ch, uint8_t client_id, uint32_t floor, bool is_private); void send_warp(std::shared_ptr c, uint32_t floor, bool is_private); void send_warp(std::shared_ptr l, uint32_t floor, bool is_private); diff --git a/src/WordSelectTable.cc b/src/WordSelectTable.cc index aea012bc..462724cf 100644 --- a/src/WordSelectTable.cc +++ b/src/WordSelectTable.cc @@ -280,7 +280,7 @@ void WordSelectTable::print_index(FILE* stream, Version v) const { } } -WordSelectMessage WordSelectTable::validate(const WordSelectMessage& msg, Version version) const { +void WordSelectTable::validate(const WordSelectMessage& msg, Version version) const { const auto& index = this->tokens_for_version(version); for (size_t z = 0; z < msg.tokens.size(); z++) { diff --git a/src/WordSelectTable.hh b/src/WordSelectTable.hh index db52ae04..05f904ef 100644 --- a/src/WordSelectTable.hh +++ b/src/WordSelectTable.hh @@ -81,7 +81,7 @@ public: void print(FILE* stream) const; void print_index(FILE* stream, Version v) const; - WordSelectMessage validate(const WordSelectMessage& msg, Version version) const; + void validate(const WordSelectMessage& msg, Version version) const; // Throws runtime_error if invalid WordSelectMessage translate(const WordSelectMessage& msg, Version from_version, Version to_version) const; private: