add previously-unknown GC command descriptions
This commit is contained in:
+332
-101
@@ -266,7 +266,7 @@ struct SC_TextHeader_01_06_11_B0_EE {
|
||||
// The copyright field in the below structure must contain the following text:
|
||||
// "DreamCast Port Map. Copyright SEGA Enterprises. 1999"
|
||||
|
||||
struct S_ServerInit_DC_PC_GC_02_17 {
|
||||
struct S_ServerInit_DC_PC_GC_02_17_92_9B {
|
||||
ptext<char, 0x40> copyright;
|
||||
le_uint32_t server_key; // Key for data sent by server
|
||||
le_uint32_t client_key; // Key for data sent by client
|
||||
@@ -274,6 +274,15 @@ struct S_ServerInit_DC_PC_GC_02_17 {
|
||||
ptext<char, 0xC0> after_message;
|
||||
};
|
||||
|
||||
// 03 (S->C): Legacy password check result (non-BB)
|
||||
// header.flag specifies if the password was correct. If header.flag is 0, the
|
||||
// password saved to the memory card (if any) is deleted and the client is
|
||||
// disconnected. If header.flag is nonzero, the client responds with an 04
|
||||
// command. On GC at least, it's likely this command is a relic from earlier
|
||||
// versions of the login sequence; most servers use the DB/9A/9C/9E commands for
|
||||
// logging in GC clients.
|
||||
// No other arguments
|
||||
|
||||
// 03 (S->C): Start encryption (BB)
|
||||
// Client will respond with an (encrypted) 93 command.
|
||||
// The copyright field in the below structure must contain the following text:
|
||||
@@ -287,6 +296,18 @@ struct S_ServerInit_BB_03 {
|
||||
ptext<char, 0xC0> after_message;
|
||||
};
|
||||
|
||||
// 04 (C->S): Legacy login
|
||||
// See comments on non-BB 03 (S->C). This is likely a relic of an older,
|
||||
// now-unused sequence.
|
||||
// header.flag is nonzero, but it's not clear what it's used for.
|
||||
struct C_LegacyLogin_GC_04 {
|
||||
uint32_t unknown_a1[2];
|
||||
le_uint32_t sub_version;
|
||||
le_uint32_t player_tag;
|
||||
ptext<char, 0x10> serial_number;
|
||||
ptext<char, 0x10> access_key;
|
||||
};
|
||||
|
||||
// 04 (S->C): Set guild card number and update client config ("security data")
|
||||
// The client config field in this command is only used by V3 clients (PSO GC).
|
||||
// We send it anyway to clients on earlier versions (e.g. PSO PC), but they
|
||||
@@ -298,23 +319,20 @@ struct S_ServerInit_BB_03 {
|
||||
// confuse the client, and (on pre-V3 clients) possibly corrupt the character
|
||||
// data. For this reason, newserv tries pretty hard to hide the remote guild
|
||||
// card number when clients connect to the proxy server.
|
||||
// Note: PSOBB has a handler for the 04 command, but (as of yet) I haven't
|
||||
// figured out what it does.
|
||||
|
||||
struct S_UpdateClientConfig_DC_PC_GC_04 {
|
||||
template <typename ClientConfigT>
|
||||
struct S_UpdateClientConfig {
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t guild_card_number;
|
||||
// The ClientConfig structure (defined in Client.hh) describes how newserv
|
||||
// uses this command; other servers do not use the same format for the
|
||||
// following 0x20 bytes (or may not use it at all). The cfg field is opaque to
|
||||
// the client; it will send back the contents verbatim in its next 9E command.
|
||||
ClientConfig cfg;
|
||||
ClientConfigT cfg;
|
||||
};
|
||||
|
||||
struct S_Unknown_BB_04 {
|
||||
// header.flag is used too, but it's not clear what it does
|
||||
le_uint32_t unknown_a1[12];
|
||||
};
|
||||
struct S_UpdateClientConfig_DC_PC_GC_04 : S_UpdateClientConfig<ClientConfig> { };
|
||||
struct S_UpdateClientConfig_BB_04 : S_UpdateClientConfig<ClientConfigBB> { };
|
||||
|
||||
// 05: Disconnect
|
||||
// No arguments
|
||||
@@ -366,7 +384,7 @@ struct S_GameMenuEntry {
|
||||
uint8_t flags; // 02 = locked, 04 = disabled (BB), 10 = battle, 20 = challenge
|
||||
};
|
||||
struct S_GameMenuEntry_PC_BB_08 : S_GameMenuEntry<char16_t> { };
|
||||
struct S_GameMenuEntry_GC_08 : S_GameMenuEntry<char> { };
|
||||
struct S_GameMenuEntry_GC_08_Ep3_E6 : S_GameMenuEntry<char> { };
|
||||
|
||||
// 09 (C->S): Menu item info request
|
||||
// Server will respond with an 11 command, or an A3 if it's the quest menu.
|
||||
@@ -382,7 +400,22 @@ struct C_MenuItemInfoRequest_09 {
|
||||
// Format unknown
|
||||
|
||||
// 0D: Invalid command
|
||||
// 0E: Invalid command
|
||||
|
||||
// 0E (S->C): Unknown; possibly legacy join game (GC)
|
||||
|
||||
struct S_Unknown_GC_0E {
|
||||
PlayerLobbyDataGC lobby_data[4]; // This type is a guess
|
||||
struct UnknownA0 {
|
||||
uint8_t unknown_a1[2];
|
||||
le_uint16_t unknown_a2;
|
||||
le_uint32_t unknown_a3;
|
||||
};
|
||||
UnknownA0 unknown_a0[8];
|
||||
le_uint32_t unknown_a1;
|
||||
parray<uint8_t, 0x20> unknown_a2;
|
||||
uint8_t unknown_a3[4];
|
||||
};
|
||||
|
||||
// 0F: Invalid command
|
||||
|
||||
// 10 (C->S): Menu selection
|
||||
@@ -400,7 +433,7 @@ struct C_MenuSelection {
|
||||
// 11 (S->C): Ship info
|
||||
// Same format as 01 command.
|
||||
|
||||
// 12 (S->C): Valid but ignored (BB)
|
||||
// 12 (S->C): Valid but ignored (GC, BB)
|
||||
|
||||
// 13 (S->C): Write online quest file
|
||||
// Used for downloading online quests. For download quests (to be saved to the
|
||||
@@ -426,9 +459,9 @@ struct C_WriteFileConfirmation_GC_BB_13_A7 {
|
||||
ptext<char, 0x10> filename;
|
||||
};
|
||||
|
||||
// 14 (S->C): Valid but ignored (BB)
|
||||
// 14 (S->C): Valid but ignored (GC, BB)
|
||||
// 15: Invalid command
|
||||
// 16 (S->C): Valid but ignored (BB)
|
||||
// 16 (S->C): Valid but ignored (GC, BB)
|
||||
|
||||
// 17 (S->C): Start encryption at login server (except on BB)
|
||||
// Same format as 02 command, but a different copyright string.
|
||||
@@ -438,7 +471,8 @@ struct C_WriteFileConfirmation_GC_BB_13_A7 {
|
||||
// The copyright field in the structure must contain the following text:
|
||||
// "DreamCast Lobby Server. Copyright SEGA Enterprises. 1999";
|
||||
|
||||
// 18: Invalid command
|
||||
// 18 (S->C): License verification result (GC)
|
||||
// Behaves exactly the same as 9A (S->C). No arguments except header.flag.
|
||||
|
||||
// 19 (S->C): Reconnect to different address
|
||||
// Client will disconnect, and reconnect to the given address/port.
|
||||
@@ -473,9 +507,10 @@ struct S_ReconnectSplit_19 {
|
||||
// There is a bug in V3 (and possibly all versions) where if this command is
|
||||
// sent after the client has joined a lobby, the chat log window contents will
|
||||
// appear in the message box, prepended to the message text from the command.
|
||||
// The maximum length of the message is 0x400 bytes.
|
||||
|
||||
// 1B: Invalid command
|
||||
// 1C: Invalid command
|
||||
// 1B: Valid but ignored (GC)
|
||||
// 1C: Valid but ignored (GC)
|
||||
|
||||
// 1D: Ping
|
||||
// No arguments
|
||||
@@ -647,6 +682,8 @@ struct C_OpenFileConfirmation_44_A6 {
|
||||
// When a client sends this command, the server should forward it to all players
|
||||
// in the same game/lobby, except the player who originally sent the command.
|
||||
// See ReceiveSubcommands or the subcommand index below for details on contents.
|
||||
// The data in this command may be up to 0x400 bytes in length. If it's larger,
|
||||
// the client will exhibit undefined behavior.
|
||||
|
||||
// 61 (C->S): Player data
|
||||
// See PSOPlayerDataPC, PSOPlayerDataGC, and PSOPlayerDataBB in Player.hh for
|
||||
@@ -665,6 +702,8 @@ struct C_OpenFileConfirmation_44_A6 {
|
||||
// identified by header.flag in the same game/lobby, even if that player is the
|
||||
// player who originally sent it.
|
||||
// See ReceiveSubcommands or the subcommand index below for details on contents.
|
||||
// The data in this command may be up to 0x400 bytes in length. If it's larger,
|
||||
// the client will exhibit undefined behavior.
|
||||
|
||||
// 63: Invalid command
|
||||
|
||||
@@ -741,7 +780,7 @@ struct S_JoinLobby_BB_65_67_68 : S_JoinLobby<PlayerLobbyDataBB, PlayerDispDataBB
|
||||
// This is sent to all players in a game except the leaving player.
|
||||
|
||||
// Header flag = leaving player ID (same as client_id);
|
||||
struct S_LeaveLobby_66_69 {
|
||||
struct S_LeaveLobby_66_69_Ep3_E9 {
|
||||
uint8_t client_id;
|
||||
uint8_t leader_id;
|
||||
le_uint16_t unused;
|
||||
@@ -762,10 +801,10 @@ struct S_LeaveLobby_66_69 {
|
||||
// 6B: Invalid command
|
||||
|
||||
// 6C: Broadcast command
|
||||
// Same format and usage as 60 command.
|
||||
// Same format and usage as 60 command, but with no size limit.
|
||||
|
||||
// 6D: Target command
|
||||
// Same format and usage as 62 command.
|
||||
// Same format and usage as 62 command, but with no size limit.
|
||||
|
||||
// 6E: Invalid command
|
||||
|
||||
@@ -790,7 +829,18 @@ struct S_LeaveLobby_66_69 {
|
||||
// 7D: Invalid command
|
||||
// 7E: Invalid command
|
||||
// 7F: Invalid command
|
||||
// 80: Invalid command
|
||||
|
||||
// 80 (S->C): Unknown (GC)
|
||||
// This command has the following structure, but the client ignores the contents
|
||||
// and does nothing in response.
|
||||
|
||||
struct S_Unknown_GC_80 {
|
||||
le_uint32_t unknown_a1;
|
||||
parray<uint8_t, 2> unknown_a2;
|
||||
le_uint16_t unknown_a3;
|
||||
le_uint32_t unknown_a4;
|
||||
parray<uint8_t, 8> unknown_a5;
|
||||
};
|
||||
|
||||
// 81: Simple mail
|
||||
// Format is the same in both directions. The server should forward the command
|
||||
@@ -887,9 +937,27 @@ struct S_ArrowUpdateEntry_88 {
|
||||
// 8D: Invalid command
|
||||
// 8E: Invalid command
|
||||
// 8F: Invalid command
|
||||
// 90: Invalid command
|
||||
// 91: Invalid command
|
||||
// 92: Invalid command
|
||||
|
||||
// 90 (C->S): Legacy login (GC)
|
||||
// From looking at Sylverant source, it appears this command is used during the
|
||||
// DC login sequence. If a GC client receives a 91 command, however, it will
|
||||
// also send a 90 in response, though the contents will be blank (all zeroes).
|
||||
|
||||
struct C_LegacyLogin_GC_90 {
|
||||
ptext<char, 0x11> serial_number;
|
||||
ptext<char, 0x13> access_key;
|
||||
};
|
||||
|
||||
// 90 (S->C): License verification result (GC)
|
||||
// Behaves exactly the same as 9A (S->C). No arguments except header.flag.
|
||||
|
||||
// 91 (S->C): Start encryption at login server (legacy; non-BB only)
|
||||
// Same format and usage as 17 command, except the client will respond with a 90
|
||||
// command. On versions that support it, this is strictly less useful than the
|
||||
// 17 command.
|
||||
|
||||
// 92 (S->C): Register result (non-BB)
|
||||
// Same format and usage as 9C (S->C) command.
|
||||
|
||||
// 93 (C->S): Log in (BB)
|
||||
|
||||
@@ -991,6 +1059,9 @@ struct C_Login_DC_PC_GC_9A {
|
||||
// 13 = servers under maintenance (118)
|
||||
// Seems like most (all?) of the rest of the codes are "network error" (119).
|
||||
|
||||
// 9B (S->C): Secondary server init (non-BB)
|
||||
// Behaves exactly the same as 17 (S->C).
|
||||
|
||||
// 9B (S->C): Secondary server init? (BB)
|
||||
// Format is the same as 03 (and the client uses the same encryption afterward).
|
||||
// The only differences that 9B has from 03:
|
||||
@@ -1011,7 +1082,7 @@ struct C_Register_DC_PC_GC_9C {
|
||||
ptext<char, 0x30> password;
|
||||
};
|
||||
|
||||
// 9C (S->C): Login result
|
||||
// 9C (S->C): Register result
|
||||
// The only possible error here seems to be wrong password (127) which is
|
||||
// displayed if the header.flag field is zero. If header.flag is nonzero, the
|
||||
// client proceeds with the login procedure by sending a 9D/9E.
|
||||
@@ -1055,11 +1126,11 @@ struct C_LoginWithUnusedSpace_GC_9E : C_Login_GC_9E {
|
||||
parray<uint8_t, 0x64> unused_space;
|
||||
};
|
||||
|
||||
// 9F (S->C): Unknown (BB)
|
||||
// 9F (S->C): Request client config / security data
|
||||
// No arguments
|
||||
|
||||
struct S_Unknown_BB_9F {
|
||||
le_uint32_t unknown_a1[10];
|
||||
};
|
||||
// 9F (C->S): Client config / security data response
|
||||
// Format is ClientConfig on GC, or ClientConfigBB on BB
|
||||
|
||||
// A0 (C->S): Change ship
|
||||
// This structure is for documentation only; newserv ignores the arguments here.
|
||||
@@ -1151,15 +1222,17 @@ struct C_UpdateQuestStatistics_AA {
|
||||
};
|
||||
|
||||
// AB (S->C): Confirm update quest statistics
|
||||
// TODO: Does this command have a different meaning in Episode 3?
|
||||
|
||||
struct S_ConfirmUpdateQuestStatistics_AB {
|
||||
le_uint32_t unknown_a1; // 0
|
||||
le_uint16_t unknown_a1; // 0
|
||||
be_uint16_t unknown_a2; // Probably actually unused
|
||||
le_uint16_t request_token; // Should match token sent in AA command
|
||||
le_uint16_t unknown_a2; // Schtserv always sends 0xBFFF here
|
||||
le_uint16_t unknown_a3; // Schtserv always sends 0xBFFF here
|
||||
};
|
||||
|
||||
// AC: Quest barrier
|
||||
// No arguments
|
||||
// No arguments; header.flag must be 0
|
||||
// After a quest begins loading in a game (the server sends 44/13 commands to
|
||||
// each player with the quest's data), each player will send an AC to the server
|
||||
// when it has parsed the quest and is ready to start. When all players in a
|
||||
@@ -1189,70 +1262,53 @@ struct S_ConfirmUpdateQuestStatistics_AB {
|
||||
// Client will respond with a 99 command.
|
||||
|
||||
// B2 (S->C): Execute code and/or checksum memory
|
||||
// GC v1.0/v1.1 and BB only.
|
||||
// Much of this command's information came from Sylverant's documentation.
|
||||
// Client will respond with a B3 command.
|
||||
// GC v1.0/v1.1 and BB only - this doesn't work on PSO Plus (v1.2) or Episode 3.
|
||||
// Client will respond with a B3 command with the same header.flag value as was
|
||||
// sent in the B2.
|
||||
|
||||
struct S_ExecuteCode_GC_B2 {
|
||||
// Offsets in this command are relative to the start of the header, not the
|
||||
// start of this structure! Add 4 to the offsets if computing them relative to
|
||||
// the start of this struct.
|
||||
le_uint32_t relocations_offset;
|
||||
le_uint32_t checksum_start; // CRC32; 0 = no checksum requested
|
||||
le_uint32_t checksum_size; // CRC32; 0 = no checksum requested
|
||||
// This field is big-endian on GC, little-endian on other systems. The value
|
||||
// is relative to the field itself, so e.g. 4 means that the code immediately
|
||||
// follows this field. I've seen some commands in the wild where this field is
|
||||
// set to a memory address (8xxxxxxxx) rather than an offset, but it's not
|
||||
// clear what that means.
|
||||
union {
|
||||
le_uint32_t l;
|
||||
be_uint32_t b;
|
||||
} code_offset;
|
||||
// Code (usually) immediately follows here.
|
||||
};
|
||||
|
||||
struct S_ExecuteCode_Relocations_GC_B2 {
|
||||
le_uint32_t count_offset;
|
||||
le_uint32_t count;
|
||||
le_uint32_t unknown_a1;
|
||||
le_uint32_t unknown_a2;
|
||||
le_uint32_t offset_start;
|
||||
le_uint16_t offset_entry;
|
||||
// Variable-length field:
|
||||
// le_uint16_t offsets[count];
|
||||
};
|
||||
|
||||
// Support exists in BB for this command, but to my knowledge it has never been
|
||||
// used. Like on GC, the client will respond with a B3 command.
|
||||
|
||||
struct S_ExecuteCode_BB_B2 {
|
||||
// If code_size == 0, no code is executed (but checksumming may still occur).
|
||||
struct S_ExecuteCode_B2 {
|
||||
// If code_size == 0, no code is executed, but checksumming may still occur.
|
||||
// In that case, this structure is the entire body of the command (no footer
|
||||
// is sent).
|
||||
le_uint32_t code_size; // Size of code (following this struct) and footer
|
||||
le_uint32_t checksum_address; // May be null if size is zero
|
||||
le_uint32_t checksum_size;
|
||||
le_uint32_t checksum_start; // May be null if size is zero
|
||||
le_uint32_t checksum_size; // If zero, no checksum is computed
|
||||
// The code immediately follows, ending with an S_ExecuteCode_Footer_B2
|
||||
};
|
||||
|
||||
struct S_ExecuteCode_Footer_BB_B2 {
|
||||
// Relocations is a list of words (le_uint16_t) containing the number of words
|
||||
// to skip for each relocation. The relocation pointer starts immediately
|
||||
// after the checksum_size field in the header, and advances by the value of
|
||||
// one word before each relocation. At each relocated doubleword, the address
|
||||
// of the first byte of the code (after checksum_size) is added to the
|
||||
// existing value.
|
||||
le_uint32_t relocations_offset;
|
||||
le_uint32_t num_relocations;
|
||||
le_uint32_t unknown_a1[2];
|
||||
le_uint32_t entrypoint_offset; // Relative to code base (after checksum_size)
|
||||
le_uint32_t unknown_a2[3];
|
||||
template <typename LongT>
|
||||
struct S_ExecuteCode_Footer_B2 {
|
||||
// Relocations is a list of words (uint16_t) containing the number of
|
||||
// doublewords (uint32_t) to skip for each relocation. The relocation pointer
|
||||
// starts immediately after the checksum_size field in the header, and
|
||||
// advances by the value of one relocation word (times 4) before each
|
||||
// relocation. At each relocated doubleword, the address of the first byte of
|
||||
// the code (after checksum_size) is added to the existing value.
|
||||
// If there is a small number of relocations, they may be placed in the unused
|
||||
// fields of this structure to save space and/or confuse reverse engineers.
|
||||
// The game never accesses the last 12 bytes of this structure unless
|
||||
// relocations_offset points there, so those 12 bytes may also be omitted from
|
||||
// the command entirely (without changing code_size - so code_size would
|
||||
// technically extend beyond the end of the B2 command).
|
||||
LongT relocations_offset; // Relative to code base (after checksum_size)
|
||||
LongT num_relocations;
|
||||
parray<LongT, 2> unused1;
|
||||
// entrypoint_offset is doubly indirect - it points to a pointer to a 32-bit
|
||||
// value that itself is the actual entrypoint. This is presumably done so the
|
||||
// entrypoint can be optionally relocated.
|
||||
LongT entrypoint_addr_offset; // Relative to code base (after checksum_size).
|
||||
parray<LongT, 3> unused2;
|
||||
};
|
||||
|
||||
struct S_ExecuteCode_Footer_GC_B2 : S_ExecuteCode_Footer_B2<be_uint32_t> { };
|
||||
struct S_ExecuteCode_Footer_BB_B2 : S_ExecuteCode_Footer_B2<le_uint32_t> { };
|
||||
|
||||
// B3 (C->S): Execute code and/or checksum memory result
|
||||
// GC v1.0/v1.1 and BB only.
|
||||
|
||||
struct C_ExecuteCodeResult_GC_BB_B3 {
|
||||
le_uint32_t return_value; // 0 if no code was run
|
||||
le_uint32_t checksum;
|
||||
le_uint32_t checksum; // 0 if no checksum was computed
|
||||
};
|
||||
|
||||
// B4: Invalid command
|
||||
@@ -1282,7 +1338,18 @@ struct S_RankUpdate_GC_Ep3_B7 {
|
||||
// No arguments
|
||||
// The client sends this after it receives a B8 from the server.
|
||||
|
||||
// B9: Invalid command
|
||||
// B9 (S->C): Unknown (Episode 3)
|
||||
|
||||
struct S_Unknown_GC_Ep3_B9 {
|
||||
le_uint32_t unknown_a1; // Must be 1-4 (inclusive)
|
||||
le_uint32_t unknown_a2;
|
||||
le_uint16_t unknown_a3;
|
||||
le_uint16_t unknown_a4;
|
||||
parray<uint8_t, 0x3800> unknown_a5;
|
||||
};
|
||||
|
||||
// B9 (C->S): Confirm received B9 (Episode 3)
|
||||
// No arguments
|
||||
|
||||
// BA: Meseta transaction (Episode 3)
|
||||
|
||||
@@ -1298,7 +1365,20 @@ struct S_Meseta_GC_Ep3_BA {
|
||||
le_uint32_t request_token; // Should match the token sent by the client
|
||||
};
|
||||
|
||||
// BB: Invalid command
|
||||
// BB (S->C): Unknown (Episode 3)
|
||||
// header.flag is used, but it's not clear for what.
|
||||
|
||||
struct S_Unknown_GC_Ep3_BB {
|
||||
uint8_t unknown_a1[0x20];
|
||||
struct Entry {
|
||||
le_uint16_t unknown_a1[2];
|
||||
uint8_t unknown_a2[0x20];
|
||||
};
|
||||
Entry entries[0x20];
|
||||
le_uint16_t unknown_a2[2];
|
||||
uint8_t unknown_a3[0x900];
|
||||
};
|
||||
|
||||
// BC: Invalid command
|
||||
// BD: Invalid command
|
||||
// BE: Invalid command
|
||||
@@ -1431,7 +1511,19 @@ struct C_SetBlockedSenders_C6 {
|
||||
// CB: Broadcast command (Episode 3)
|
||||
// Same as 60, but only send to Episode 3 clients.
|
||||
|
||||
// CC: Invalid command
|
||||
// CC (S->C): Unknown (Episode 3)
|
||||
|
||||
struct S_Unknown_GC_Ep3_CC {
|
||||
parray<uint8_t, 0x40> unknown_a1;
|
||||
parray<le_uint16_t, 4> unknown_a2;
|
||||
parray<uint8_t, 0x40> unknown_a3;
|
||||
struct Entry {
|
||||
parray<le_uint16_t, 2> unknown_a1;
|
||||
parray<uint8_t, 0x20> unknown_a2;
|
||||
};
|
||||
Entry entries[0x20];
|
||||
};
|
||||
|
||||
// CD: Invalid command
|
||||
// CE: Invalid command
|
||||
// CF: Invalid command
|
||||
@@ -1453,8 +1545,9 @@ struct C_SetBlockedSenders_C6 {
|
||||
// No arguments
|
||||
|
||||
// D5: Large message box
|
||||
// Same as 1A command. Sylverant's documentation notes that D5 is not valid on
|
||||
// pre-V3 versions (PSO DC or PSO PC).
|
||||
// Same as 1A command, except the maximum length of the message is 0x1000 bytes.
|
||||
// Sylverant's documentation notes that D5 is not valid on pre-V3 versions (PSO
|
||||
// DC or PSO PC).
|
||||
|
||||
// D6 (C->S): Large message box closed (GC)
|
||||
// No arguments
|
||||
@@ -1472,6 +1565,11 @@ struct C_GBAGameRequest_GC_D7 {
|
||||
ptext<char, 0x10> filename;
|
||||
};
|
||||
|
||||
// D7 (S->C): Unknown (GC)
|
||||
// This command does... something. The command isn't *completely* ignored: it
|
||||
// sets a global state variable, but it's not clear what that variable does, or
|
||||
// even if it does anything at all.
|
||||
|
||||
// D7 (S->C): Valid but ignored (BB)
|
||||
|
||||
// D8 (C->S): Info board request
|
||||
@@ -1523,7 +1621,9 @@ struct C_VerifyLicense_BB_DB {
|
||||
|
||||
// DC: Player menu state (Episode 3)
|
||||
// No arguments. It seems the client expects the server to respond with another
|
||||
// DC command with header.flag = 0.
|
||||
// DC command, the contents and flag of which are ignored entirely - all it does
|
||||
// is set a global flag on the client. This could be the mechanism for waiting
|
||||
// until all players are at the counter, like how AC (quest barrier) works.
|
||||
|
||||
// DC: Guild card data (BB)
|
||||
|
||||
@@ -1559,17 +1659,27 @@ struct S_RareMonsterConfig_BB_DE {
|
||||
|
||||
// E0 (S->C): Tournament list (Episode 3)
|
||||
|
||||
// Command is a list of these; header.flag is the entry count. Some servers
|
||||
// always send 0x740 bytes even if the entries don't fill the space.
|
||||
struct S_TournamentEntry_GC_Ep3_E0 {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
parray<uint8_t, 0x30> unknown;
|
||||
// header.flag is the count of filled-in entries.
|
||||
struct S_TournamentList_GC_Ep3_E0 {
|
||||
struct Entry {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
parray<uint8_t, 4> unknown_a1;
|
||||
le_uint32_t unknown_a2;
|
||||
parray<le_uint32_t, 8> unknown_a3;
|
||||
parray<le_uint16_t, 4> unknown_a4;
|
||||
};
|
||||
Entry entries[0x20];
|
||||
uint8_t unknown_a1[4];
|
||||
};
|
||||
|
||||
// E0 (C->S): Request team and key config (BB)
|
||||
|
||||
// E1: Invalid command
|
||||
// E1 (S->C): Unknown (Episode 3)
|
||||
|
||||
struct S_Unknown_GC_Ep3_E1 {
|
||||
uint8_t unknown_a1[0x294];
|
||||
};
|
||||
|
||||
// E2 (C->S): Tournament control (Episode 3)
|
||||
// Flag = 0 => request tournament list (server responds with E0)
|
||||
@@ -1580,9 +1690,36 @@ struct S_TournamentEntry_GC_Ep3_E0 {
|
||||
|
||||
// E2 (S->C): Tournament entry control (Episode 3)
|
||||
|
||||
struct S_TournamentControl_GC_Ep3_E2 {
|
||||
le_uint16_t unknown_a1;
|
||||
le_uint16_t unknown_a2;
|
||||
struct Entry {
|
||||
le_uint32_t menu_id;
|
||||
le_uint32_t item_id;
|
||||
parray<uint8_t, 4> unknown_a1;
|
||||
parray<le_uint32_t, 8> unknown_a2;
|
||||
};
|
||||
Entry entries[0x20];
|
||||
};
|
||||
|
||||
// E2 (S->C): Team and key config (BB)
|
||||
// See KeyAndTeamConfigBB in Player.hh for format
|
||||
|
||||
// E3 (S->C): Unknown (Episode 3)
|
||||
|
||||
struct S_Unknown_GC_Ep3_E3 {
|
||||
struct Entry {
|
||||
parray<uint8_t, 0x20> unknown_a1;
|
||||
le_uint16_t unknown_a2;
|
||||
le_uint16_t unknown_a3;
|
||||
};
|
||||
parray<uint8_t, 0x34> unknown_a1;
|
||||
Entry entries[0x20];
|
||||
parray<uint8_t, 0x100> unknown_a2;
|
||||
parray<le_uint16_t, 4> unknown_a3;
|
||||
parray<uint8_t, 0x180> unknown_a4;
|
||||
};
|
||||
|
||||
// E3 (C->S): Player preview request (BB)
|
||||
|
||||
struct C_PlayerPreviewRequest_BB_E3 {
|
||||
@@ -1603,7 +1740,8 @@ struct C_CardLobbyGame_GC_E4 {
|
||||
// Header flag = 2
|
||||
struct S_CardLobbyGame_GC_E4 {
|
||||
struct Entry {
|
||||
le_uint32_t present; // 1 = player present, 0 = no player
|
||||
le_uint16_t present; // 1 = player present, 0 = no player
|
||||
le_uint16_t unknown_a1;
|
||||
le_uint32_t guild_card_number;
|
||||
};
|
||||
Entry entries[4];
|
||||
@@ -1643,9 +1781,40 @@ struct S_ClientInit_BB_00E6 {
|
||||
le_uint32_t caps; // should be 0x00000102
|
||||
};
|
||||
|
||||
// E7 (S->C): Unknown (Episode 3)
|
||||
// Same format as E2 command.
|
||||
|
||||
// E7: Save or load full player data
|
||||
// See export_bb_player_data() in Player.cc for format.
|
||||
|
||||
// E8 (S->C): Join spectator team (probably) (Episode 3)
|
||||
|
||||
struct S_Unknown_GC_Ep3_E8 {
|
||||
parray<le_uint32_t, 0x20> unknown_a1;
|
||||
struct PlayerEntry {
|
||||
PlayerLobbyDataGC lobby_data;
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCGC disp;
|
||||
};
|
||||
PlayerEntry players[4];
|
||||
parray<uint8_t, 8> unknown_a2;
|
||||
le_uint32_t unknown_a3;
|
||||
parray<uint8_t, 4> unknown_a4;
|
||||
struct SpectatorEntry {
|
||||
// TODO: The game treats this as [8][8][16][32], but that doesn't
|
||||
// necessarily mean it's a player tag / guild card number pair.
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t guild_card_number;
|
||||
parray<be_uint32_t, 8> unknown_a2;
|
||||
uint8_t unknown_a3[2];
|
||||
le_uint16_t unknown_a4;
|
||||
parray<le_uint32_t, 2> unknown_a5;
|
||||
parray<le_uint16_t, 2> unknown_a6;
|
||||
};
|
||||
SpectatorEntry entries[12];
|
||||
parray<uint8_t, 0x20> unknown_a5;
|
||||
};
|
||||
|
||||
// E8 (C->S): Client checksum (BB)
|
||||
// E8 (S->C): Accept client checksum (BB)
|
||||
|
||||
@@ -1654,11 +1823,35 @@ struct S_AcceptClientChecksum_BB_02E8 {
|
||||
le_uint32_t unused;
|
||||
};
|
||||
|
||||
// E9: Invalid command
|
||||
// E9 (S->C): Other player left spectator team (probably) (Episode 3)
|
||||
// Same format as 66/69 commands.
|
||||
|
||||
// EA (S->C): Unknown (Episode 3)
|
||||
// header.flag is relevant - the behavior is different if it's 1 (vs. any other
|
||||
// value)
|
||||
|
||||
struct S_Unknown_GC_Ep3_EA {
|
||||
le_uint32_t unknown_a1;
|
||||
parray<uint8_t, 0x1000> unknown_a2;
|
||||
};
|
||||
|
||||
// EA: Team control (BB)
|
||||
// Format unknown. There are many subcommands - up to at least 14EA.
|
||||
|
||||
// EB (S->C): Send stream file index and chunks
|
||||
// EB (S->C): Unknown (Episode 3)
|
||||
// Looks like another lobby-joining type of command.
|
||||
|
||||
struct S_Unknown_GC_Ep3_EB {
|
||||
parray<uint8_t, 0x0c> unknown_a1;
|
||||
struct PlayerEntry {
|
||||
PlayerLobbyDataGC lobby_data;
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCGC disp;
|
||||
};
|
||||
PlayerEntry players[12];
|
||||
};
|
||||
|
||||
// EB (S->C): Send stream file index and chunks (BB)
|
||||
|
||||
// Command is a list of these; header.flag is the entry count.
|
||||
struct S_StreamFileIndexEntry_BB_01EB {
|
||||
@@ -1678,6 +1871,9 @@ struct S_StreamFileChunk_BB_02EB {
|
||||
|
||||
// EC: Leave character select (BB)
|
||||
|
||||
// ED (S->C): Unknown (Episode 3)
|
||||
// No arguments
|
||||
|
||||
// ED (C->S): Update account data (BB)
|
||||
// There are several subcommands (noted in the union below) that each update a
|
||||
// specific kind of account data.
|
||||
@@ -1693,10 +1889,45 @@ union C_UpdateAccountData_BB_ED {
|
||||
parray<uint8_t, 0xE8> customize; // 07ED
|
||||
};
|
||||
|
||||
// EE (S->C): Unknown (Episode 3)
|
||||
// This command has different forms depending on the header.flag value. Since
|
||||
// the relevant flag values match the Episodes 1 & 2 trade window commands, this
|
||||
// may be used for trading cards.
|
||||
|
||||
union S_Unknown_GC_Ep3_EE {
|
||||
struct {
|
||||
le_uint16_t unknown_a1;
|
||||
le_uint16_t unknown_a2;
|
||||
struct Entry {
|
||||
le_uint32_t unknown_a1;
|
||||
le_uint32_t unknown_a2;
|
||||
};
|
||||
Entry entries[4];
|
||||
} flag_D3;
|
||||
struct {
|
||||
le_uint32_t unknown_a1;
|
||||
} flag_D1;
|
||||
struct {
|
||||
le_uint32_t unknown_a1;
|
||||
} flag_D4;
|
||||
};
|
||||
|
||||
// EE (S->C): Scrolling message (BB)
|
||||
// Same format as 01. The message appears at the top of the screen and slowly
|
||||
// scrolls to the left.
|
||||
|
||||
// EF (S->C): Unknown (Episode 3)
|
||||
|
||||
struct S_Unknown_GC_Ep3_EF {
|
||||
le_uint16_t unknown_a1;
|
||||
le_uint16_t unknown_a2;
|
||||
struct Entry {
|
||||
le_uint16_t unknown_a1;
|
||||
le_uint16_t unknown_a2;
|
||||
};
|
||||
Entry entries[0x14];
|
||||
};
|
||||
|
||||
// EF (S->C): Unknown (BB)
|
||||
// Has an unknown number of subcommands (00EF, 01EF, etc.)
|
||||
// Contents are plain text (char).
|
||||
|
||||
@@ -137,8 +137,8 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
|
||||
|
||||
// Most servers don't include after_message or have a shorter
|
||||
// after_message than newserv does, so don't require it
|
||||
const auto& cmd = check_size_t<S_ServerInit_DC_PC_GC_02_17>(data,
|
||||
offsetof(S_ServerInit_DC_PC_GC_02_17, after_message), 0xFFFF);
|
||||
const auto& cmd = check_size_t<S_ServerInit_DC_PC_GC_02_17_92_9B>(data,
|
||||
offsetof(S_ServerInit_DC_PC_GC_02_17_92_9B, after_message), 0xFFFF);
|
||||
|
||||
if (!session.license) {
|
||||
session.log(INFO, "No license in linked session");
|
||||
@@ -747,7 +747,7 @@ static bool process_server_64(shared_ptr<ServerState>,
|
||||
|
||||
static bool process_server_66_69(shared_ptr<ServerState>,
|
||||
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
|
||||
const auto& cmd = check_size_t<S_LeaveLobby_66_69>(data);
|
||||
const auto& cmd = check_size_t<S_LeaveLobby_66_69_Ep3_E9>(data);
|
||||
size_t index = cmd.client_id;
|
||||
if (index >= session.lobby_players.size()) {
|
||||
session.log(WARNING, "Lobby leave command references missing position");
|
||||
@@ -888,7 +888,7 @@ static bool process_client_dc_pc_gc_A0_A1(shared_ptr<ServerState> s,
|
||||
}
|
||||
uint8_t leaving_id = x;
|
||||
uint8_t leader_id = session.lobby_client_id;
|
||||
S_LeaveLobby_66_69 cmd = {leaving_id, leader_id, 0};
|
||||
S_LeaveLobby_66_69_Ep3_E9 cmd = {leaving_id, leader_id, 0};
|
||||
session.send_to_end(false, 0x69, leaving_id, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
|
||||
+16
-4
@@ -377,6 +377,17 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
}
|
||||
}
|
||||
|
||||
void process_return_client_config(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 9F
|
||||
if (c->version == GameVersion::BB) {
|
||||
const auto& cfg = check_size_t<ClientConfigBB>(data);
|
||||
c->import_config(cfg);
|
||||
} else {
|
||||
const auto& cfg = check_size_t<ClientConfig>(data);
|
||||
c->import_config(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
void process_client_checksum(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 96
|
||||
check_size_t<C_ClientChecksum_GC_96>(data);
|
||||
@@ -1058,9 +1069,10 @@ void process_update_quest_statistics(shared_ptr<ServerState> s,
|
||||
}
|
||||
|
||||
S_ConfirmUpdateQuestStatistics_AB response;
|
||||
response.unknown_a1 = 0;
|
||||
response.unknown_a1 = 0x0000;
|
||||
response.unknown_a2 = 0x0000;
|
||||
response.request_token = cmd.request_token;
|
||||
response.unknown_a2 = 0xBFFF;
|
||||
response.unknown_a3 = 0xBFFF;
|
||||
send_command_t(c, 0xAB, 0x00, response);
|
||||
}
|
||||
|
||||
@@ -2036,7 +2048,7 @@ static process_command_t gc_handlers[0x100] = {
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, process_client_checksum, nullptr,
|
||||
process_player_data, process_ignored_command, nullptr, nullptr,
|
||||
process_login_c_dc_pc_gc, process_login_d_e_pc_gc, process_login_d_e_pc_gc, nullptr,
|
||||
process_login_c_dc_pc_gc, process_login_d_e_pc_gc, process_login_d_e_pc_gc, process_return_client_config,
|
||||
|
||||
// A0
|
||||
process_change_ship, process_change_block, process_quest_list_request, nullptr,
|
||||
@@ -2125,7 +2137,7 @@ static process_command_t bb_handlers[0x100] = {
|
||||
nullptr, nullptr, nullptr, process_login_bb,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
process_player_data, process_ignored_command, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, process_return_client_config,
|
||||
|
||||
// A0
|
||||
process_change_ship, process_change_block, process_quest_list_request, nullptr,
|
||||
|
||||
+4
-5
@@ -181,7 +181,6 @@ void send_command_with_header(shared_ptr<Client> c, const void* data,
|
||||
|
||||
|
||||
|
||||
|
||||
// specific command sending functions follow. in general, they're written in
|
||||
// such a way that you don't need to think about anything, even the client's
|
||||
// version, before calling them. for this reason, some of them are quite
|
||||
@@ -202,11 +201,11 @@ static const char* bb_game_server_copyright = "Phantasy Star Online Blue Burst G
|
||||
// static const char* bb_pm_server_copyright = "PSO NEW PM Server. Copyright 1999-2002 SONICTEAM.";
|
||||
static const char* patch_server_copyright = "Patch Server. Copyright SonicTeam, LTD. 2001";
|
||||
|
||||
S_ServerInit_DC_PC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
|
||||
S_ServerInit_DC_PC_GC_02_17_92_9B prepare_server_init_contents_dc_pc_gc(
|
||||
bool initial_connection,
|
||||
uint32_t server_key,
|
||||
uint32_t client_key) {
|
||||
S_ServerInit_DC_PC_GC_02_17 cmd;
|
||||
S_ServerInit_DC_PC_GC_02_17_92_9B cmd;
|
||||
cmd.copyright = initial_connection
|
||||
? dc_port_map_copyright : dc_lobby_server_copyright;
|
||||
cmd.server_key = server_key;
|
||||
@@ -1083,12 +1082,12 @@ void send_player_join_notification(shared_ptr<Client> c,
|
||||
}
|
||||
|
||||
void send_player_leave_notification(shared_ptr<Lobby> l, uint8_t leaving_client_id) {
|
||||
S_LeaveLobby_66_69 cmd = {leaving_client_id, l->leader_id, 0};
|
||||
S_LeaveLobby_66_69_Ep3_E9 cmd = {leaving_client_id, l->leader_id, 0};
|
||||
send_command_t(l, l->is_game() ? 0x66 : 0x69, leaving_client_id, cmd);
|
||||
}
|
||||
|
||||
void send_self_leave_notification(shared_ptr<Client> c) {
|
||||
S_LeaveLobby_66_69 cmd = {c->lobby_client_id, 0, 0};
|
||||
S_LeaveLobby_66_69_Ep3_E9 cmd = {c->lobby_client_id, 0, 0};
|
||||
send_command_t(c, 0x69, c->lobby_client_id, cmd);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ void send_command_with_header(std::shared_ptr<Client> c, const void* data,
|
||||
|
||||
|
||||
|
||||
S_ServerInit_DC_PC_GC_02_17 prepare_server_init_contents_dc_pc_gc(
|
||||
S_ServerInit_DC_PC_GC_02_17_92_9B prepare_server_init_contents_dc_pc_gc(
|
||||
bool initial_connection,
|
||||
uint32_t server_key,
|
||||
uint32_t client_key);
|
||||
|
||||
Reference in New Issue
Block a user