refine many game command formats; $infhp no longer removes positive effects

This commit is contained in:
Martin Michelsen
2024-11-09 18:07:59 -08:00
parent 70dfeeba91
commit e7db8f2404
12 changed files with 311 additions and 210 deletions
+3 -3
View File
@@ -2066,7 +2066,7 @@ static void server_command_infinite_hp(shared_ptr<Client> 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<ProxyServer::LinkedSession> 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);
}
}
+219 -147
View File
@@ -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<TextEncoding::ASCII, 0x30> 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<uint8_t, 2> 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<G_ClientIDHeader> 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<uint8_t, 0x10> 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<G_ClientIDHeader> 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<uint8_t, 0x10> unknown_a5;
/* 0044 */ Telepipe6x70 telepipe;
/* 0044 */ G_6x70_Sub_Telepipe telepipe;
/* 0060 */ le_uint32_t unknown_a8 = 0;
/* 0064 */ parray<uint8_t, 0x10> 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<G_Unknown_6x70_SubA2, 5> 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<uint8_t, 0x14> 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<G_ClientIDHeader> 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<PlayerInventoryItem, 0x1E> 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<G_ClientIDHeader> 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<PlayerInventoryItem, 0x1E> 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<G_ClientIDHeader> 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<PlayerInventoryItem, 0x1E> 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<G_ClientIDHeader> header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_BB_6x70)};
/* 0010 */ G_SyncPlayerDispAndInventory_BaseV1 base;
/* 0010 */ G_6x70_Base_V1 base;
/* 0128 */ pstring<TextEncoding::UTF16_ALWAYS_MARKED, 0x10> 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<uint8_t, 2> unknown_a1;
parray<uint8_t, 2> 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<uint8_t, 2> 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<uint8_t, 2> 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<le_uint16_t, 8> 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<uint8_t, 3> 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<uint8_t, 0x54> unknown_a1;
parray<uint8_t, 0x54> 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<uint8_t, 2> unknown_a1;
/* 0A */ parray<uint8_t, 2> 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<uint8_t, 3> 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<uint8_t, 2> 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<le_uint16_t, 3> card_refs;
/* 10 */ Episode3::Location loc;
/* 14 */ parray<le_uint32_t, 2> 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<uint8_t, 4> unknown_a1;
} __packed_ws__(G_Unknown_Ep3_6xB5x2D, 0x0C);
parray<uint8_t, 4> 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<uint8_t, 3> 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<TextEncoding::MARKED, 20> player_name;
/* 18 */ pstring<TextEncoding::MARKED, 25> 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<le_uint16_t, 0x1F> card_ids;
/* 76 */ parray<uint8_t, 2> 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<uint8_t, 4> unknown_a1;
parray<uint8_t, 0x18> unknown_a2;
pstring<TextEncoding::MARKED, 0x10> deck_name;
parray<uint8_t, 0x0E> unknown_a3;
le_uint16_t unknown_a4 = 0;
parray<le_uint16_t, 0x1F> card_ids;
parray<uint8_t, 2> 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<uint8_t, 3> 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<uint8_t, 3> 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<uint8_t, 3> 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<uint8_t, 2> 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<uint8_t, 2> 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<le_uint16_t, 8> 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<parray<le_uint16_t, 4>, 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:
+2 -2
View File
@@ -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];
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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);
+20 -5
View File
@@ -1174,12 +1174,27 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
} else if ((data[0] == 0x60) || (static_cast<uint8_t>(data[0]) == 0xA2)) {
return SC_6x60_6xA2(ses, data);
} else if ((static_cast<uint8_t>(data[0]) == 0xB5) && is_ep3(ses->version()) && (data.size() > 4)) {
} else if ((static_cast<uint8_t>(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<G_Unknown_Ep3_6xB5x20>(data);
if (cmd.client_id >= 12) {
return HandlerResult::Type::SUPPRESS;
}
} else if (data[4] == 0x31) {
auto& cmd = check_size_t<G_ConfirmDeckSelection_Ep3_6xB5x31>(data);
if (cmd.menu_type >= 0x15) {
return HandlerResult::Type::SUPPRESS;
}
} else if (data[4] == 0x32) {
auto& cmd = check_size_t<G_MoveSharedMenuCursor_Ep3_6xB5x32>(data);
if (cmd.menu_type >= 0x15) {
return HandlerResult::Type::SUPPRESS;
}
} else if (data[4] == 0x36) {
auto& cmd = check_size_t<G_RecreatePlayer_Ep3_6xB5x36>(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<ProxyServer::LinkedSession> ses, uint16_t,
} else if ((static_cast<uint8_t>(data[0]) == 0xBD) &&
ses->config.check_flag(Client::Flag::PROXY_EP3_UNMASK_WHISPERS) &&
is_ep3(ses->version())) {
auto& cmd = check_size_t<G_WordSelectDuringBattle_Ep3_6xBD>(data);
auto& cmd = check_size_t<G_PrivateWordSelect_Ep3_6xBD>(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<void>(shared_ptr<ProxyServer::LinkedSession> 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) {
+36 -20
View File
@@ -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<Client> c, uint8_t command, uint8_t fl
if (header.subsubcommand == 0x1A) {
return;
} else if (header.subsubcommand == 0x20) {
const auto& cmd = check_size_t<G_Unknown_Ep3_6xB5x20>(data, size);
if (cmd.client_id >= 12) {
return;
}
} else if (header.subsubcommand == 0x31) {
const auto& cmd = check_size_t<G_ConfirmDeckSelection_Ep3_6xB5x31>(data, size);
if (cmd.menu_type >= 0x15) {
return;
}
} else if (header.subsubcommand == 0x32) {
const auto& cmd = check_size_t<G_MoveSharedMenuCursor_Ep3_6xB5x32>(data, size);
if (cmd.menu_type >= 0x15) {
return;
}
} else if (header.subsubcommand == 0x36) {
const auto& cmd = check_size_t<G_RecreatePlayer_Ep3_6xB5x36>(data, size);
if (l->is_game() && (cmd.client_id >= 4)) {
@@ -1512,7 +1535,7 @@ static void on_received_condition(shared_ptr<Client> 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<Client> c, uint
}
} else if (is_ep3(c->version())) {
const auto& cmd = check_size_t<G_PrivateWordSelect_Ep3_6xBD>(data, size);
s->word_select_table->validate(cmd.message, c->version());
const auto& cmd = check_size_t<G_WordSelectDuringBattle_Ep3_6xBD>(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<Client> 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<G_LevelUp_DCNTE_6x30>(data, size);
check_size_t<G_ChangePlayerLevel_DCNTE_6x30>(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<Client> 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<G_LevelUp_6x30>(data, size);
const auto& cmd = check_size_t<G_ChangePlayerLevel_6x30>(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<Client> 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<Client> 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<Client> 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<Client> 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();
+10 -6
View File
@@ -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<uint8_t, 0x10> unknown_a5_112000;
parray<G_Unknown_6x70_SubA2, 5> 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<uint8_t, 0x10> 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<uint8_t, 0x14> 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);
+14 -20
View File
@@ -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<Client> c) {
parray<G_AddOrRemoveCondition_6x0C_6x0D, 4> 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<Client> 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<G_AddOrRemoveCondition_6x0C_6x0D, 4> 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<Client> 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,
+2 -2
View File
@@ -326,8 +326,8 @@ enum PlayerStatsChange {
void send_player_stats_change(std::shared_ptr<Client> 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<Client> c);
void send_remove_conditions(Channel& ch, uint16_t client_id);
void send_remove_negative_conditions(std::shared_ptr<Client> 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<Client> c, uint32_t floor, bool is_private);
void send_warp(std::shared_ptr<Lobby> l, uint32_t floor, bool is_private);
+1 -1
View File
@@ -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++) {
+1 -1
View File
@@ -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: