add support for PSO DC v1/v2
This commit is contained in:
@@ -10,22 +10,23 @@ newserv supports several versions of PSO. Specifically:
|
||||
| Version | Basic commands | Lobbies | Games | Proxy |
|
||||
|----------------------|----------------|---------------|---------------|---------------|
|
||||
| Dreamcast Trial | Not supported | Not supported | Not supported | Not supported |
|
||||
| Dreamcast V1 | Not supported | Not supported | Not supported | Not supported |
|
||||
| Dreamcast V2 | Not supported | Not supported | Not supported | Not supported |
|
||||
| Dreamcast V1 | Supported (1) | Supported | Supported | Supported |
|
||||
| Dreamcast V2 | Supported (1) | Supported | Supported | Supported |
|
||||
| PC | Supported | Supported | Supported | Supported |
|
||||
| GameCube Ep1&2 Trial | Untested (4) | Untested (4) | Untested (4) | Untested (4) |
|
||||
| GameCube Ep1&2 Trial | Untested (2) | Untested (2) | Untested (2) | Untested (2) |
|
||||
| GameCube Ep1&2 | Supported | Supported | Supported | Supported |
|
||||
| GameCube Ep1&2 Plus | Supported | Supported | Supported | Supported |
|
||||
| GameCube Ep3 Trial | Supported | Supported | Partial (1) | Supported |
|
||||
| GameCube Ep3 | Supported | Supported | Partial (1) | Supported |
|
||||
| XBOX Ep1&2 | Untested (3) | Untested (3) | Untested (3) | Untested (3) |
|
||||
| Blue Burst | Supported | Supported | Partial (2) | Supported |
|
||||
| GameCube Ep3 Trial | Supported | Supported | Partial (3) | Supported |
|
||||
| GameCube Ep3 | Supported | Supported | Partial (3) | Supported |
|
||||
| XBOX Ep1&2 | Untested (4) | Untested (4) | Untested (4) | Untested (4) |
|
||||
| Blue Burst | Supported | Supported | Partial (5) | Supported |
|
||||
|
||||
*Notes:*
|
||||
1. *Episode 3 players can create and join games, but CARD battles are not implemented yet.*
|
||||
2. *Some basic features are not implemented in Blue Burst games, so the games are not very playable. A lot of work has to be done to get this to a playable state.*
|
||||
3. *newserv's implementation of PSOX is based on disassembly of the client executable; it has never been tested and probably doesn't work.*
|
||||
4. *This version only supports the modem adapter, which Dolphin does not currently emulate, so it's difficult to test.*
|
||||
1. *DC support has only been tested with the US versions of PSO DC. Other versions probably don't work, but will be easy to add. Please submit a GitHub issue if you have a non-US DC version, and can provide a log from a connection attempt.*
|
||||
2. *This version only supports the modem adapter, which Dolphin does not currently emulate, so it's difficult to test.*
|
||||
3. *Episode 3 players can create and join games, but CARD battles are not implemented yet. Tournaments are also not supported.*
|
||||
4. *newserv's implementation of PSOX is based on disassembly of the client executable; it has never been tested with a real client and most likely doesn't work.*
|
||||
5. *Some basic features are not implemented in Blue Burst games, so the games are not very playable. A lot of work has to be done to get BB games to a playable state.*
|
||||
|
||||
## Future
|
||||
|
||||
|
||||
@@ -203,6 +203,15 @@ Channel::Message Channel::recv(bool print_contents) {
|
||||
< static_cast<ssize_t>(command_data.size())) {
|
||||
throw logic_error("enough bytes available, but could not remove them");
|
||||
}
|
||||
|
||||
// Some versions of PSO DC can send commands whose sizes are not a multiple of
|
||||
// 4, but the server is expected to always use a multiple of 4 bytes when
|
||||
// decrypting (the extra cipher bytes are lost). To emulate this behavior, we
|
||||
// have to round up the size for DC commands here.
|
||||
if (version == GameVersion::DC) {
|
||||
command_data.resize((command_data.size() + 3) & (~3));
|
||||
}
|
||||
|
||||
if (this->crypt_in.get()) {
|
||||
this->crypt_in->decrypt(command_data.data(), command_data.size());
|
||||
}
|
||||
|
||||
+2
-2
@@ -49,13 +49,13 @@ static void check_privileges(shared_ptr<Client> c, uint64_t mask) {
|
||||
}
|
||||
|
||||
static void check_version(shared_ptr<Client> c, GameVersion version) {
|
||||
if (c->version != version) {
|
||||
if (c->version() != version) {
|
||||
throw precondition_failed(u"$C6This command cannot\nbe used for your\nversion of PSO.");
|
||||
}
|
||||
}
|
||||
|
||||
static void check_not_version(shared_ptr<Client> c, GameVersion version) {
|
||||
if (c->version == version) {
|
||||
if (c->version() == version) {
|
||||
throw precondition_failed(u"$C6This command cannot\nbe used for your\nversion of PSO.");
|
||||
}
|
||||
}
|
||||
|
||||
+5
-6
@@ -30,10 +30,9 @@ Client::Client(
|
||||
ServerBehavior server_behavior)
|
||||
: id(next_id++),
|
||||
log("", client_log.min_level),
|
||||
version(version),
|
||||
bb_game_state(0),
|
||||
flags(flags_for_version(this->version, -1)),
|
||||
channel(bev, this->version, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
|
||||
flags(flags_for_version(version, -1)),
|
||||
channel(bev, version, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
|
||||
server_behavior(server_behavior),
|
||||
should_disconnect(false),
|
||||
should_send_to_lobby_server(false),
|
||||
@@ -64,7 +63,7 @@ Client::Client(
|
||||
this->last_switch_enabled_command.subcommand = 0;
|
||||
memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr));
|
||||
|
||||
if (this->version == GameVersion::BB) {
|
||||
if (this->version() == GameVersion::BB) {
|
||||
struct timeval tv = usecs_to_timeval(60000000); // 1 minute
|
||||
event_add(this->save_game_data_event.get(), &tv);
|
||||
}
|
||||
@@ -73,7 +72,7 @@ Client::Client(
|
||||
void Client::set_license(shared_ptr<const License> l) {
|
||||
this->license = l;
|
||||
this->game_data.serial_number = this->license->serial_number;
|
||||
if (this->version == GameVersion::BB) {
|
||||
if (this->version() == GameVersion::BB) {
|
||||
this->game_data.bb_username = this->license->username;
|
||||
}
|
||||
}
|
||||
@@ -119,7 +118,7 @@ void Client::dispatch_save_game_data(evutil_socket_t, short, void* ctx) {
|
||||
}
|
||||
|
||||
void Client::save_game_data() {
|
||||
if (this->version != GameVersion::BB) {
|
||||
if (this->version() != GameVersion::BB) {
|
||||
throw logic_error("save_game_data called for non-BB client");
|
||||
}
|
||||
if (this->game_data.account(false)) {
|
||||
|
||||
+4
-1
@@ -61,7 +61,6 @@ struct Client {
|
||||
|
||||
// License & account
|
||||
std::shared_ptr<const License> license;
|
||||
GameVersion version;
|
||||
|
||||
// Note: these fields are included in the client config. On GC, the client
|
||||
// config can be up to 0x20 bytes; on BB it can be 0x28 bytes. We don't use
|
||||
@@ -111,6 +110,10 @@ struct Client {
|
||||
|
||||
Client(struct bufferevent* bev, GameVersion version, ServerBehavior server_behavior);
|
||||
|
||||
inline GameVersion version() const {
|
||||
return this->channel.version;
|
||||
}
|
||||
|
||||
void set_license(std::shared_ptr<const License> l);
|
||||
|
||||
ClientConfig export_config() const;
|
||||
|
||||
+180
-91
@@ -296,6 +296,8 @@ struct SC_TextHeader_01_06_11_B0_EE {
|
||||
// PC, the client will respond with an (encrypted) 9A or 9D command.
|
||||
// The copyright field in the below structure must contain the following text:
|
||||
// "DreamCast Lobby Server. Copyright SEGA Enterprises. 1999"
|
||||
// (The above text is required on all versions that use this command, including
|
||||
// those versions not running on the DreamCast.)
|
||||
|
||||
struct S_ServerInit_DC_PC_V3_02_17_91_9B {
|
||||
ptext<char, 0x40> copyright;
|
||||
@@ -307,6 +309,7 @@ struct S_ServerInit_DC_PC_V3_02_17_91_9B {
|
||||
};
|
||||
|
||||
// 03 (C->S): Legacy login (non-BB)
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
struct C_LegacyLogin_PC_V3_03 {
|
||||
le_uint64_t unused; // Same as unused field in 9D/9E
|
||||
@@ -349,6 +352,7 @@ struct S_ServerInit_BB_03_9B {
|
||||
// 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_PC_V3_04 {
|
||||
le_uint64_t unused1; // Same as unused field in 9D/9E
|
||||
le_uint32_t sub_version;
|
||||
@@ -368,7 +372,7 @@ struct C_LegacyLogin_BB_04 {
|
||||
// 04 (S->C): Set guild card number and update client config ("security data")
|
||||
// header.flag specifies an error code; the format described below is only used
|
||||
// if this code is 0 (no error). Otherwise, the command has no arguments.
|
||||
// Error codes:
|
||||
// Error codes (on GC):
|
||||
// 01 = Line is busy (103)
|
||||
// 02 = Already logged in (104)
|
||||
// 03 = Incorrect password (106)
|
||||
@@ -408,6 +412,12 @@ struct S_UpdateClientConfig_BB_04 : S_UpdateClientConfig<ClientConfigBB> { };
|
||||
|
||||
// 05: Disconnect
|
||||
// No arguments
|
||||
// Sending this command to a client will cause it to disconnect. There's no
|
||||
// advantage to doing this over simply closing the TCP connection. Clients will
|
||||
// send this command to the server when they are about to disconnect, but the
|
||||
// server does not need to close the connection when it receives this command
|
||||
// (and in some cases, the client will send multiple 05 commands before actually
|
||||
// disconnecting).
|
||||
|
||||
// 06: Chat
|
||||
// Server->client format is same as 01 command. The maximum size of the message
|
||||
@@ -453,11 +463,16 @@ struct S_GameMenuEntry {
|
||||
uint8_t difficulty_tag; // 0x0A = Ep3; else difficulty + 0x22 (so 0x25 = Ult)
|
||||
uint8_t num_players;
|
||||
ptext<CharT, 0x10> name;
|
||||
uint8_t episode; // 40 = Ep1, 41 = Ep2, 43 = Ep4. Ignored on Ep3
|
||||
// The episode field is used differently by different versions:
|
||||
// - On DC v1, PC, and GC Episode 3, the value is ignored.
|
||||
// - On DC v2, 1 means v1 players can't join the game, and 0 means they can.
|
||||
// - On GC Ep1&2, 0x40 means Episode 1, and 0x41 means Episode 2.
|
||||
// - On BB, 0x40/0x41 mean Episodes 1/2 as on GC, and 0x43 means Episode 4.
|
||||
uint8_t episode;
|
||||
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_V3_08_Ep3_E6 : S_GameMenuEntry<char> { };
|
||||
struct S_GameMenuEntry_DC_V3_08_Ep3_E6 : S_GameMenuEntry<char> { };
|
||||
|
||||
// 09 (C->S): Menu item info request
|
||||
// Server will respond with an 11 command, or an A3 or A5 if the specified menu
|
||||
@@ -480,6 +495,7 @@ struct C_MenuItemInfoRequest_09 {
|
||||
// There is a failure mode in the command handlers on PC and V3 that causes the
|
||||
// thread receiving the command to loop infinitely doing nothing, effectively
|
||||
// softlocking the game.
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
struct S_Unknown_PC_0E {
|
||||
parray<uint8_t, 0x08> unknown_a1;
|
||||
@@ -488,7 +504,7 @@ struct S_Unknown_PC_0E {
|
||||
};
|
||||
|
||||
struct S_Unknown_GC_0E {
|
||||
PlayerLobbyDataGC lobby_data[4]; // This type is a guess
|
||||
PlayerLobbyDataDCGC lobby_data[4]; // This type is a guess
|
||||
struct UnknownA0 {
|
||||
uint8_t unknown_a1[2];
|
||||
le_uint16_t unknown_a2;
|
||||
@@ -546,6 +562,7 @@ struct C_MenuSelection_PC_BB_10_Flag03 : C_MenuSelection_10_Flag03<char16_t> { }
|
||||
// Same format as 01 command.
|
||||
|
||||
// 12 (S->C): Valid but ignored (PC/V3/BB)
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
// 13 (S->C): Write online quest file
|
||||
// Used for downloading online quests. For download quests (to be saved to the
|
||||
@@ -561,9 +578,9 @@ struct S_WriteFile_13_A7 {
|
||||
le_uint32_t data_size;
|
||||
};
|
||||
|
||||
// 13 (C->S): Confirm file write
|
||||
// 13 (C->S): Confirm file write (V3/BB)
|
||||
// Client sends this in response to each 13 sent by the server. It appears these
|
||||
// are only sent by V3 and BB - PSO PC does not send these.
|
||||
// are only sent by V3 and BB - PSO DC and PC do not send these.
|
||||
// This structure is for documentation only; newserv ignores these.
|
||||
|
||||
// header.flag = file chunk index (same as in the 13/A7 sent by the server)
|
||||
@@ -572,8 +589,12 @@ struct C_WriteFileConfirmation_V3_BB_13_A7 {
|
||||
};
|
||||
|
||||
// 14 (S->C): Valid but ignored (PC/V3/BB)
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
// 15: Invalid command
|
||||
|
||||
// 16 (S->C): Valid but ignored (PC/V3/BB)
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
// 17 (S->C): Start encryption at login server (except on BB)
|
||||
// Same format as 02 command, but a different copyright string.
|
||||
@@ -588,9 +609,12 @@ struct C_WriteFileConfirmation_V3_BB_13_A7 {
|
||||
|
||||
// 18 (S->C): License verification result (PC/V3)
|
||||
// Behaves exactly the same as 9A (S->C). No arguments except header.flag.
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
// 19 (S->C): Reconnect to different address
|
||||
// Client will disconnect, and reconnect to the given address/port.
|
||||
// Client will disconnect, and reconnect to the given address/port. Encryption
|
||||
// will be disabled on the new connection; the server should send an appropriate
|
||||
// command to enable it when the client connects.
|
||||
// Note: PSO XB seems to ignore the address field, which makes sense given its
|
||||
// networking architecture.
|
||||
|
||||
@@ -600,7 +624,7 @@ struct S_Reconnect_19 {
|
||||
le_uint16_t unused;
|
||||
};
|
||||
|
||||
// Because PSO PC and some versions of PSO GC use the same port but different
|
||||
// Because PSO PC and some versions of PSO DC/GC use the same port but different
|
||||
// protocols, we use a specially-crafted 19 command to send them to two
|
||||
// different ports depending on the client version. I first saw this technique
|
||||
// used by Schthack; I don't know if it was his original creation.
|
||||
@@ -618,7 +642,7 @@ struct S_ReconnectSplit_19 {
|
||||
};
|
||||
|
||||
// 1A (S->C): Large message box
|
||||
// On V3, client will usually respond with a D6 command (see D6 for more
|
||||
// On V3, client will sometimes respond with a D6 command (see D6 for more
|
||||
// information).
|
||||
// Contents are plain text (char on DC/V3, char16_t on PC/BB). There must be at
|
||||
// least one null character ('\0') before the end of the command data.
|
||||
@@ -629,7 +653,10 @@ struct S_ReconnectSplit_19 {
|
||||
// between this command and the D5 command.
|
||||
|
||||
// 1B (S->C): Valid but ignored (PC/V3)
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
// 1C (S->C): Valid but ignored (PC/V3)
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
// 1D: Ping
|
||||
// No arguments
|
||||
@@ -640,14 +667,14 @@ struct S_ReconnectSplit_19 {
|
||||
|
||||
// 1F (C->S): Request information menu
|
||||
// No arguments
|
||||
// This command is used in PSO PC. It exists in V3 as well but is apparently
|
||||
// unused.
|
||||
// This command is used in PSO DC and PC. It exists in V3 as well but is
|
||||
// apparently unused.
|
||||
|
||||
// 1F (S->C): Information menu
|
||||
// Same format and usage as 07 command, except:
|
||||
// - The menu title will say "Information" instead of "Ship Select".
|
||||
// - There is no way to request information before selecting a menu item (the
|
||||
// client will not send 09 commands).
|
||||
// - There is no way to request details before selecting a menu item (the client
|
||||
// will not send 09 commands).
|
||||
// - The player can press a button (B on GC, for example) to close the menu
|
||||
// without selecting anything, unlike the ship select menu. The client does
|
||||
// not send anything when this happens.
|
||||
@@ -766,8 +793,16 @@ struct S_GuildCardSearchResult_BB_41
|
||||
// Unlike the A6 command, the client will react to a 44 command only if the
|
||||
// filename ends in .bin or .dat.
|
||||
|
||||
struct S_OpenFile_DC_44_A6 {
|
||||
ptext<char, 0x20> name; // Should begin with "PSO/"
|
||||
parray<uint8_t, 2> unused;
|
||||
uint8_t flags;
|
||||
ptext<char, 0x11> filename;
|
||||
le_uint32_t file_size;
|
||||
};
|
||||
|
||||
struct S_OpenFile_PC_V3_44_A6 {
|
||||
ptext<char, 0x20> name;
|
||||
ptext<char, 0x20> name; // Should begin with "PSO/"
|
||||
parray<uint8_t, 2> unused;
|
||||
le_uint16_t flags; // 0 = download quest, 2 = online quest, 3 = Episode 3
|
||||
ptext<char, 0x10> filename;
|
||||
@@ -792,6 +827,7 @@ struct S_OpenFile_BB_44_A6 {
|
||||
// 44 (C->S): Confirm open file
|
||||
// Client sends this in response to each 44 sent by the server.
|
||||
// This structure is for documentation only; newserv ignores these.
|
||||
// TODO: Is this command sent by DC/PC clients?
|
||||
|
||||
// header.flag = quest number (sort of - seems like the client just echoes
|
||||
// whatever the server sent in its header.flag field. Also quest numbers can be
|
||||
@@ -836,10 +872,10 @@ struct C_OpenFileConfirmation_44_A6 {
|
||||
// the client will exhibit undefined behavior.
|
||||
|
||||
// 61 (C->S): Player data
|
||||
// See PSOPlayerDataPC, PSOPlayerDataV3, and PSOPlayerDataBB in Player.hh for
|
||||
// this command's format. header.flag specifies the format version, which is
|
||||
// related to (but not identical to) the game's major version. For example, the
|
||||
// format version is 02 on PSO PC, 03 on PSO GC, XB, and BB, and 04 on Ep3.
|
||||
// See the PSOPlayerData structs in Player.hh for this command's format.
|
||||
// header.flag specifies the format version, which is related to (but not
|
||||
// identical to) the game's major version. For example, the format version is 01
|
||||
// on DC v1, 02 on PSO PC, 03 on PSO GC, XB, and BB, and 04 on Ep3.
|
||||
// Upon joining a game, the client assigns inventory item IDs sequentially as
|
||||
// (0x00010000 + (0x00200000 * lobby_client_id) + x). So, for example, player
|
||||
// 3's 8th item's ID would become 0x00610007. The item IDs from the last game
|
||||
@@ -882,26 +918,28 @@ struct S_JoinGame {
|
||||
uint8_t section_id;
|
||||
uint8_t challenge_mode;
|
||||
le_uint32_t rare_seed;
|
||||
// Note: The 64 command for PSO DC ends here (the next 4 fields are ignored).
|
||||
// newserv sends them anyway for code simplicity reasons.
|
||||
uint8_t episode;
|
||||
uint8_t unused2; // Should be 1 for PSO PC?
|
||||
uint8_t solo_mode;
|
||||
uint8_t unused3;
|
||||
};
|
||||
|
||||
struct S_JoinGame_PC_64 : S_JoinGame<PlayerLobbyDataPC, PlayerDispDataPCV3> { };
|
||||
struct S_JoinGame_GC_64 : S_JoinGame<PlayerLobbyDataGC, PlayerDispDataPCV3> { };
|
||||
struct S_JoinGame_PC_64 : S_JoinGame<PlayerLobbyDataPC, PlayerDispDataDCPCV3> { };
|
||||
struct S_JoinGame_DC_GC_64 : S_JoinGame<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3> { };
|
||||
|
||||
struct S_JoinGame_GC_Ep3_64 : S_JoinGame_GC_64 {
|
||||
struct S_JoinGame_GC_Ep3_64 : S_JoinGame_DC_GC_64 {
|
||||
// This field is only present if the game (and client) is Episode 3. Similarly
|
||||
// to lobby_data in the base struct, all four of these are always present and
|
||||
// they are filled in in slot positions.
|
||||
struct {
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCV3 disp;
|
||||
PlayerDispDataDCPCV3 disp;
|
||||
} players_ep3[4];
|
||||
};
|
||||
|
||||
struct S_JoinGame_XB_64 : S_JoinGame<PlayerLobbyDataXB, PlayerDispDataPCV3> {
|
||||
struct S_JoinGame_XB_64 : S_JoinGame<PlayerLobbyDataXB, PlayerDispDataDCPCV3> {
|
||||
parray<le_uint32_t, 6> unknown_a1;
|
||||
};
|
||||
|
||||
@@ -920,7 +958,7 @@ struct S_JoinLobby {
|
||||
uint8_t unknown_a1;
|
||||
uint8_t event;
|
||||
uint8_t unknown_a2;
|
||||
parray<uint8_t, 4> unknown_a3;
|
||||
le_uint32_t unused;
|
||||
struct Entry {
|
||||
LobbyDataT lobby_data;
|
||||
PlayerInventory inventory;
|
||||
@@ -935,9 +973,9 @@ struct S_JoinLobby {
|
||||
}
|
||||
};
|
||||
struct S_JoinLobby_PC_65_67_68
|
||||
: S_JoinLobby<PlayerLobbyDataPC, PlayerDispDataPCV3> { };
|
||||
struct S_JoinLobby_GC_65_67_68
|
||||
: S_JoinLobby<PlayerLobbyDataGC, PlayerDispDataPCV3> { };
|
||||
: S_JoinLobby<PlayerLobbyDataPC, PlayerDispDataDCPCV3> { };
|
||||
struct S_JoinLobby_DC_GC_65_67_68
|
||||
: S_JoinLobby<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3> { };
|
||||
struct S_JoinLobby_BB_65_67_68
|
||||
: S_JoinLobby<PlayerLobbyDataBB, PlayerDispDataBB> { };
|
||||
|
||||
@@ -955,7 +993,7 @@ struct S_JoinLobby_XB_65_67_68 {
|
||||
struct Entry {
|
||||
PlayerLobbyDataXB lobby_data;
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCV3 disp;
|
||||
PlayerDispDataDCPCV3 disp;
|
||||
};
|
||||
// Note: not all of these will be filled in and sent if the lobby isn't full
|
||||
// (the command size will be shorter than this struct's size)
|
||||
@@ -1022,6 +1060,7 @@ struct S_LeaveLobby_66_69_Ep3_E9 {
|
||||
// 7F: Invalid command
|
||||
|
||||
// 80 (S->C): Ignored (PC/V3)
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
struct S_Unknown_PC_V3_80 {
|
||||
le_uint32_t which; // Expected to be in the range 00-0B... maybe client ID?
|
||||
@@ -1050,7 +1089,7 @@ struct SC_SimpleMail_81 {
|
||||
};
|
||||
|
||||
struct SC_SimpleMail_PC_81 : SC_SimpleMail_81<char16_t> { };
|
||||
struct SC_SimpleMail_V3_81 : SC_SimpleMail_81<char> { };
|
||||
struct SC_SimpleMail_DC_V3_81 : SC_SimpleMail_81<char> { };
|
||||
|
||||
struct SC_SimpleMail_BB_81 {
|
||||
le_uint32_t player_tag;
|
||||
@@ -1092,6 +1131,10 @@ struct C_LobbySelection_84 {
|
||||
// 87: Invalid command
|
||||
|
||||
// 88 (S->C): Update lobby arrows
|
||||
// If this command is sent while a client is joining a lobby, the client may
|
||||
// ignore it. For this reason, the server should wait a few seconds after a
|
||||
// client joins a lobby before sending an 88 command.
|
||||
// This command is not supported on DC v1.
|
||||
|
||||
// Command is a list of these; header.flag is the entry count. There should
|
||||
// be an update for every player in the lobby in this command, even if their
|
||||
@@ -1103,7 +1146,7 @@ struct S_ArrowUpdateEntry_88 {
|
||||
};
|
||||
|
||||
// The arrow color values are:
|
||||
// 00 - none
|
||||
// any number not specified below (including 00) - none
|
||||
// 01 - red
|
||||
// 02 - blue
|
||||
// 03 - green
|
||||
@@ -1116,7 +1159,6 @@ struct S_ArrowUpdateEntry_88 {
|
||||
// 0A - white
|
||||
// 0B - white
|
||||
// 0C - black
|
||||
// anything else - none
|
||||
|
||||
// 89 (C->S): Set lobby arrow
|
||||
// header.flag = arrow color number (see above); no other arguments.
|
||||
@@ -1138,12 +1180,13 @@ struct S_ArrowUpdateEntry_88 {
|
||||
// 8E: Invalid command
|
||||
// 8F: Invalid command
|
||||
|
||||
// 90 (C->S): Legacy login (PC/V3)
|
||||
// From looking at Sylverant source, it appears this command is used during the
|
||||
// DC login sequence. If a V3 client receives a 91 command, however, it will
|
||||
// also send a 90 in response, though the contents will be blank (all zeroes).
|
||||
// 90 (C->S): V1 login (DC/PC/V3)
|
||||
// This command is used during the DCv1 login sequence; a DCv1 client will
|
||||
// respond to a 17 command with an (encrypted) 90. If a V3 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_V3_90 {
|
||||
struct C_LoginV1_DC_PC_V3_90 {
|
||||
ptext<char, 0x11> serial_number;
|
||||
ptext<char, 0x11> access_key;
|
||||
parray<uint8_t, 2> unused;
|
||||
@@ -1157,27 +1200,44 @@ struct C_LegacyLogin_V3_90 {
|
||||
// command. On versions that support it, this is strictly less useful than the
|
||||
// 17 command.
|
||||
|
||||
// 92 (C->S): Register (DC)
|
||||
|
||||
struct C_RegisterV1_DC_92 {
|
||||
parray<uint8_t, 0x0C> unknown_a1;
|
||||
uint8_t unknown_a2;
|
||||
uint8_t language; // TODO: This is a guess; verify it
|
||||
uint8_t unknown_a3[2];
|
||||
ptext<char, 0x10> hardware_id;
|
||||
parray<uint8_t, 0x50> unused1;
|
||||
ptext<char, 0x20> email; // According to Sylverant documentation
|
||||
parray<uint8_t, 0x10> unused2;
|
||||
};
|
||||
|
||||
// 92 (S->C): Register result (non-BB)
|
||||
// Same format and usage as 9C (S->C) command.
|
||||
|
||||
// 93 (C->S): Log in (DCv1)
|
||||
|
||||
struct C_Login_DCv1_93 {
|
||||
struct C_LoginV1_DC_93 {
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t guild_card_number;
|
||||
le_uint32_t unknown_a1;
|
||||
le_uint32_t unknown_a2;
|
||||
le_uint32_t sub_version;
|
||||
uint8_t unknown_a3; // Probably is_extended
|
||||
uint8_t language_code;
|
||||
uint8_t unused1[2];
|
||||
uint8_t language;
|
||||
parray<uint8_t, 2> unused1;
|
||||
ptext<char, 0x11> serial_number;
|
||||
ptext<char, 0x11> access_key;
|
||||
// Note: The hardware_id field is likely shorter than this (only 8 bytes
|
||||
// appear to actually be used).
|
||||
ptext<char, 0x60> hardware_id;
|
||||
ptext<char, 0x10> name;
|
||||
uint8_t unused2[2];
|
||||
parray<uint8_t, 2> unused2;
|
||||
};
|
||||
|
||||
struct C_LoginExtendedV1_DC_93 : C_LoginV1_DC_93 {
|
||||
parray<uint8_t, 0x64> unused3;
|
||||
};
|
||||
|
||||
// 93 (C->S): Log in (BB)
|
||||
@@ -1229,6 +1289,7 @@ struct C_Login_BB_93 {
|
||||
// Client will respond with a 61 command.
|
||||
|
||||
// 96 (C->S): Character save information
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
struct C_CharSaveInfo_V3_BB_96 {
|
||||
// This field appears to be a checksum or random stamp of some sort; it seems
|
||||
@@ -1259,6 +1320,7 @@ struct C_CharSaveInfo_V3_BB_96 {
|
||||
// No arguments
|
||||
|
||||
// 9A (C->S): Initial login (no password or client config)
|
||||
// Not used on DCv1 - that version uses 90 instead.
|
||||
|
||||
struct C_Login_DC_PC_V3_9A {
|
||||
ptext<char, 0x10> v1_serial_number;
|
||||
@@ -1295,6 +1357,7 @@ struct C_Login_DC_PC_V3_9A {
|
||||
|
||||
// 9B (S->C): Secondary server init (non-BB)
|
||||
// Behaves exactly the same as 17 (S->C).
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
// 9B (S->C): Secondary server init (BB)
|
||||
// Format is the same as 03 (and the client uses the same encryption afterward).
|
||||
@@ -1330,11 +1393,13 @@ struct C_Register_BB_9C {
|
||||
};
|
||||
|
||||
// 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.
|
||||
// On GC, the only possible error here seems to be wrong password (127) which is
|
||||
// displayed if the header.flag field is zero. On DCv2/PC, the error text says
|
||||
// something like "registration failed" instead. If header.flag is nonzero, the
|
||||
// client proceeds with the login procedure by sending a 9D or 9E.
|
||||
|
||||
// 9D (C->S): Log in without client config (PC/GC)
|
||||
// 9D (C->S): Log in without client config (DCv2/PC/GC)
|
||||
// Not used on DCv1 - that version uses 93 instead.
|
||||
// Not used on most versions of V3 - the client sends 9E instead. The one
|
||||
// type of PSO V3 that uses 9D is the Trial Edition of Episodes 1&2.
|
||||
// The extended version of this command is sent if the client has not yet
|
||||
@@ -1350,7 +1415,7 @@ struct C_Login_MeetUserExtension {
|
||||
ptext<CharT, 0x20> target_player_name;
|
||||
};
|
||||
|
||||
struct C_Login_PC_GC_9D {
|
||||
struct C_Login_DC_PC_GC_9D {
|
||||
le_uint32_t player_tag; // 0x00010000 if guild card is set (via 04)
|
||||
le_uint32_t guild_card_number; // 0xFFFFFFFF if not set
|
||||
le_uint64_t unused;
|
||||
@@ -1366,15 +1431,16 @@ struct C_Login_PC_GC_9D {
|
||||
ptext<char, 0x30> access_key2; // On XB, this is the XBL user ID
|
||||
ptext<char, 0x10> name;
|
||||
};
|
||||
struct C_LoginExtended_PC_GC_9D : C_Login_PC_GC_9D {
|
||||
struct C_LoginExtended_DC_PC_GC_9D : C_Login_DC_PC_GC_9D {
|
||||
C_Login_MeetUserExtension<char16_t> extension;
|
||||
};
|
||||
|
||||
// 9E (C->S): Log in with client config (V3/BB)
|
||||
// Not used on GC Episodes 1&2 Trial Edition.
|
||||
// The extended version of this command is used in the same circumstances as
|
||||
// when PSO PC uses the extended version of the 9D command.
|
||||
|
||||
struct C_Login_GC_9E : C_Login_PC_GC_9D {
|
||||
struct C_Login_GC_9E : C_Login_DC_PC_GC_9D {
|
||||
union ClientConfigFields {
|
||||
ClientConfig cfg;
|
||||
parray<uint8_t, 0x20> data;
|
||||
@@ -1424,6 +1490,8 @@ struct C_LoginExtended_BB_9E {
|
||||
|
||||
// A0 (C->S): Change ship
|
||||
// This structure is for documentation only; newserv ignores the arguments here.
|
||||
// TODO: This structore is valid for GC clients; check if this command has the
|
||||
// same arguments on DC/PC.
|
||||
|
||||
struct C_ChangeShipOrBlock_A0_A1 {
|
||||
le_uint32_t player_tag;
|
||||
@@ -1457,7 +1525,7 @@ struct S_QuestMenuEntry {
|
||||
ptext<CharT, ShortDescLength> short_description;
|
||||
};
|
||||
struct S_QuestMenuEntry_PC_A2_A4 : S_QuestMenuEntry<char16_t, 0x70> { };
|
||||
struct S_QuestMenuEntry_GC_A2_A4 : S_QuestMenuEntry<char, 0x70> { };
|
||||
struct S_QuestMenuEntry_DC_GC_A2_A4 : S_QuestMenuEntry<char, 0x70> { };
|
||||
struct S_QuestMenuEntry_XB_A2_A4 : S_QuestMenuEntry<char, 0x80> { };
|
||||
struct S_QuestMenuEntry_BB_A2_A4 : S_QuestMenuEntry<char16_t, 0x7A> { };
|
||||
|
||||
@@ -1498,10 +1566,15 @@ struct S_QuestMenuEntry_BB_A2_A4 : S_QuestMenuEntry<char16_t, 0x7A> { };
|
||||
// No arguments
|
||||
// This command is sent when the in-game quest menu (A2) is closed. When the
|
||||
// download quest menu is closed, either by downloading a quest or canceling,
|
||||
// the client sends A0 instead.
|
||||
// the client sends A0 instead. The existance of the A0 response on the download
|
||||
// case makes sense, because the client may not be in a lobby and the server may
|
||||
// need to send another menu or redirect the client. But for the online quest
|
||||
// menu, the client is already in a game and can move normally after canceling
|
||||
// the quest menu, so it's not obvious why A9 is needed at all. newserv (and
|
||||
// probably all other private servers) ignores it.
|
||||
// Curiously, PSO GC sends uninitialized data in the flag argument.
|
||||
|
||||
// AA (C->S): Update quest statistics
|
||||
// AA (C->S): Update quest statistics (V3/BB)
|
||||
// This command is used in Maximum Attack 2, but its format is unlikely to be
|
||||
// specific to that quest. The structure here represents the only instance I've
|
||||
// seen so far.
|
||||
@@ -1509,7 +1582,7 @@ struct S_QuestMenuEntry_BB_A2_A4 : S_QuestMenuEntry<char16_t, 0x7A> { };
|
||||
// This command is likely never sent by PSO GC Episodes 1&2 Trial Edition,
|
||||
// because the following command (AB) is definitely not valid on that version.
|
||||
|
||||
struct C_UpdateQuestStatistics_AA {
|
||||
struct C_UpdateQuestStatistics_V3_BB_AA {
|
||||
le_uint16_t quest_internal_id;
|
||||
le_uint16_t unused;
|
||||
le_uint16_t request_token;
|
||||
@@ -1520,12 +1593,12 @@ struct C_UpdateQuestStatistics_AA {
|
||||
parray<le_uint32_t, 5> unknown_a3;
|
||||
};
|
||||
|
||||
// AB (S->C): Confirm update quest statistics
|
||||
// AB (S->C): Confirm update quest statistics (V3/BB)
|
||||
// This command is not valid on PSO GC Episodes 1&2 Trial Edition.
|
||||
// TODO: Does this command have a different meaning in Episode 3? Is it used at
|
||||
// all there, or is the handler an undeleted vestige from Episodes 1&2?
|
||||
|
||||
struct S_ConfirmUpdateQuestStatistics_AB {
|
||||
struct S_ConfirmUpdateQuestStatistics_V3_BB_AB {
|
||||
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
|
||||
@@ -1551,6 +1624,7 @@ struct S_ConfirmUpdateQuestStatistics_AB {
|
||||
// Same format as 01 command.
|
||||
// The message appears as an overlay on the right side of the screen. The player
|
||||
// doesn't do anything to dismiss it; it will disappear after a few seconds.
|
||||
// TODO: Check if this command exists on DC v1/v2.
|
||||
|
||||
// B1 (C->S): Request server time
|
||||
// No arguments
|
||||
@@ -1590,8 +1664,8 @@ struct S_ExecuteCode_B2 {
|
||||
|
||||
template <typename LongT>
|
||||
struct S_ExecuteCode_Footer_B2 {
|
||||
// Relocations is a list of words (le_uint16_t on PC/XB/BB, be_uint16_t on GC)
|
||||
// containing the number of doublewords (uint32_t) to skip for each
|
||||
// Relocations is a list of words (le_uint16_t on DC/PC/XB/BB, be_uint16_t on
|
||||
// GC) 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
|
||||
@@ -1619,13 +1693,14 @@ struct S_ExecuteCode_Footer_B2 {
|
||||
};
|
||||
|
||||
struct S_ExecuteCode_Footer_GC_B2 : S_ExecuteCode_Footer_B2<be_uint32_t> { };
|
||||
struct S_ExecuteCode_Footer_PC_XB_BB_B2
|
||||
struct S_ExecuteCode_Footer_DC_PC_XB_BB_B2
|
||||
: S_ExecuteCode_Footer_B2<le_uint32_t> { };
|
||||
|
||||
// B3 (C->S): Execute code and/or checksum memory result
|
||||
// Not used on versions that don't support the B2 command (see above).
|
||||
|
||||
struct C_ExecuteCodeResult_B3 {
|
||||
// On DC, return_value has the value in r0 when the function returns.
|
||||
// On PC, return_value is always 0.
|
||||
// On GC, return_value has the value in r3 when the function returns.
|
||||
// On XB and BB, return_value has the value in eax when the function returns.
|
||||
@@ -1648,7 +1723,7 @@ struct S_RankUpdate_GC_Ep3_B7 {
|
||||
le_uint32_t jukebox_songs_unlocked;
|
||||
};
|
||||
|
||||
// B7 (C->S): Confirm rank update
|
||||
// B7 (C->S): Confirm rank update (Episode 3)
|
||||
// No arguments
|
||||
// The client sends this after it receives a B7 from the server.
|
||||
|
||||
@@ -1719,16 +1794,17 @@ struct S_Unknown_GC_Ep3_BB {
|
||||
// C0 (S->C): Choice search options
|
||||
|
||||
// Command is a list of these; header.flag is the entry count (incl. top-level).
|
||||
template <typename CharT>
|
||||
template <typename ItemIDT, typename CharT>
|
||||
struct S_ChoiceSearchEntry {
|
||||
// Category IDs are nonzero; if the high byte of the ID is nonzero then the
|
||||
// category can be set by the user at any time; otherwise it can't.
|
||||
le_uint16_t parent_category_id; // 0 for top-level categories
|
||||
le_uint16_t category_id;
|
||||
ItemIDT parent_category_id; // 0 for top-level categories
|
||||
ItemIDT category_id;
|
||||
ptext<CharT, 0x1C> text;
|
||||
};
|
||||
struct S_ChoiceSearchEntry_DC_V3_C0 : S_ChoiceSearchEntry<char> { };
|
||||
struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry<char16_t> { };
|
||||
struct S_ChoiceSearchEntry_DC_C0 : S_ChoiceSearchEntry<le_uint32_t, char> { };
|
||||
struct S_ChoiceSearchEntry_V3_C0 : S_ChoiceSearchEntry<le_uint16_t, char> { };
|
||||
struct S_ChoiceSearchEntry_PC_BB_C0 : S_ChoiceSearchEntry<le_uint16_t, char16_t> { };
|
||||
|
||||
// Top-level categories are things like "Level", "Class", etc.
|
||||
// Choices for each top-level category immediately follow the category, so
|
||||
@@ -1760,7 +1836,7 @@ struct C_CreateGame {
|
||||
// players; if set to 1, it's v2-only.
|
||||
uint8_t episode; // 1-4 on V3+ (3 on Episode 3); unused on DC/PC
|
||||
};
|
||||
struct C_CreateGame_DC_V3_C1_Ep3_EC : C_CreateGame<char> { };
|
||||
struct C_CreateGame_DC_V3_0C_C1_Ep3_EC : C_CreateGame<char> { };
|
||||
struct C_CreateGame_PC_C1 : C_CreateGame<char16_t> { };
|
||||
|
||||
struct C_CreateGame_BB_C1 : C_CreateGame<char16_t> {
|
||||
@@ -1771,28 +1847,24 @@ struct C_CreateGame_BB_C1 : C_CreateGame<char16_t> {
|
||||
// C2 (C->S): Set choice search parameters
|
||||
// Server does not respond.
|
||||
|
||||
struct C_SetChoiceSearchParameters_C2 {
|
||||
le_uint16_t disabled; // 0 = enabled, 1 = disabled
|
||||
template <typename ItemIDT>
|
||||
struct C_ChoiceSearchSelections_C2_C3 {
|
||||
le_uint16_t disabled; // 0 = enabled, 1 = disabled. Unused for command C3
|
||||
le_uint16_t unused;
|
||||
struct Entry {
|
||||
le_uint16_t parent_category_id;
|
||||
le_uint16_t category_id;
|
||||
ItemIDT parent_category_id;
|
||||
ItemIDT category_id;
|
||||
};
|
||||
Entry entries[0];
|
||||
};
|
||||
|
||||
struct C_ChoiceSearchSelections_DC_C2_C3 : C_ChoiceSearchSelections_C2_C3<le_uint32_t> { };
|
||||
struct C_ChoiceSearchSelections_PC_V3_BB_C2_C3 : C_ChoiceSearchSelections_C2_C3<le_uint16_t> { };
|
||||
|
||||
// C3 (C->S): Execute choice search
|
||||
// Same format as C2. The disabled field is unused.
|
||||
// Server should respond with a C4 command.
|
||||
|
||||
struct C_ExecuteChoiceSearch_C3 {
|
||||
le_uint32_t unknown;
|
||||
struct Entry {
|
||||
le_uint16_t parent_category_id;
|
||||
le_uint16_t category_id;
|
||||
};
|
||||
Entry entries[0];
|
||||
};
|
||||
|
||||
// C4 (S->C): Choice search results
|
||||
|
||||
// Command is a list of these; header.flag is the entry count
|
||||
@@ -1845,7 +1917,7 @@ struct C_SetBlockedSenders_BB_C6 : C_SetBlockedSenders_C6<28> { };
|
||||
// No arguments
|
||||
// Server does not respond
|
||||
|
||||
// C9 (C->S): Unknown (XBOX)
|
||||
// C9 (C->S): Unknown (XB)
|
||||
// No arguments except header.flag
|
||||
|
||||
// C9: Broadcast command (Episode 3)
|
||||
@@ -2300,9 +2372,9 @@ struct C_CreateSpectatorTeam_GC_Ep3_E7 {
|
||||
struct S_Unknown_GC_Ep3_E8 {
|
||||
parray<le_uint32_t, 0x20> unknown_a1;
|
||||
struct PlayerEntry {
|
||||
PlayerLobbyDataGC lobby_data;
|
||||
PlayerLobbyDataDCGC lobby_data;
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCV3 disp;
|
||||
PlayerDispDataDCPCV3 disp;
|
||||
};
|
||||
PlayerEntry players[4];
|
||||
parray<uint8_t, 8> unknown_a2;
|
||||
@@ -2484,9 +2556,9 @@ struct C_Unknown_BB_1EEA {
|
||||
struct S_Unknown_GC_Ep3_EB {
|
||||
parray<uint8_t, 12> unknown_a1;
|
||||
struct PlayerEntry {
|
||||
PlayerLobbyDataGC lobby_data;
|
||||
PlayerLobbyDataDCGC lobby_data;
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCV3 disp;
|
||||
PlayerDispDataDCPCV3 disp;
|
||||
};
|
||||
PlayerEntry players[12];
|
||||
};
|
||||
@@ -2673,8 +2745,8 @@ struct G_SwitchStateChanged_6x05 {
|
||||
|
||||
// 06: Send guild card
|
||||
|
||||
template <typename CharT>
|
||||
struct G_SendGuildCard_PC_V3 {
|
||||
template <typename CharT, size_t UnusedLength>
|
||||
struct G_SendGuildCard_DC_PC_V3 {
|
||||
uint8_t subcommand;
|
||||
uint8_t size;
|
||||
le_uint16_t unused;
|
||||
@@ -2682,14 +2754,18 @@ struct G_SendGuildCard_PC_V3 {
|
||||
le_uint32_t guild_card_number;
|
||||
ptext<CharT, 0x18> name;
|
||||
ptext<CharT, 0x48> description;
|
||||
parray<uint8_t, 0x24> unused2;
|
||||
parray<uint8_t, UnusedLength> unused2;
|
||||
uint8_t present;
|
||||
uint8_t present2;
|
||||
uint8_t section_id;
|
||||
uint8_t char_class;
|
||||
};
|
||||
struct G_SendGuildCard_PC_6x06 : G_SendGuildCard_PC_V3<char16_t> { };
|
||||
struct G_SendGuildCard_V3_6x06 : G_SendGuildCard_PC_V3<char> { };
|
||||
|
||||
struct G_SendGuildCard_DC_6x06 : G_SendGuildCard_DC_PC_V3<char16_t, 0x11> {
|
||||
parray<uint8_t, 3> unused3;
|
||||
};
|
||||
struct G_SendGuildCard_PC_6x06 : G_SendGuildCard_DC_PC_V3<char16_t, 0x24> { };
|
||||
struct G_SendGuildCard_V3_6x06 : G_SendGuildCard_DC_PC_V3<char, 0x24> { };
|
||||
|
||||
struct G_SendGuildCard_BB_6x06 {
|
||||
uint8_t subcommand;
|
||||
@@ -2845,12 +2921,15 @@ struct G_PlayerDropItem_6x2A {
|
||||
|
||||
// 2B: Create item in inventory (e.g. via tekker or bank withdraw)
|
||||
|
||||
struct G_PlayerCreateInventoryItem_6x2B {
|
||||
struct G_PlayerCreateInventoryItem_DC_6x2B {
|
||||
uint8_t command;
|
||||
uint8_t size;
|
||||
uint8_t client_id;
|
||||
uint8_t unused;
|
||||
ItemData item;
|
||||
};
|
||||
|
||||
struct G_PlayerCreateInventoryItem_PC_V3_BB_6x2B : G_PlayerCreateInventoryItem_DC_6x2B {
|
||||
le_uint32_t unknown;
|
||||
};
|
||||
|
||||
@@ -2988,7 +3067,7 @@ struct G_PickUpItemRequest_6x5A {
|
||||
|
||||
// 5D: Drop meseta or stacked item
|
||||
|
||||
struct G_DropStackedItem_6x5D {
|
||||
struct G_DropStackedItem_DC_6x5D {
|
||||
uint8_t subcommand;
|
||||
uint8_t size;
|
||||
uint8_t client_id;
|
||||
@@ -2998,6 +3077,9 @@ struct G_DropStackedItem_6x5D {
|
||||
le_float x;
|
||||
le_float z;
|
||||
ItemData data;
|
||||
};
|
||||
|
||||
struct G_DropStackedItem_PC_V3_BB_6x5D : G_DropStackedItem_DC_6x5D {
|
||||
le_uint32_t unused3;
|
||||
};
|
||||
|
||||
@@ -3013,7 +3095,7 @@ struct G_BuyShopItem_6x5E {
|
||||
|
||||
// 5F: Drop item from box/enemy
|
||||
|
||||
struct G_DropItem_6x5F {
|
||||
struct G_DropItem_DC_6x5F {
|
||||
uint8_t subcommand;
|
||||
uint8_t size;
|
||||
le_uint16_t unused;
|
||||
@@ -3024,12 +3106,15 @@ struct G_DropItem_6x5F {
|
||||
le_float z;
|
||||
le_uint32_t unused2;
|
||||
ItemData data;
|
||||
};
|
||||
|
||||
struct G_DropItem_PC_V3_BB_6x5F : G_DropItem_DC_6x5F {
|
||||
le_uint32_t unused3;
|
||||
};
|
||||
|
||||
// 60: Request for item drop (handled by the server on BB)
|
||||
|
||||
struct G_EnemyDropItemRequest_6x60 {
|
||||
struct G_EnemyDropItemRequest_DC_6x60 {
|
||||
uint8_t command;
|
||||
uint8_t size;
|
||||
le_uint16_t unused;
|
||||
@@ -3038,7 +3123,11 @@ struct G_EnemyDropItemRequest_6x60 {
|
||||
le_uint16_t request_id;
|
||||
le_float x;
|
||||
le_float z;
|
||||
le_uint64_t unknown;
|
||||
le_uint32_t unknown_a1;
|
||||
};
|
||||
|
||||
struct G_EnemyDropItemRequest_PC_V3_BB_6x60 : G_EnemyDropItemRequest_DC_6x60 {
|
||||
le_uint32_t unknown_a2;
|
||||
};
|
||||
|
||||
// 61: Feed MAG
|
||||
|
||||
@@ -88,7 +88,7 @@ string CompiledFunctionCode::generate_client_command(
|
||||
return this->generate_client_command_t<S_ExecuteCode_Footer_GC_B2, be_uint16_t>(
|
||||
label_writes, suffix);
|
||||
} else if (this->arch == Architecture::X86) {
|
||||
return this->generate_client_command_t<S_ExecuteCode_Footer_PC_XB_BB_B2, le_uint16_t>(
|
||||
return this->generate_client_command_t<S_ExecuteCode_Footer_DC_PC_XB_BB_B2, le_uint16_t>(
|
||||
label_writes, suffix);
|
||||
} else {
|
||||
throw logic_error("invalid architecture");
|
||||
|
||||
+3
-1
@@ -172,7 +172,9 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||
}
|
||||
}
|
||||
|
||||
bool should_delete_item = true;
|
||||
// TODO: It appears that on DC, using an item should NOT delete it from the
|
||||
// player's inventory - the client will send a followup 6x29 to do that.
|
||||
bool should_delete_item = (c->version() != GameVersion::DC);
|
||||
|
||||
auto& item = c->game_data.player()->inventory.items[item_index];
|
||||
if (item.data.data1w[0] == 0x0203) { // technique disk
|
||||
|
||||
@@ -22,7 +22,6 @@ Lobby::Lobby(uint32_t id)
|
||||
section_id(0),
|
||||
episode(1),
|
||||
difficulty(0),
|
||||
mode(0),
|
||||
random_seed(random_object<uint32_t>()),
|
||||
random(new mt19937(this->random_seed)),
|
||||
event(0),
|
||||
|
||||
+4
-1
@@ -28,6 +28,10 @@ struct Lobby {
|
||||
QUEST_IN_PROGRESS = 0x00000200,
|
||||
JOINABLE_QUEST_IN_PROGRESS = 0x00000400,
|
||||
ITEM_TRACKING_ENABLED = 0x00000800,
|
||||
DC_V2_ONLY = 0x00001000,
|
||||
BATTLE_MODE = 0x00002000,
|
||||
CHALLENGE_MODE = 0x00004000,
|
||||
SOLO_MODE = 0x00008000,
|
||||
|
||||
// Flags used only for lobbies
|
||||
PUBLIC = 0x00010000,
|
||||
@@ -62,7 +66,6 @@ struct Lobby {
|
||||
uint8_t section_id;
|
||||
uint8_t episode; // 1 = Ep1, 2 = Ep2, 3 = Ep4, 0xFF = Ep3
|
||||
uint8_t difficulty;
|
||||
uint8_t mode;
|
||||
std::u16string password;
|
||||
std::u16string name;
|
||||
// This seed is also sent to the client for rare enemy generation
|
||||
|
||||
@@ -233,9 +233,13 @@ void PSOV2OrV3DetectorEncryption::encrypt(void* data, size_t size, bool advance)
|
||||
bool v2_match = this->v2_matches.count(decrypted_v2);
|
||||
bool v3_match = this->v3_matches.count(decrypted_v3);
|
||||
if (!v2_match && !v3_match) {
|
||||
throw runtime_error("unable to determine crypt version");
|
||||
throw runtime_error(string_printf(
|
||||
"unable to determine crypt version (v2=%08" PRIX32 ", v3=%08" PRIX32 ")",
|
||||
decrypted_v2.load(), decrypted_v3.load()));
|
||||
} else if (v2_match && v3_match) {
|
||||
throw runtime_error("ambiguous crypt version");
|
||||
throw runtime_error(string_printf(
|
||||
"ambiguous crypt version (v2=%08" PRIX32 ", v3=%08" PRIX32 ")",
|
||||
decrypted_v2.load(), decrypted_v3.load()));
|
||||
} else if (v2_match) {
|
||||
this->active_crypt = move(v2_crypt);
|
||||
} else {
|
||||
|
||||
+14
-14
@@ -35,7 +35,7 @@ static FileContentsCache player_files_cache(300 * 1000 * 1000);
|
||||
PlayerStats::PlayerStats() noexcept
|
||||
: atp(0), mst(0), evp(0), hp(0), dfp(0), ata(0), lck(0) { }
|
||||
|
||||
PlayerDispDataPCV3::PlayerDispDataPCV3() noexcept
|
||||
PlayerDispDataDCPCV3::PlayerDispDataDCPCV3() noexcept
|
||||
: level(0),
|
||||
experience(0),
|
||||
meseta(0),
|
||||
@@ -59,8 +59,8 @@ PlayerDispDataPCV3::PlayerDispDataPCV3() noexcept
|
||||
proportion_x(0),
|
||||
proportion_y(0) { }
|
||||
|
||||
void PlayerDispDataPCV3::enforce_pc_limits() {
|
||||
// PC has fewer classes, so we'll substitute some here
|
||||
void PlayerDispDataDCPCV3::enforce_v2_limits() {
|
||||
// V1/V2 have fewer classes, so we'll substitute some here
|
||||
if (this->char_class == 11) {
|
||||
this->char_class = 0; // FOmar -> HUmar
|
||||
} else if (this->char_class == 10) {
|
||||
@@ -78,7 +78,7 @@ void PlayerDispDataPCV3::enforce_pc_limits() {
|
||||
this->version = 2;
|
||||
}
|
||||
|
||||
PlayerDispDataBB PlayerDispDataPCV3::to_bb() const {
|
||||
PlayerDispDataBB PlayerDispDataDCPCV3::to_bb() const {
|
||||
PlayerDispDataBB bb;
|
||||
bb.stats.atp = this->stats.atp;
|
||||
bb.stats.mst = this->stats.mst;
|
||||
@@ -144,8 +144,8 @@ PlayerDispDataBB::PlayerDispDataBB() noexcept
|
||||
proportion_x(0),
|
||||
proportion_y(0) { }
|
||||
|
||||
PlayerDispDataPCV3 PlayerDispDataBB::to_pcv3() const {
|
||||
PlayerDispDataPCV3 ret;
|
||||
PlayerDispDataDCPCV3 PlayerDispDataBB::to_dcpcv3() const {
|
||||
PlayerDispDataDCPCV3 ret;
|
||||
ret.stats.atp = this->stats.atp;
|
||||
ret.stats.mst = this->stats.mst;
|
||||
ret.stats.evp = this->stats.evp;
|
||||
@@ -475,14 +475,14 @@ void ClientGameData::save_player_data() const {
|
||||
}
|
||||
}
|
||||
|
||||
void ClientGameData::import_player(const PSOPlayerDataPC& pc) {
|
||||
void ClientGameData::import_player(const PSOPlayerDataDCPC& pd) {
|
||||
auto player = this->player();
|
||||
player->inventory = pc.inventory;
|
||||
player->disp = pc.disp.to_bb();
|
||||
player->inventory = pd.inventory;
|
||||
player->disp = pd.disp.to_bb();
|
||||
// TODO: Add these fields to the command structure so we can parse them
|
||||
// info_board = pc.info_board;
|
||||
// blocked_senders = pc.blocked_senders;
|
||||
// auto_reply = pc.auto_reply;
|
||||
// info_board = pd.info_board;
|
||||
// blocked_senders = pd.blocked_senders;
|
||||
// auto_reply = pd.auto_reply;
|
||||
}
|
||||
|
||||
void ClientGameData::import_player(const PSOPlayerDataV3& gc) {
|
||||
@@ -567,7 +567,7 @@ XBNetworkLocation::XBNetworkLocation() noexcept
|
||||
PlayerLobbyDataPC::PlayerLobbyDataPC() noexcept
|
||||
: player_tag(0), guild_card(0), ip_address(0x7F000001), client_id(0) { }
|
||||
|
||||
PlayerLobbyDataGC::PlayerLobbyDataGC() noexcept
|
||||
PlayerLobbyDataDCGC::PlayerLobbyDataDCGC() noexcept
|
||||
: player_tag(0), guild_card(0), ip_address(0x7F000001), client_id(0) { }
|
||||
|
||||
PlayerLobbyDataXB::PlayerLobbyDataXB() noexcept
|
||||
@@ -584,7 +584,7 @@ void PlayerLobbyDataPC::clear() {
|
||||
ptext<char16_t, 0x10> name;
|
||||
}
|
||||
|
||||
void PlayerLobbyDataGC::clear() {
|
||||
void PlayerLobbyDataDCGC::clear() {
|
||||
this->player_tag = 0;
|
||||
this->guild_card = 0;
|
||||
this->ip_address = 0;
|
||||
|
||||
+18
-19
@@ -96,8 +96,7 @@ struct PendingItemTrade {
|
||||
|
||||
struct PlayerDispDataBB;
|
||||
|
||||
// PC/V3 player appearance and stats data
|
||||
struct PlayerDispDataPCV3 { // 0xD0 bytes
|
||||
struct PlayerDispDataDCPCV3 { // 0xD0 bytes
|
||||
PlayerStats stats;
|
||||
parray<uint8_t, 0x0A> unknown_a1;
|
||||
le_uint32_t level;
|
||||
@@ -131,9 +130,9 @@ struct PlayerDispDataPCV3 { // 0xD0 bytes
|
||||
// that has a fixed-size array. If we didn't define this constructor, the
|
||||
// trivial fields in that array's members would be uninitialized, and we could
|
||||
// send uninitialized memory to the client.
|
||||
PlayerDispDataPCV3() noexcept;
|
||||
PlayerDispDataDCPCV3() noexcept;
|
||||
|
||||
void enforce_pc_limits();
|
||||
void enforce_v2_limits();
|
||||
PlayerDispDataBB to_bb() const;
|
||||
} __attribute__((packed));
|
||||
|
||||
@@ -202,8 +201,8 @@ struct PlayerDispDataBB {
|
||||
|
||||
PlayerDispDataBB() noexcept;
|
||||
|
||||
inline void enforce_pc_limits() { }
|
||||
PlayerDispDataPCV3 to_pcv3() const;
|
||||
inline void enforce_v2_limits() { }
|
||||
PlayerDispDataDCPCV3 to_dcpcv3() const;
|
||||
PlayerDispDataBBPreview to_preview() const;
|
||||
void apply_preview(const PlayerDispDataBBPreview&);
|
||||
} __attribute__((packed));
|
||||
@@ -286,14 +285,14 @@ struct PlayerLobbyDataPC {
|
||||
void clear();
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PlayerLobbyDataGC {
|
||||
struct PlayerLobbyDataDCGC {
|
||||
le_uint32_t player_tag;
|
||||
le_uint32_t guild_card;
|
||||
be_uint32_t ip_address;
|
||||
le_uint32_t client_id;
|
||||
ptext<char, 0x10> name;
|
||||
|
||||
PlayerLobbyDataGC() noexcept;
|
||||
PlayerLobbyDataDCGC() noexcept;
|
||||
void clear();
|
||||
} __attribute__((packed));
|
||||
|
||||
@@ -371,14 +370,14 @@ struct PlayerChallengeDataBB {
|
||||
|
||||
|
||||
|
||||
struct PSOPlayerDataPC { // For command 61
|
||||
struct PSOPlayerDataDCPC { // For command 61
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCV3 disp;
|
||||
PlayerDispDataDCPCV3 disp;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct PSOPlayerDataV3 { // For command 61
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCV3 disp;
|
||||
PlayerDispDataDCPCV3 disp;
|
||||
PlayerChallengeDataV3 challenge_data;
|
||||
parray<uint8_t, 0x18> unknown;
|
||||
ptext<char, 0xAC> info_board;
|
||||
@@ -392,7 +391,7 @@ struct PSOPlayerDataV3 { // For command 61
|
||||
|
||||
struct PSOPlayerDataGCEp3 { // For command 61
|
||||
PlayerInventory inventory;
|
||||
PlayerDispDataPCV3 disp;
|
||||
PlayerDispDataDCPCV3 disp;
|
||||
PlayerChallengeDataV3 challenge_data;
|
||||
parray<uint8_t, 0x18> unknown;
|
||||
ptext<char, 0xAC> info_board;
|
||||
@@ -521,7 +520,7 @@ public:
|
||||
void load_player_data();
|
||||
void save_player_data() const;
|
||||
|
||||
void import_player(const PSOPlayerDataPC& pd);
|
||||
void import_player(const PSOPlayerDataDCPC& pd);
|
||||
void import_player(const PSOPlayerDataV3& pd);
|
||||
void import_player(const PSOPlayerDataBB& pd);
|
||||
// Note: this function is not const because it can cause player and account
|
||||
@@ -542,20 +541,20 @@ DestT convert_player_disp_data(const SrcT&) {
|
||||
}
|
||||
|
||||
template <>
|
||||
inline PlayerDispDataPCV3 convert_player_disp_data<PlayerDispDataPCV3>(
|
||||
const PlayerDispDataPCV3& src) {
|
||||
inline PlayerDispDataDCPCV3 convert_player_disp_data<PlayerDispDataDCPCV3>(
|
||||
const PlayerDispDataDCPCV3& src) {
|
||||
return src;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline PlayerDispDataPCV3 convert_player_disp_data<PlayerDispDataPCV3, PlayerDispDataBB>(
|
||||
inline PlayerDispDataDCPCV3 convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(
|
||||
const PlayerDispDataBB& src) {
|
||||
return src.to_pcv3();
|
||||
return src.to_dcpcv3();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB, PlayerDispDataPCV3>(
|
||||
const PlayerDispDataPCV3& src) {
|
||||
inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB, PlayerDispDataDCPCV3>(
|
||||
const PlayerDispDataDCPCV3& src) {
|
||||
return src.to_bb();
|
||||
}
|
||||
|
||||
|
||||
+86
-52
@@ -159,8 +159,12 @@ static HandlerResult process_server_gc_9A(shared_ptr<ServerState>,
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
}
|
||||
|
||||
static HandlerResult process_server_pc_v3_patch_02_17(shared_ptr<ServerState> s,
|
||||
ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) {
|
||||
static HandlerResult process_server_dc_pc_v3_patch_02_17(
|
||||
shared_ptr<ServerState> s,
|
||||
ProxyServer::LinkedSession& session,
|
||||
uint16_t command,
|
||||
uint32_t flag,
|
||||
string& data) {
|
||||
if (session.version == GameVersion::PATCH && command == 0x17) {
|
||||
throw invalid_argument("patch server sent 17 server init");
|
||||
}
|
||||
@@ -183,7 +187,7 @@ static HandlerResult process_server_pc_v3_patch_02_17(shared_ptr<ServerState> s,
|
||||
session.server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key));
|
||||
session.client_channel.crypt_in.reset(new PSOV3Encryption(cmd.client_key));
|
||||
session.client_channel.crypt_out.reset(new PSOV3Encryption(cmd.server_key));
|
||||
} else { // PC or patch server (they both use PC encryption)
|
||||
} else { // DC, PC, or patch server (they all use V2 encryption)
|
||||
session.server_channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key));
|
||||
session.server_channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key));
|
||||
session.client_channel.crypt_in.reset(new PSOV2Encryption(cmd.client_key));
|
||||
@@ -196,16 +200,20 @@ static HandlerResult process_server_pc_v3_patch_02_17(shared_ptr<ServerState> s,
|
||||
session.log.info("Existing license in linked session");
|
||||
|
||||
// This isn't forwarded to the client, so don't recreate the client's crypts
|
||||
if ((session.version == GameVersion::PATCH) ||
|
||||
(session.version == GameVersion::PC)) {
|
||||
session.server_channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key));
|
||||
session.server_channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key));
|
||||
} else if ((session.version == GameVersion::GC) ||
|
||||
(session.version == GameVersion::XB)) {
|
||||
session.server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key));
|
||||
session.server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key));
|
||||
} else {
|
||||
throw invalid_argument("unsupported version");
|
||||
switch (session.version) {
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC:
|
||||
case GameVersion::PATCH:
|
||||
session.server_channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key));
|
||||
session.server_channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key));
|
||||
break;
|
||||
case GameVersion::GC:
|
||||
case GameVersion::XB:
|
||||
session.server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key));
|
||||
session.server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key));
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unsupported version");
|
||||
}
|
||||
|
||||
// Respond with an appropriate login command. We don't let the client do this
|
||||
@@ -216,27 +224,51 @@ static HandlerResult process_server_pc_v3_patch_02_17(shared_ptr<ServerState> s,
|
||||
session.server_channel.send(0x02);
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
|
||||
} else if (session.version == GameVersion::PC) {
|
||||
C_Login_PC_GC_9D cmd;
|
||||
if (session.remote_guild_card_number == 0) {
|
||||
cmd.player_tag = 0xFFFF0000;
|
||||
cmd.guild_card_number = 0xFFFFFFFF;
|
||||
} else if ((session.version == GameVersion::DC) ||
|
||||
(session.version == GameVersion::PC)) {
|
||||
if (session.newserv_client_config.cfg.flags & Client::Flag::DCV1) {
|
||||
C_LoginV1_DC_93 cmd;
|
||||
if (session.remote_guild_card_number == 0) {
|
||||
cmd.player_tag = 0xFFFF0000;
|
||||
cmd.guild_card_number = 0xFFFFFFFF;
|
||||
} else {
|
||||
cmd.player_tag = 0x00010000;
|
||||
cmd.guild_card_number = session.remote_guild_card_number;
|
||||
}
|
||||
cmd.unknown_a1 = 0;
|
||||
cmd.unknown_a2 = 0;
|
||||
cmd.sub_version = session.sub_version;
|
||||
cmd.unknown_a3 = 0;
|
||||
cmd.language = session.language;
|
||||
cmd.serial_number = string_printf("%08" PRIX32 "",
|
||||
session.license->serial_number);
|
||||
cmd.access_key = session.license->access_key;
|
||||
cmd.hardware_id = session.hardware_id;
|
||||
cmd.name = session.character_name;
|
||||
session.server_channel.send(0x93, 0x00, &cmd, sizeof(cmd));
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
} else {
|
||||
cmd.player_tag = 0x00010000;
|
||||
cmd.guild_card_number = session.remote_guild_card_number;
|
||||
C_Login_DC_PC_GC_9D cmd;
|
||||
if (session.remote_guild_card_number == 0) {
|
||||
cmd.player_tag = 0xFFFF0000;
|
||||
cmd.guild_card_number = 0xFFFFFFFF;
|
||||
} else {
|
||||
cmd.player_tag = 0x00010000;
|
||||
cmd.guild_card_number = session.remote_guild_card_number;
|
||||
}
|
||||
cmd.unused = 0xFFFFFFFFFFFF0000;
|
||||
cmd.sub_version = session.sub_version;
|
||||
cmd.is_extended = 0;
|
||||
cmd.language = session.language;
|
||||
cmd.serial_number = string_printf("%08" PRIX32 "",
|
||||
session.license->serial_number);
|
||||
cmd.access_key = session.license->access_key;
|
||||
cmd.serial_number2 = cmd.serial_number;
|
||||
cmd.access_key2 = cmd.access_key;
|
||||
cmd.name = session.character_name;
|
||||
session.server_channel.send(0x9D, 0x00, &cmd, sizeof(cmd));
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
}
|
||||
cmd.unused = 0xFFFFFFFFFFFF0000;
|
||||
cmd.sub_version = session.sub_version;
|
||||
cmd.is_extended = 0;
|
||||
cmd.language = session.language;
|
||||
cmd.serial_number = string_printf("%08" PRIX32 "",
|
||||
session.license->serial_number);
|
||||
cmd.access_key = session.license->access_key;
|
||||
cmd.serial_number2 = cmd.serial_number;
|
||||
cmd.access_key2 = cmd.access_key;
|
||||
cmd.name = session.character_name;
|
||||
session.server_channel.send(0x9D, 0x00, &cmd, sizeof(cmd));
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
|
||||
} else if (session.version == GameVersion::GC) {
|
||||
if (command == 0x17) {
|
||||
@@ -708,7 +740,9 @@ static HandlerResult process_server_60_62_6C_6D_C9_CB(shared_ptr<ServerState>,
|
||||
session.next_drop_item.data.data1d[0] &&
|
||||
(session.version != GameVersion::BB)) {
|
||||
if (data[0] == 0x60) {
|
||||
const auto& cmd = check_size_t<G_EnemyDropItemRequest_6x60>(data);
|
||||
const auto& cmd = check_size_t<G_EnemyDropItemRequest_DC_6x60>(data,
|
||||
sizeof(G_EnemyDropItemRequest_DC_6x60),
|
||||
sizeof(G_EnemyDropItemRequest_PC_V3_BB_6x60));
|
||||
session.next_drop_item.data.id = session.next_item_id++;
|
||||
send_drop_item(session.server_channel, session.next_drop_item.data,
|
||||
true, cmd.area, cmd.x, cmd.z, cmd.request_id);
|
||||
@@ -1020,7 +1054,7 @@ static HandlerResult process_client_40(shared_ptr<ServerState>,
|
||||
template <typename CmdT>
|
||||
static HandlerResult process_client_81(shared_ptr<ServerState>,
|
||||
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
|
||||
auto& cmd = check_size_t<SC_SimpleMail_V3_81>(data);
|
||||
auto& cmd = check_size_t<CmdT>(data);
|
||||
if (session.license) {
|
||||
if (cmd.from_guild_card_number == session.license->serial_number) {
|
||||
cmd.from_guild_card_number = session.remote_guild_card_number;
|
||||
@@ -1179,17 +1213,17 @@ typedef HandlerResult (*process_command_t)(
|
||||
auto defh = process_default;
|
||||
|
||||
static process_command_t dc_server_handlers[0x100] = {
|
||||
/* 00 */ defh, defh, defh, defh, process_server_dc_pc_v3_04, defh, process_server_dc_pc_v3_06, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, defh, defh, process_server_game_19_patch_14, defh, defh, defh, defh, defh, defh,
|
||||
/* 00 */ defh, defh, process_server_dc_pc_v3_patch_02_17, defh, process_server_dc_pc_v3_04, defh, process_server_dc_pc_v3_06, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, process_server_dc_pc_v3_patch_02_17, defh, process_server_game_19_patch_14, defh, defh, defh, defh, defh, defh,
|
||||
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 40 */ defh, process_server_41<S_GuildCardSearchResult_DC_V3_41>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 40 */ defh, process_server_41<S_GuildCardSearchResult_DC_V3_41>, defh, defh, process_server_44_A6<S_OpenFile_DC_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 50 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 60 */ process_server_60_62_6C_6D_C9_CB, defh, process_server_60_62_6C_6D_C9_CB, defh, defh, defh, process_server_66_69, defh, defh, process_server_66_69, defh, defh, process_server_60_62_6C_6D_C9_CB, process_server_60_62_6C_6D_C9_CB, defh, defh,
|
||||
/* 60 */ process_server_60_62_6C_6D_C9_CB, defh, process_server_60_62_6C_6D_C9_CB, defh, process_server_64<S_JoinGame_DC_GC_64>, process_server_65_67_68<S_JoinLobby_DC_GC_65_67_68>, process_server_66_69, process_server_65_67_68<S_JoinLobby_DC_GC_65_67_68>, process_server_65_67_68<S_JoinLobby_DC_GC_65_67_68>, process_server_66_69, defh, defh, process_server_60_62_6C_6D_C9_CB, process_server_60_62_6C_6D_C9_CB, defh, defh,
|
||||
/* 70 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 80 */ defh, defh, defh, defh, defh, defh, defh, defh, process_server_88, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 90 */ defh, defh, defh, defh, defh, defh, defh, process_server_97, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* A0 */ defh, defh, defh, defh, defh, defh, defh, process_server_13_A7, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* A0 */ defh, defh, defh, defh, defh, defh, process_server_44_A6<S_OpenFile_DC_44_A6>, process_server_13_A7, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* B0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* C0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* D0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
@@ -1197,8 +1231,8 @@ static process_command_t dc_server_handlers[0x100] = {
|
||||
/* F0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
};
|
||||
static process_command_t pc_server_handlers[0x100] = {
|
||||
/* 00 */ defh, defh, process_server_pc_v3_patch_02_17, defh, process_server_dc_pc_v3_04, defh, process_server_dc_pc_v3_06, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, process_server_pc_v3_patch_02_17, defh, process_server_game_19_patch_14, defh, defh, defh, defh, defh, defh,
|
||||
/* 00 */ defh, defh, process_server_dc_pc_v3_patch_02_17, defh, process_server_dc_pc_v3_04, defh, process_server_dc_pc_v3_06, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, process_server_dc_pc_v3_patch_02_17, defh, process_server_game_19_patch_14, defh, defh, defh, defh, defh, defh,
|
||||
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 40 */ defh, process_server_41<S_GuildCardSearchResult_PC_41>, defh, defh, process_server_44_A6<S_OpenFile_PC_V3_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
@@ -1215,15 +1249,15 @@ static process_command_t pc_server_handlers[0x100] = {
|
||||
/* F0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
};
|
||||
static process_command_t gc_server_handlers[0x100] = {
|
||||
/* 00 */ defh, defh, process_server_pc_v3_patch_02_17, defh, process_server_dc_pc_v3_04, defh, process_server_dc_pc_v3_06, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, process_server_pc_v3_patch_02_17, defh, process_server_game_19_patch_14, process_server_v3_1A_D5, defh, defh, defh, defh, defh,
|
||||
/* 00 */ defh, defh, process_server_dc_pc_v3_patch_02_17, defh, process_server_dc_pc_v3_04, defh, process_server_dc_pc_v3_06, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, process_server_dc_pc_v3_patch_02_17, defh, process_server_game_19_patch_14, process_server_v3_1A_D5, defh, defh, defh, defh, defh,
|
||||
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 40 */ defh, process_server_41<S_GuildCardSearchResult_DC_V3_41>, defh, defh, process_server_44_A6<S_OpenFile_PC_V3_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 50 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 60 */ process_server_60_62_6C_6D_C9_CB, defh, process_server_60_62_6C_6D_C9_CB, defh, process_server_64<S_JoinGame_GC_64>, process_server_65_67_68<S_JoinLobby_GC_65_67_68>, process_server_66_69, process_server_65_67_68<S_JoinLobby_GC_65_67_68>, process_server_65_67_68<S_JoinLobby_GC_65_67_68>, process_server_66_69, defh, defh, process_server_60_62_6C_6D_C9_CB, process_server_60_62_6C_6D_C9_CB, defh, defh,
|
||||
/* 60 */ process_server_60_62_6C_6D_C9_CB, defh, process_server_60_62_6C_6D_C9_CB, defh, process_server_64<S_JoinGame_DC_GC_64>, process_server_65_67_68<S_JoinLobby_DC_GC_65_67_68>, process_server_66_69, process_server_65_67_68<S_JoinLobby_DC_GC_65_67_68>, process_server_65_67_68<S_JoinLobby_DC_GC_65_67_68>, process_server_66_69, defh, defh, process_server_60_62_6C_6D_C9_CB, process_server_60_62_6C_6D_C9_CB, defh, defh,
|
||||
/* 70 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 80 */ defh, process_server_81<SC_SimpleMail_V3_81>, defh, defh, defh, defh, defh, defh, process_server_88, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 80 */ defh, process_server_81<SC_SimpleMail_DC_V3_81>, defh, defh, defh, defh, defh, defh, process_server_88, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 90 */ defh, defh, defh, defh, defh, defh, defh, process_server_97, defh, defh, process_server_gc_9A, defh, defh, defh, defh, defh,
|
||||
/* A0 */ defh, defh, defh, defh, defh, defh, process_server_44_A6<S_OpenFile_PC_V3_44_A6>, process_server_13_A7, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* B0 */ defh, defh, process_server_B2, defh, defh, defh, defh, defh, process_server_gc_B8, defh, defh, defh, defh, defh, defh, defh,
|
||||
@@ -1233,15 +1267,15 @@ static process_command_t gc_server_handlers[0x100] = {
|
||||
/* F0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
};
|
||||
static process_command_t xb_server_handlers[0x100] = {
|
||||
/* 00 */ defh, defh, process_server_pc_v3_patch_02_17, defh, process_server_dc_pc_v3_04, defh, process_server_dc_pc_v3_06, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, process_server_pc_v3_patch_02_17, defh, process_server_game_19_patch_14, process_server_v3_1A_D5, defh, defh, defh, defh, defh,
|
||||
/* 00 */ defh, defh, process_server_dc_pc_v3_patch_02_17, defh, process_server_dc_pc_v3_04, defh, process_server_dc_pc_v3_06, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, process_server_dc_pc_v3_patch_02_17, defh, process_server_game_19_patch_14, process_server_v3_1A_D5, defh, defh, defh, defh, defh,
|
||||
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 40 */ defh, process_server_41<S_GuildCardSearchResult_DC_V3_41>, defh, defh, process_server_44_A6<S_OpenFile_PC_V3_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 50 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 60 */ process_server_60_62_6C_6D_C9_CB, defh, process_server_60_62_6C_6D_C9_CB, defh, process_server_64<S_JoinGame_GC_64>, process_server_65_67_68<S_JoinLobby_GC_65_67_68>, process_server_66_69, process_server_65_67_68<S_JoinLobby_GC_65_67_68>, process_server_65_67_68<S_JoinLobby_GC_65_67_68>, process_server_66_69, defh, defh, process_server_60_62_6C_6D_C9_CB, process_server_60_62_6C_6D_C9_CB, defh, defh,
|
||||
/* 60 */ process_server_60_62_6C_6D_C9_CB, defh, process_server_60_62_6C_6D_C9_CB, defh, process_server_64<S_JoinGame_XB_64>, process_server_65_67_68<S_JoinLobby_XB_65_67_68>, process_server_66_69, process_server_65_67_68<S_JoinLobby_XB_65_67_68>, process_server_65_67_68<S_JoinLobby_XB_65_67_68>, process_server_66_69, defh, defh, process_server_60_62_6C_6D_C9_CB, process_server_60_62_6C_6D_C9_CB, defh, defh,
|
||||
/* 70 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 80 */ defh, process_server_81<SC_SimpleMail_V3_81>, defh, defh, defh, defh, defh, defh, process_server_88, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 80 */ defh, process_server_81<SC_SimpleMail_DC_V3_81>, defh, defh, defh, defh, defh, defh, process_server_88, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 90 */ defh, defh, defh, defh, defh, defh, defh, process_server_97, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* A0 */ defh, defh, defh, defh, defh, defh, process_server_44_A6<S_OpenFile_PC_V3_44_A6>, process_server_13_A7, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* B0 */ defh, defh, process_server_B2, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
@@ -1269,7 +1303,7 @@ static process_command_t bb_server_handlers[0x100] = {
|
||||
/* F0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
};
|
||||
static process_command_t patch_server_handlers[0x100] = {
|
||||
/* 00 */ defh, defh, process_server_pc_v3_patch_02_17, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 00 */ defh, defh, process_server_dc_pc_v3_patch_02_17, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 10 */ defh, defh, defh, defh, process_server_game_19_patch_14, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
@@ -1334,7 +1368,7 @@ static process_command_t gc_client_handlers[0x100] = {
|
||||
/* 50 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 60 */ process_client_60_62_6C_6D_C9_CB<G_SendGuildCard_V3_6x06>, defh, process_client_60_62_6C_6D_C9_CB<G_SendGuildCard_V3_6x06>, defh, defh, defh, defh, defh, defh, defh, defh, defh, process_client_60_62_6C_6D_C9_CB<G_SendGuildCard_V3_6x06>, process_client_60_62_6C_6D_C9_CB<G_SendGuildCard_V3_6x06>, defh, defh,
|
||||
/* 70 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 80 */ defh, process_client_81<SC_SimpleMail_V3_81>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 80 */ defh, process_client_81<SC_SimpleMail_DC_V3_81>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 90 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* A0 */ process_client_dc_pc_v3_A0_A1, process_client_dc_pc_v3_A0_A1, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* B0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
@@ -1352,7 +1386,7 @@ static process_command_t xb_client_handlers[0x100] = {
|
||||
/* 50 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 60 */ process_client_60_62_6C_6D_C9_CB<G_SendGuildCard_V3_6x06>, defh, process_client_60_62_6C_6D_C9_CB<G_SendGuildCard_V3_6x06>, defh, defh, defh, defh, defh, defh, defh, defh, defh, process_client_60_62_6C_6D_C9_CB<G_SendGuildCard_V3_6x06>, process_client_60_62_6C_6D_C9_CB<G_SendGuildCard_V3_6x06>, defh, defh,
|
||||
/* 70 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 80 */ defh, process_client_81<SC_SimpleMail_V3_81>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 80 */ defh, process_client_81<SC_SimpleMail_DC_V3_81>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 90 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* A0 */ process_client_dc_pc_v3_A0_A1, process_client_dc_pc_v3_A0_A1, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* B0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
|
||||
+35
-6
@@ -186,6 +186,7 @@ void ProxyServer::on_client_connect(
|
||||
switch (version) {
|
||||
case GameVersion::PATCH:
|
||||
throw logic_error("cannot create unlinked patch session");
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC:
|
||||
case GameVersion::GC:
|
||||
case GameVersion::XB: {
|
||||
@@ -196,7 +197,7 @@ void ProxyServer::on_client_connect(
|
||||
session->channel.send(0x02, 0x00, &cmd, sizeof(cmd));
|
||||
// TODO: Is this actually needed?
|
||||
// bufferevent_flush(session->channel.bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
|
||||
if (version == GameVersion::PC) {
|
||||
if ((version == GameVersion::DC) || (version == GameVersion::PC)) {
|
||||
session->channel.crypt_out.reset(new PSOV2Encryption(server_key));
|
||||
session->channel.crypt_in.reset(new PSOV2Encryption(client_key));
|
||||
} else {
|
||||
@@ -261,16 +262,41 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
|
||||
string character_name;
|
||||
ClientConfigBB client_config;
|
||||
string login_command_bb;
|
||||
string hardware_id;
|
||||
|
||||
try {
|
||||
if (session->version == GameVersion::PC) {
|
||||
if (session->version == GameVersion::DC) {
|
||||
// We should only get a 93 or 9D while the session is unlinked; if we get
|
||||
// anything else, disconnect
|
||||
if (command == 0x93) {
|
||||
const auto& cmd = check_size_t<C_LoginV1_DC_93>(data);
|
||||
license = session->server->state->license_manager->verify_pc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
language = cmd.language;
|
||||
character_name = cmd.name;
|
||||
hardware_id = cmd.hardware_id;
|
||||
client_config.cfg.flags |= Client::Flag::DCV1;
|
||||
} else if (command == 0x9D) {
|
||||
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(
|
||||
data, sizeof(C_Login_DC_PC_GC_9D), sizeof(C_LoginExtended_DC_PC_GC_9D));
|
||||
license = session->server->state->license_manager->verify_pc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
language = cmd.language;
|
||||
character_name = cmd.name;
|
||||
} else {
|
||||
throw runtime_error("command is not 93 or 9D");
|
||||
}
|
||||
|
||||
} else if (session->version == GameVersion::PC) {
|
||||
// We should only get a 9D while the session is unlinked; if we get
|
||||
// anything else, disconnect
|
||||
if (command != 0x9D) {
|
||||
throw runtime_error("command is not 9D");
|
||||
}
|
||||
const auto& cmd = check_size_t<C_Login_PC_GC_9D>(
|
||||
data, sizeof(C_Login_PC_GC_9D), sizeof(C_LoginExtended_PC_GC_9D));
|
||||
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(
|
||||
data, sizeof(C_Login_DC_PC_GC_9D), sizeof(C_LoginExtended_DC_PC_GC_9D));
|
||||
license = session->server->state->license_manager->verify_pc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
@@ -373,7 +399,8 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
|
||||
session->detector_crypt,
|
||||
sub_version,
|
||||
language,
|
||||
character_name);
|
||||
character_name,
|
||||
hardware_id);
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
linked_session->log.error("Failed to resume linked session: %s", e.what());
|
||||
@@ -498,10 +525,12 @@ void ProxyServer::LinkedSession::resume(
|
||||
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
uint32_t sub_version,
|
||||
uint8_t language,
|
||||
const string& character_name) {
|
||||
const string& character_name,
|
||||
const string& hardware_id) {
|
||||
this->sub_version = sub_version;
|
||||
this->language = language;
|
||||
this->character_name = character_name;
|
||||
this->hardware_id = hardware_id;
|
||||
this->resume_inner(move(client_channel), detector_crypt);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -54,6 +54,7 @@ public:
|
||||
uint32_t sub_version;
|
||||
uint8_t language;
|
||||
std::string character_name;
|
||||
std::string hardware_id; // Only used for DC sessions
|
||||
std::string login_command_bb;
|
||||
|
||||
uint32_t remote_guild_card_number;
|
||||
@@ -127,7 +128,8 @@ public:
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
uint32_t sub_version,
|
||||
uint8_t language,
|
||||
const std::string& character_name);
|
||||
const std::string& character_name,
|
||||
const std::string& hardware_id);
|
||||
void resume(
|
||||
Channel&& client_channel,
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
|
||||
+2
-2
@@ -656,8 +656,8 @@ shared_ptr<const string> QuestIndex::get_gba(const string& name) const {
|
||||
return this->gba_file_contents.at(name);
|
||||
}
|
||||
|
||||
vector<shared_ptr<const Quest>> QuestIndex::filter(GameVersion version,
|
||||
bool is_dcv1, QuestCategory category) const {
|
||||
vector<shared_ptr<const Quest>> QuestIndex::filter(
|
||||
GameVersion version, bool is_dcv1, QuestCategory category) const {
|
||||
auto it = this->version_menu_item_id_to_quest.lower_bound(make_pair(version, 0));
|
||||
auto end_it = this->version_menu_item_id_to_quest.upper_bound(make_pair(version, 0xFFFFFFFF));
|
||||
|
||||
|
||||
+258
-143
@@ -126,7 +126,7 @@ void process_login_complete(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
|
||||
} else if (c->server_behavior == ServerBehavior::LOBBY_SERVER) {
|
||||
|
||||
if (c->version == GameVersion::BB) {
|
||||
if (c->version() == GameVersion::BB) {
|
||||
// This implicitly loads the client's account and player data
|
||||
send_complete_player_bb(c);
|
||||
}
|
||||
@@ -161,14 +161,14 @@ static void set_console_client_flags(
|
||||
shared_ptr<Client> c, uint32_t sub_version) {
|
||||
if (c->channel.crypt_in->type() == PSOEncryption::Type::V2) {
|
||||
if (sub_version < 0x28) {
|
||||
c->version = GameVersion::DC;
|
||||
c->channel.version = GameVersion::DC;
|
||||
c->log.info("Game version changed to DC");
|
||||
} else if (c->version == GameVersion::GC) {
|
||||
} else if (c->version() == GameVersion::GC) {
|
||||
c->flags |= Client::Flag::GC_TRIAL_EDITION;
|
||||
c->log.info("Trial edition flag set");
|
||||
}
|
||||
}
|
||||
c->flags |= flags_for_version(c->version, sub_version);
|
||||
c->flags |= flags_for_version(c->version(), sub_version);
|
||||
}
|
||||
|
||||
void process_verify_license_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
@@ -212,6 +212,79 @@ void process_verify_license_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
}
|
||||
}
|
||||
|
||||
void process_login_0_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 90
|
||||
const auto& cmd = check_size_t<C_LoginV1_DC_PC_V3_90>(data);
|
||||
c->channel.version = GameVersion::DC;
|
||||
c->flags |= flags_for_version(c->version(), -1);
|
||||
c->flags |= Client::Flag::DCV1;
|
||||
|
||||
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
|
||||
try {
|
||||
shared_ptr<const License> l = s->license_manager->verify_pc(
|
||||
serial_number, cmd.access_key);
|
||||
c->set_license(l);
|
||||
send_command(c, 0x90, 0x02);
|
||||
|
||||
} catch (const incorrect_access_key& e) {
|
||||
send_command(c, 0x90, 0x03);
|
||||
c->should_disconnect = true;
|
||||
|
||||
} catch (const missing_license& e) {
|
||||
if (!s->allow_unregistered_users) {
|
||||
send_command(c, 0x90, 0x03);
|
||||
c->should_disconnect = true;
|
||||
} else {
|
||||
auto l = LicenseManager::create_license_pc(
|
||||
serial_number, cmd.access_key, true);
|
||||
s->license_manager->add(l);
|
||||
c->set_license(l);
|
||||
send_command(c, 0x90, 0x01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void process_login_2_dc(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 92
|
||||
check_size_t<C_RegisterV1_DC_92>(data);
|
||||
send_command(c, 0x92, 0x01);
|
||||
}
|
||||
|
||||
void process_login_3_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 93
|
||||
const auto& cmd = check_size_t<C_LoginV1_DC_93>(data,
|
||||
sizeof(C_LoginV1_DC_93), sizeof(C_LoginExtendedV1_DC_93));
|
||||
set_console_client_flags(c, cmd.sub_version);
|
||||
|
||||
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
|
||||
try {
|
||||
shared_ptr<const License> l = s->license_manager->verify_pc(
|
||||
serial_number, cmd.access_key);
|
||||
c->set_license(l);
|
||||
|
||||
} catch (const incorrect_access_key& e) {
|
||||
send_message_box(c, u"Incorrect access key");
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
|
||||
} catch (const missing_license& e) {
|
||||
if (!s->allow_unregistered_users) {
|
||||
send_message_box(c, u"Incorrect serial number");
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else {
|
||||
auto l = LicenseManager::create_license_pc(
|
||||
serial_number, cmd.access_key, true);
|
||||
s->license_manager->add(l);
|
||||
c->set_license(l);
|
||||
}
|
||||
}
|
||||
|
||||
send_update_client_config(c);
|
||||
|
||||
process_login_complete(s, c);
|
||||
}
|
||||
|
||||
void process_login_a_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 9A
|
||||
const auto& cmd = check_size_t<C_Login_DC_PC_V3_9A>(data);
|
||||
@@ -220,7 +293,8 @@ void process_login_a_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
|
||||
try {
|
||||
shared_ptr<const License> l;
|
||||
switch (c->version) {
|
||||
switch (c->version()) {
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC:
|
||||
l = s->license_manager->verify_pc(serial_number, cmd.access_key);
|
||||
break;
|
||||
@@ -252,11 +326,11 @@ void process_login_a_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
// license. So, if no license exists at this point, disconnect the client
|
||||
// even if unregistered clients are allowed.
|
||||
shared_ptr<License> l;
|
||||
if ((c->version == GameVersion::GC) || (c->version == GameVersion::XB)) {
|
||||
if ((c->version() == GameVersion::GC) || (c->version() == GameVersion::XB)) {
|
||||
send_command(c, 0x9A, 0x04);
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else if (c->version == GameVersion::PC) {
|
||||
} else if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) {
|
||||
l = LicenseManager::create_license_pc(serial_number, cmd.access_key, true);
|
||||
s->license_manager->add(l);
|
||||
c->set_license(l);
|
||||
@@ -271,12 +345,13 @@ void process_login_c_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 9C
|
||||
const auto& cmd = check_size_t<C_Register_DC_PC_V3_9C>(data);
|
||||
|
||||
c->flags |= flags_for_version(c->version, cmd.sub_version);
|
||||
c->flags |= flags_for_version(c->version(), cmd.sub_version);
|
||||
|
||||
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
|
||||
try {
|
||||
shared_ptr<const License> l;
|
||||
switch (c->version) {
|
||||
switch (c->version()) {
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC:
|
||||
l = s->license_manager->verify_pc(serial_number, cmd.access_key);
|
||||
break;
|
||||
@@ -305,7 +380,8 @@ void process_login_c_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
return;
|
||||
} else {
|
||||
shared_ptr<License> l;
|
||||
switch (c->version) {
|
||||
switch (c->version()) {
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC:
|
||||
l = LicenseManager::create_license_pc(serial_number, cmd.access_key,
|
||||
true);
|
||||
@@ -327,14 +403,14 @@ void process_login_c_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
}
|
||||
}
|
||||
|
||||
void process_login_d_e_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
void process_login_d_e_dc_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t command, uint32_t, const string& data) { // 9D 9E
|
||||
const C_Login_PC_GC_9D* base_cmd;
|
||||
const C_Login_DC_PC_GC_9D* base_cmd;
|
||||
if (command == 0x9D) {
|
||||
base_cmd = &check_size_t<C_Login_PC_GC_9D>(data,
|
||||
sizeof(C_Login_PC_GC_9D), sizeof(C_LoginExtended_PC_GC_9D));
|
||||
base_cmd = &check_size_t<C_Login_DC_PC_GC_9D>(data,
|
||||
sizeof(C_Login_DC_PC_GC_9D), sizeof(C_LoginExtended_DC_PC_GC_9D));
|
||||
if (base_cmd->is_extended) {
|
||||
const auto& cmd = check_size_t<C_LoginExtended_PC_GC_9D>(data);
|
||||
const auto& cmd = check_size_t<C_LoginExtended_DC_PC_GC_9D>(data);
|
||||
if (cmd.extension.menu_id == MenuID::LOBBY) {
|
||||
c->preferred_lobby_id = cmd.extension.preferred_lobby_id;
|
||||
}
|
||||
@@ -351,7 +427,7 @@ void process_login_d_e_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
break;
|
||||
case sizeof(C_Login_XB_9E):
|
||||
case sizeof(C_LoginExtended_XB_9E):
|
||||
c->version = GameVersion::XB;
|
||||
c->channel.version = GameVersion::XB;
|
||||
c->log.info("Game version set to XB");
|
||||
break;
|
||||
default:
|
||||
@@ -384,7 +460,8 @@ void process_login_d_e_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint32_t serial_number = stoul(base_cmd->serial_number, nullptr, 16);
|
||||
try {
|
||||
shared_ptr<const License> l;
|
||||
switch (c->version) {
|
||||
switch (c->version()) {
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC:
|
||||
l = s->license_manager->verify_pc(serial_number, base_cmd->access_key);
|
||||
break;
|
||||
@@ -415,11 +492,11 @@ void process_login_d_e_pc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
// license. So, if no license exists at this point, disconnect the client
|
||||
// even if unregistered clients are allowed.
|
||||
shared_ptr<License> l;
|
||||
if ((c->version == GameVersion::GC) || (c->version == GameVersion::XB)) {
|
||||
if ((c->version() == GameVersion::GC) || (c->version() == GameVersion::XB)) {
|
||||
send_command(c, 0x04, 0x04);
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else if (c->version == GameVersion::PC) {
|
||||
} else if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) {
|
||||
l = LicenseManager::create_license_pc(serial_number, base_cmd->access_key, true);
|
||||
s->license_manager->add(l);
|
||||
c->set_license(l);
|
||||
@@ -447,7 +524,7 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
throw runtime_error("invalid size for 93 command");
|
||||
}
|
||||
|
||||
c->flags |= flags_for_version(c->version, -1);
|
||||
c->flags |= flags_for_version(c->version(), -1);
|
||||
|
||||
try {
|
||||
auto l = s->license_manager->verify_bb(cmd.username, cmd.password);
|
||||
@@ -521,7 +598,7 @@ 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) {
|
||||
if (c->version() == GameVersion::BB) {
|
||||
const auto& cfg = check_size_t<ClientConfigBB>(data);
|
||||
c->import_config(cfg);
|
||||
} else {
|
||||
@@ -552,7 +629,7 @@ void process_server_time_request(shared_ptr<ServerState> s, shared_ptr<Client> c
|
||||
if (c->should_send_to_lobby_server) {
|
||||
static const vector<string> version_to_port_name({
|
||||
"console-lobby", "pc-lobby", "bb-lobby", "console-lobby", "console-lobby", "bb-lobby"});
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version()));
|
||||
send_reconnect(c, s->connect_address_for_client(c),
|
||||
s->name_to_port_config.at(port_name)->port);
|
||||
}
|
||||
@@ -691,7 +768,7 @@ void process_message_box_closed(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
check_size_v(data.size(), 0);
|
||||
if (c->flags & Client::Flag::IN_INFORMATION_MENU) {
|
||||
send_menu(c, u"Information", MenuID::INFORMATION,
|
||||
*s->information_menu_for_version(c->version));
|
||||
*s->information_menu_for_version(c->version()));
|
||||
} else if (c->flags & Client::Flag::AT_WELCOME_MESSAGE) {
|
||||
send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu);
|
||||
c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE;
|
||||
@@ -718,7 +795,7 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
} else {
|
||||
try {
|
||||
// we use item_id + 1 here because "go back" is the first item
|
||||
send_ship_info(c, s->information_menu_for_version(c->version)->at(cmd.item_id + 1).description.c_str());
|
||||
send_ship_info(c, s->information_menu_for_version(c->version())->at(cmd.item_id + 1).description.c_str());
|
||||
} catch (const out_of_range&) {
|
||||
send_ship_info(c, u"$C4Missing information\nmenu item");
|
||||
}
|
||||
@@ -730,7 +807,7 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
send_ship_info(c, u"Return to the\nmain menu.");
|
||||
} else {
|
||||
try {
|
||||
const auto& menu = s->proxy_destinations_menu_for_version(c->version);
|
||||
const auto& menu = s->proxy_destinations_menu_for_version(c->version());
|
||||
// we use item_id + 1 here because "go back" is the first item
|
||||
send_ship_info(c, menu.at(cmd.item_id + 1).description.c_str());
|
||||
} catch (const out_of_range&) {
|
||||
@@ -750,7 +827,7 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
send_quest_info(c, u"$C6Quests are not available.", !c->lobby_id);
|
||||
break;
|
||||
}
|
||||
auto q = s->quest_index->get(c->version, cmd.item_id);
|
||||
auto q = s->quest_index->get(c->version(), cmd.item_id);
|
||||
if (!q) {
|
||||
send_quest_info(c, u"$C4Quest does not\nexist.", !c->lobby_id);
|
||||
break;
|
||||
@@ -797,10 +874,18 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
episode = 3;
|
||||
}
|
||||
string secid_str = name_for_section_id(game->section_id);
|
||||
const char* mode_abbrev = "Nml";
|
||||
if (game->flags & Lobby::Flag::BATTLE_MODE) {
|
||||
mode_abbrev = "Btl";
|
||||
} else if (game->flags & Lobby::Flag::CHALLENGE_MODE) {
|
||||
mode_abbrev = "Chl";
|
||||
} else if (game->flags & Lobby::Flag::SOLO_MODE) {
|
||||
mode_abbrev = "Solo";
|
||||
}
|
||||
info += string_printf("Ep%d %c %s %s\n",
|
||||
episode,
|
||||
abbreviation_for_difficulty(game->difficulty),
|
||||
abbreviation_for_game_mode(game->mode),
|
||||
mode_abbrev,
|
||||
secid_str.c_str());
|
||||
|
||||
bool cheats_enabled = game->flags & Lobby::Flag::CHEATS_ENABLED;
|
||||
@@ -858,7 +943,7 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
|
||||
void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 10
|
||||
bool uses_unicode = ((c->version == GameVersion::PC) || (c->version == GameVersion::BB));
|
||||
bool uses_unicode = ((c->version() == GameVersion::PC) || (c->version() == GameVersion::BB));
|
||||
|
||||
uint32_t menu_id;
|
||||
uint32_t item_id;
|
||||
@@ -894,7 +979,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
} else {
|
||||
static const vector<string> version_to_port_name({
|
||||
"console-lobby", "pc-lobby", "bb-lobby", "console-lobby", "console-lobby", "bb-lobby"});
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version()));
|
||||
send_reconnect(c, s->connect_address_for_client(c),
|
||||
s->name_to_port_config.at(port_name)->port);
|
||||
}
|
||||
@@ -903,20 +988,20 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
case MainMenuItemID::INFORMATION:
|
||||
send_menu(c, u"Information", MenuID::INFORMATION,
|
||||
*s->information_menu_for_version(c->version));
|
||||
*s->information_menu_for_version(c->version()));
|
||||
c->flags |= Client::Flag::IN_INFORMATION_MENU;
|
||||
break;
|
||||
|
||||
case MainMenuItemID::PROXY_DESTINATIONS:
|
||||
send_menu(c, u"Proxy server", MenuID::PROXY_DESTINATIONS,
|
||||
s->proxy_destinations_menu_for_version(c->version));
|
||||
s->proxy_destinations_menu_for_version(c->version()));
|
||||
break;
|
||||
|
||||
case MainMenuItemID::DOWNLOAD_QUESTS:
|
||||
if (c->flags & Client::Flag::EPISODE_3) {
|
||||
shared_ptr<Lobby> l = c->lobby_id ? s->find_lobby(c->lobby_id) : nullptr;
|
||||
auto quests = s->quest_index->filter(
|
||||
c->version, false, QuestCategory::EPISODE_3);
|
||||
c->version(), c->flags & Client::Flag::DCV1, QuestCategory::EPISODE_3);
|
||||
if (quests.empty()) {
|
||||
send_lobby_message_box(c, u"$C6There are no quests\navailable.");
|
||||
} else {
|
||||
@@ -977,7 +1062,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
} else {
|
||||
const pair<string, uint16_t>* dest = nullptr;
|
||||
try {
|
||||
dest = &s->proxy_destinations_for_version(c->version).at(item_id);
|
||||
dest = &s->proxy_destinations_for_version(c->version()).at(item_id);
|
||||
} catch (const out_of_range&) { }
|
||||
|
||||
if (!dest) {
|
||||
@@ -990,8 +1075,8 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
// license/char name/etc. for remote auth)
|
||||
|
||||
static const vector<string> version_to_port_name({
|
||||
"console-proxy", "pc-proxy", "", "console-proxy", "console-proxy", "bb-proxy"});
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
|
||||
"dc-proxy", "pc-proxy", "", "gc-proxy", "xb-proxy", "bb-proxy"});
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version()));
|
||||
uint16_t local_port = s->name_to_port_config.at(port_name)->port;
|
||||
|
||||
c->proxy_destination_address = resolve_ipv4(dest->first);
|
||||
@@ -1000,7 +1085,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
s->proxy_server->delete_session(c->license->serial_number);
|
||||
s->proxy_server->create_licensed_session(
|
||||
c->license, local_port, c->version, c->export_config_bb());
|
||||
c->license, local_port, c->version(), c->export_config_bb());
|
||||
|
||||
send_reconnect(c, s->connect_address_for_client(c), local_port);
|
||||
}
|
||||
@@ -1022,8 +1107,9 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
send_lobby_message_box(c, u"$C6You cannot join this\ngame because it is\nfull.");
|
||||
break;
|
||||
}
|
||||
if ((game->version != c->version) ||
|
||||
(!(game->flags & Lobby::Flag::EPISODE_3_ONLY) != !(c->flags & Client::Flag::EPISODE_3))) {
|
||||
if ((game->version != c->version()) ||
|
||||
(!(game->flags & Lobby::Flag::EPISODE_3_ONLY) != !(c->flags & Client::Flag::EPISODE_3)) ||
|
||||
((game->flags & Lobby::Flag::DC_V2_ONLY) && (c->flags & Client::Flag::DCV1))) {
|
||||
send_lobby_message_box(c, u"$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO.");
|
||||
break;
|
||||
}
|
||||
@@ -1035,7 +1121,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
send_lobby_message_box(c, u"$C6You cannot join this\ngame because\nanother player is\ncurrently loading.\nTry again soon.");
|
||||
break;
|
||||
}
|
||||
if (game->mode == 3) {
|
||||
if (game->flags & Lobby::Flag::SOLO_MODE) {
|
||||
send_lobby_message_box(c, u"$C6You cannot join this\n game because it is\na Solo Mode game.");
|
||||
break;
|
||||
}
|
||||
@@ -1068,7 +1154,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
break;
|
||||
}
|
||||
shared_ptr<Lobby> l = c->lobby_id ? s->find_lobby(c->lobby_id) : nullptr;
|
||||
auto quests = s->quest_index->filter(c->version,
|
||||
auto quests = s->quest_index->filter(c->version(),
|
||||
c->flags & Client::Flag::DCV1,
|
||||
static_cast<QuestCategory>(item_id & 0xFF));
|
||||
if (quests.empty()) {
|
||||
@@ -1087,7 +1173,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
send_lobby_message_box(c, u"$C6Quests are not available.");
|
||||
break;
|
||||
}
|
||||
auto q = s->quest_index->get(c->version, item_id);
|
||||
auto q = s->quest_index->get(c->version(), item_id);
|
||||
if (!q) {
|
||||
send_lobby_message_box(c, u"$C6Quest does not exist.");
|
||||
break;
|
||||
@@ -1145,8 +1231,8 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
// (C->S 13 commands) like there are on GC. So, for PC/Trial clients,
|
||||
// we can just not set the loading flag, since we never need to
|
||||
// check/clear it later.
|
||||
if ((l->clients[x]->version != GameVersion::DC) &&
|
||||
(l->clients[x]->version != GameVersion::PC) &&
|
||||
if ((l->clients[x]->version() != GameVersion::DC) &&
|
||||
(l->clients[x]->version() != GameVersion::PC) &&
|
||||
!(l->clients[x]->flags & Client::Flag::GC_TRIAL_EDITION)) {
|
||||
l->clients[x]->flags |= Client::Flag::LOADING_QUEST;
|
||||
}
|
||||
@@ -1256,11 +1342,11 @@ void process_game_list_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
send_game_menu(c, s);
|
||||
}
|
||||
|
||||
void process_information_menu_request_pc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 1F
|
||||
void process_information_menu_request_dc_pc(shared_ptr<ServerState> s,
|
||||
shared_ptr<Client> c, uint16_t, uint32_t, const string& data) { // 1F
|
||||
check_size_v(data.size(), 0);
|
||||
send_menu(c, u"Information", MenuID::INFORMATION,
|
||||
*s->information_menu_for_version(c->version), true);
|
||||
*s->information_menu_for_version(c->version()), true);
|
||||
}
|
||||
|
||||
void process_change_ship(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
@@ -1281,8 +1367,8 @@ void process_change_ship(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
send_message_box(c, u"");
|
||||
|
||||
static const vector<string> version_to_port_name({
|
||||
"dc-login", "pc-login", "bb-patch", "gc-us3", "xb-login", "bb-init"});
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
|
||||
"console-login", "pc-login", "bb-patch", "console-login", "console-login", "bb-init"});
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version()));
|
||||
|
||||
send_reconnect(c, s->connect_address_for_client(c),
|
||||
s->name_to_port_config.at(port_name)->port);
|
||||
@@ -1370,19 +1456,17 @@ void process_quest_list_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
} else {
|
||||
vector<MenuItem>* menu = nullptr;
|
||||
if ((c->version == GameVersion::BB) && flag) {
|
||||
if ((c->version() == GameVersion::BB) && flag) {
|
||||
menu = &quest_government_menu;
|
||||
} else {
|
||||
if (l->mode == 0) {
|
||||
menu = &quest_categories_menu;
|
||||
} else if (l->mode == 1) {
|
||||
if (l->flags & Lobby::Flag::BATTLE_MODE) {
|
||||
menu = &quest_battle_menu;
|
||||
} else if (l->mode == 2) {
|
||||
} else if (l->flags & Lobby::Flag::CHALLENGE_MODE) {
|
||||
menu = &quest_challenge_menu;
|
||||
} else if (l->mode == 3) {
|
||||
} else if (l->flags & Lobby::Flag::SOLO_MODE) {
|
||||
menu = &quest_solo_menu;
|
||||
} else {
|
||||
throw logic_error("no quest menu available for mode");
|
||||
menu = &quest_categories_menu;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1428,7 +1512,7 @@ void process_quest_barrier(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
void process_update_quest_statistics(shared_ptr<ServerState> s,
|
||||
shared_ptr<Client> c, uint16_t, uint32_t, const string& data) { // AA
|
||||
const auto& cmd = check_size_t<C_UpdateQuestStatistics_AA>(data);
|
||||
const auto& cmd = check_size_t<C_UpdateQuestStatistics_V3_BB_AA>(data);
|
||||
|
||||
if (c->flags & Client::Flag::GC_TRIAL_EDITION) {
|
||||
throw runtime_error("trial edition client sent update quest stats command");
|
||||
@@ -1440,7 +1524,7 @@ void process_update_quest_statistics(shared_ptr<ServerState> s,
|
||||
return;
|
||||
}
|
||||
|
||||
S_ConfirmUpdateQuestStatistics_AB response;
|
||||
S_ConfirmUpdateQuestStatistics_V3_BB_AB response;
|
||||
response.unknown_a1 = 0x0000;
|
||||
response.unknown_a2 = 0x0000;
|
||||
response.request_token = cmd.request_token;
|
||||
@@ -1469,10 +1553,11 @@ void process_player_data(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
// Note: we add extra buffer on the end when checking sizes because the
|
||||
// autoreply text is a variable length
|
||||
switch (c->version) {
|
||||
switch (c->version()) {
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC: {
|
||||
const auto& pd = check_size_t<PSOPlayerDataPC>(data,
|
||||
sizeof(PSOPlayerDataPC), 0xFFFF);
|
||||
const auto& pd = check_size_t<PSOPlayerDataDCPC>(data,
|
||||
sizeof(PSOPlayerDataDCPC), 0xFFFF);
|
||||
c->game_data.import_player(pd);
|
||||
break;
|
||||
}
|
||||
@@ -1950,25 +2035,29 @@ void process_simple_mail(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // 81
|
||||
u16string message;
|
||||
uint32_t to_guild_card_number;
|
||||
if ((c->version == GameVersion::GC) || (c->version == GameVersion::XB)) {
|
||||
const auto& cmd = check_size_t<SC_SimpleMail_V3_81>(data);
|
||||
to_guild_card_number = cmd.to_guild_card_number;
|
||||
message = decode_sjis(cmd.text);
|
||||
|
||||
} else if (c->version == GameVersion::PC) {
|
||||
const auto& cmd = check_size_t<SC_SimpleMail_PC_81>(data);
|
||||
to_guild_card_number = cmd.to_guild_card_number;
|
||||
message = cmd.text;
|
||||
|
||||
} else if (c->version == GameVersion::BB) {
|
||||
const auto& cmd = check_size_t<SC_SimpleMail_BB_81>(data);
|
||||
to_guild_card_number = cmd.to_guild_card_number;
|
||||
message = cmd.text;
|
||||
|
||||
} else {
|
||||
// TODO
|
||||
send_text_message(c, u"$C6Simple Mail is not\nsupported yet on\nthis platform.");
|
||||
return;
|
||||
switch (c->version()) {
|
||||
case GameVersion::DC:
|
||||
case GameVersion::GC:
|
||||
case GameVersion::XB: {
|
||||
const auto& cmd = check_size_t<SC_SimpleMail_DC_V3_81>(data);
|
||||
to_guild_card_number = cmd.to_guild_card_number;
|
||||
message = decode_sjis(cmd.text);
|
||||
break;
|
||||
}
|
||||
case GameVersion::PC: {
|
||||
const auto& cmd = check_size_t<SC_SimpleMail_PC_81>(data);
|
||||
to_guild_card_number = cmd.to_guild_card_number;
|
||||
message = cmd.text;
|
||||
break;
|
||||
}
|
||||
case GameVersion::BB: {
|
||||
const auto& cmd = check_size_t<SC_SimpleMail_BB_81>(data);
|
||||
to_guild_card_number = cmd.to_guild_card_number;
|
||||
message = cmd.text;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw logic_error("invalid game version");
|
||||
}
|
||||
|
||||
auto target = s->find_client(nullptr, to_guild_card_number);
|
||||
@@ -2032,7 +2121,7 @@ void process_disable_auto_reply(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
|
||||
void process_set_blocked_senders_list(shared_ptr<ServerState>, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // C6
|
||||
if (c->version == GameVersion::BB) {
|
||||
if (c->version() == GameVersion::BB) {
|
||||
const auto& cmd = check_size_t<C_SetBlockedSenders_BB_C6>(data);
|
||||
c->game_data.account()->blocked_senders = cmd.blocked_senders;
|
||||
} else {
|
||||
@@ -2046,10 +2135,13 @@ void process_set_blocked_senders_list(shared_ptr<ServerState>, shared_ptr<Client
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Game commands
|
||||
|
||||
shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
||||
shared_ptr<Client> c, const std::u16string& name,
|
||||
const std::u16string& password, uint8_t episode, uint8_t difficulty,
|
||||
uint8_t battle, uint8_t challenge, uint8_t solo) {
|
||||
static shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
||||
shared_ptr<Client> c,
|
||||
const std::u16string& name,
|
||||
const std::u16string& password,
|
||||
uint8_t episode,
|
||||
uint8_t difficulty,
|
||||
uint32_t flags) {
|
||||
|
||||
// A player's actual level is their displayed level - 1, so the minimums for
|
||||
// Episode 1 (for example) are actually 1, 20, 40, 80.
|
||||
@@ -2058,13 +2150,13 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
||||
{0, 29, 49, 89}, // episode 2
|
||||
{0, 39, 79, 109}}; // episode 4
|
||||
|
||||
bool is_ep3 = (flags & Lobby::Flag::EPISODE_3_ONLY);
|
||||
if (episode == 0) {
|
||||
episode = 0xFF;
|
||||
}
|
||||
if (((episode != 0xFF) && (episode > 3)) || (episode == 0)) {
|
||||
throw invalid_argument("incorrect episode number");
|
||||
}
|
||||
bool is_ep3 = (episode == 0xFF);
|
||||
|
||||
if (difficulty > 3) {
|
||||
throw invalid_argument("incorrect difficulty level");
|
||||
@@ -2081,25 +2173,16 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
||||
throw invalid_argument("level too low for difficulty");
|
||||
}
|
||||
|
||||
bool item_tracking_enabled = (c->version == GameVersion::BB) | s->item_tracking_enabled;
|
||||
bool item_tracking_enabled = (c->version() == GameVersion::BB) | s->item_tracking_enabled;
|
||||
|
||||
shared_ptr<Lobby> game = s->create_lobby();
|
||||
game->name = name;
|
||||
game->password = password;
|
||||
game->version = c->version;
|
||||
game->version = c->version();
|
||||
game->section_id = c->override_section_id >= 0
|
||||
? c->override_section_id : c->game_data.player()->disp.section_id;
|
||||
game->episode = episode;
|
||||
game->difficulty = difficulty;
|
||||
if (battle) {
|
||||
game->mode = 1;
|
||||
}
|
||||
if (challenge) {
|
||||
game->mode = 2;
|
||||
}
|
||||
if (solo) {
|
||||
game->mode = 3;
|
||||
}
|
||||
if (c->override_random_seed >= 0) {
|
||||
game->random_seed = c->override_random_seed;
|
||||
game->random->seed(game->random_seed);
|
||||
@@ -2112,7 +2195,8 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
||||
game->flags =
|
||||
(is_ep3 ? Lobby::Flag::EPISODE_3_ONLY : 0) |
|
||||
(item_tracking_enabled ? Lobby::Flag::ITEM_TRACKING_ENABLED : 0) |
|
||||
Lobby::Flag::GAME;
|
||||
Lobby::Flag::GAME |
|
||||
flags;
|
||||
game->min_level = min_level;
|
||||
game->max_level = 0xFFFFFFFF;
|
||||
|
||||
@@ -2126,17 +2210,17 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
|
||||
}
|
||||
game->next_game_item_id = 0x00810000;
|
||||
|
||||
auto bp_subtable = s->battle_params->get_subtable(game->mode == 3,
|
||||
game->episode - 1, game->difficulty);
|
||||
bool is_solo = (game->flags & Lobby::Flag::SOLO_MODE);
|
||||
auto bp_subtable = s->battle_params->get_subtable(
|
||||
is_solo, game->episode - 1, game->difficulty);
|
||||
|
||||
generate_variations(
|
||||
game->variations, game->random, game->episode, game->mode == 3);
|
||||
generate_variations(game->variations, game->random, game->episode, is_solo);
|
||||
|
||||
for (size_t x = 0; x < 0x10; x++) {
|
||||
try {
|
||||
auto file = map_data_for_variation(
|
||||
game->episode,
|
||||
game->mode == 3,
|
||||
is_solo,
|
||||
x,
|
||||
game->variations[x * 2 + 0],
|
||||
game->variations[x * 2 + 1]);
|
||||
@@ -2184,41 +2268,67 @@ void process_create_game_pc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // C1
|
||||
const auto& cmd = check_size_t<C_CreateGame_PC_C1>(data);
|
||||
|
||||
create_game_generic(s, c, cmd.name, cmd.password, 1,
|
||||
cmd.difficulty, cmd.battle_mode, cmd.challenge_mode, 0);
|
||||
uint32_t flags = 0;
|
||||
if (cmd.battle_mode) {
|
||||
flags |= Lobby::Flag::BATTLE_MODE;
|
||||
}
|
||||
if (cmd.challenge_mode) {
|
||||
flags |= Lobby::Flag::CHALLENGE_MODE;
|
||||
}
|
||||
create_game_generic(s, c, cmd.name, cmd.password, 1, cmd.difficulty, flags);
|
||||
}
|
||||
|
||||
void process_create_game_dc_v3(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t command, uint32_t, const string& data) { // C1 EC (EC Ep3 only)
|
||||
const auto& cmd = check_size_t<C_CreateGame_DC_V3_C1_Ep3_EC>(data);
|
||||
uint16_t command, uint32_t, const string& data) { // 0C C1 EC (EC Ep3 only)
|
||||
const auto& cmd = check_size_t<C_CreateGame_DC_V3_0C_C1_Ep3_EC>(data);
|
||||
|
||||
// only allow EC from Ep3 clients
|
||||
// Only allow EC from Ep3 clients
|
||||
bool client_is_ep3 = c->flags & Client::Flag::EPISODE_3;
|
||||
if ((command == 0xEC) && !client_is_ep3) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t episode = cmd.episode;
|
||||
if ((c->version == GameVersion::DC) || (c->version == GameVersion::PC)) {
|
||||
uint32_t flags = 0;
|
||||
if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) {
|
||||
if (episode) {
|
||||
flags |= Lobby::Flag::DC_V2_ONLY;
|
||||
}
|
||||
episode = 1;
|
||||
}
|
||||
if (client_is_ep3) {
|
||||
} else if (client_is_ep3) {
|
||||
flags |= Lobby::Flag::EPISODE_3_ONLY;
|
||||
episode = 0xFF;
|
||||
}
|
||||
|
||||
u16string name = decode_sjis(cmd.name);
|
||||
u16string password = decode_sjis(cmd.password);
|
||||
|
||||
create_game_generic(s, c, name.c_str(), password.c_str(),
|
||||
episode, cmd.difficulty, cmd.battle_mode, cmd.challenge_mode, 0);
|
||||
if (cmd.battle_mode) {
|
||||
flags |= Lobby::Flag::BATTLE_MODE;
|
||||
}
|
||||
if (cmd.challenge_mode) {
|
||||
flags |= Lobby::Flag::CHALLENGE_MODE;
|
||||
}
|
||||
create_game_generic(
|
||||
s, c, name.c_str(), password.c_str(), episode, cmd.difficulty, flags);
|
||||
}
|
||||
|
||||
void process_create_game_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, const string& data) { // C1
|
||||
const auto& cmd = check_size_t<C_CreateGame_BB_C1>(data);
|
||||
|
||||
create_game_generic(s, c, cmd.name, cmd.password, cmd.episode,
|
||||
cmd.difficulty, cmd.battle_mode, cmd.challenge_mode, cmd.solo_mode);
|
||||
uint32_t flags = 0;
|
||||
if (cmd.battle_mode) {
|
||||
flags |= Lobby::Flag::BATTLE_MODE;
|
||||
}
|
||||
if (cmd.challenge_mode) {
|
||||
flags |= Lobby::Flag::CHALLENGE_MODE;
|
||||
}
|
||||
if (cmd.solo_mode) {
|
||||
flags |= Lobby::Flag::SOLO_MODE;
|
||||
}
|
||||
create_game_generic(
|
||||
s, c, cmd.name, cmd.password, cmd.episode, cmd.difficulty, flags);
|
||||
}
|
||||
|
||||
void process_lobby_name_request(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
@@ -2237,8 +2347,7 @@ void process_client_ready(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
auto l = s->find_lobby(c->lobby_id);
|
||||
if (!l || !l->is_game()) {
|
||||
// go home client; you're drunk
|
||||
throw invalid_argument("ready command cannot be sent outside game");
|
||||
throw runtime_error("client sent ready command ontside of game");
|
||||
}
|
||||
c->flags &= (~Client::Flag::LOADING);
|
||||
|
||||
@@ -2247,7 +2356,7 @@ void process_client_ready(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
// Only get player info again on BB, since on other versions the returned info
|
||||
// only includes items that would be saved if the client disconnects
|
||||
// unexpectedly (that is, only equipped items are included).
|
||||
if (c->version == GameVersion::BB) {
|
||||
if (c->version() == GameVersion::BB) {
|
||||
send_get_player_info(c);
|
||||
}
|
||||
}
|
||||
@@ -2523,7 +2632,7 @@ void process_ignored_command(shared_ptr<ServerState>, shared_ptr<Client>,
|
||||
|
||||
void process_unimplemented_command(shared_ptr<ServerState>,
|
||||
shared_ptr<Client> c, uint16_t command, uint32_t flag, const string& data) {
|
||||
c->log.warning("Unknown command: size=%04zX command=%04hX flag=%08" PRIX32 "\n",
|
||||
c->log.warning("Unknown command: size=%04zX command=%04hX flag=%08" PRIX32,
|
||||
data.size(), command, flag);
|
||||
throw invalid_argument("unimplemented command");
|
||||
}
|
||||
@@ -2541,13 +2650,13 @@ static process_command_t dc_handlers[0x100] = {
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, process_ignored_command, process_chat_dc_v3, nullptr,
|
||||
process_game_list_request, process_menu_item_info_request, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
process_create_game_dc_v3, nullptr, nullptr, nullptr,
|
||||
|
||||
// 10
|
||||
process_menu_selection, nullptr, nullptr, process_ignored_command,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, process_ignored_command, nullptr, nullptr,
|
||||
nullptr, process_ignored_command, nullptr, process_information_menu_request_dc_pc,
|
||||
|
||||
// 20
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
@@ -2567,7 +2676,7 @@ static process_command_t dc_handlers[0x100] = {
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
// 60
|
||||
process_game_command, nullptr, process_game_command, nullptr,
|
||||
process_game_command, process_player_data, process_game_command, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
process_game_command, process_game_command, nullptr, process_client_ready,
|
||||
@@ -2583,15 +2692,15 @@ static process_command_t dc_handlers[0x100] = {
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
// 90
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
process_login_0_dc_pc_v3, nullptr, process_login_2_dc, process_login_3_dc_pc_v3,
|
||||
nullptr, nullptr, process_client_checksum, nullptr,
|
||||
process_player_data, process_ignored_command, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
process_player_data, process_ignored_command, process_login_a_dc_pc_v3, nullptr,
|
||||
process_login_c_dc_pc_v3, process_login_d_e_dc_pc_v3, nullptr, nullptr,
|
||||
|
||||
// A0
|
||||
process_change_ship, process_change_block, process_quest_list_request, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, process_ignored_command, process_update_quest_statistics, nullptr,
|
||||
nullptr, process_ignored_command, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
// B0
|
||||
@@ -2600,15 +2709,14 @@ static process_command_t dc_handlers[0x100] = {
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
// C0
|
||||
nullptr, process_create_game_dc_v3, nullptr, nullptr,
|
||||
nullptr, nullptr, process_set_blocked_senders_list, process_set_auto_reply_t<char>,
|
||||
process_disable_auto_reply, nullptr, nullptr, nullptr,
|
||||
process_choice_search, process_create_game_dc_v3, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
// D0
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
process_info_board_request, process_write_info_board_t<char>, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
// E0
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
@@ -2630,7 +2738,7 @@ static process_command_t pc_handlers[0x100] = {
|
||||
process_menu_selection, nullptr, nullptr, process_ignored_command,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, process_ignored_command, nullptr, process_information_menu_request_pc,
|
||||
nullptr, process_ignored_command, nullptr, process_information_menu_request_dc_pc,
|
||||
|
||||
// 20
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
@@ -2669,7 +2777,7 @@ static process_command_t pc_handlers[0x100] = {
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, process_client_checksum, nullptr,
|
||||
process_player_data, process_ignored_command, process_login_a_dc_pc_v3, nullptr,
|
||||
process_login_c_dc_pc_v3, process_login_d_e_pc_v3, process_login_d_e_pc_v3, nullptr,
|
||||
process_login_c_dc_pc_v3, process_login_d_e_dc_pc_v3, process_login_d_e_dc_pc_v3, nullptr,
|
||||
|
||||
// A0
|
||||
process_change_ship, process_change_block, process_quest_list_request, nullptr,
|
||||
@@ -2750,10 +2858,10 @@ static process_command_t gc_handlers[0x100] = {
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
|
||||
// 90
|
||||
nullptr, nullptr, nullptr, nullptr,
|
||||
process_login_0_dc_pc_v3, nullptr, nullptr, process_login_3_dc_pc_v3,
|
||||
nullptr, nullptr, process_client_checksum, nullptr,
|
||||
process_player_data, process_ignored_command, nullptr, nullptr,
|
||||
process_login_c_dc_pc_v3, process_login_d_e_pc_v3, process_login_d_e_pc_v3, process_return_client_config,
|
||||
process_player_data, process_ignored_command, process_login_a_dc_pc_v3, nullptr,
|
||||
process_login_c_dc_pc_v3, process_login_d_e_dc_pc_v3, process_login_d_e_dc_pc_v3, process_return_client_config,
|
||||
|
||||
// A0
|
||||
process_change_ship, process_change_block, process_quest_list_request, nullptr,
|
||||
@@ -2841,7 +2949,7 @@ static process_command_t xb_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_v3, process_login_d_e_pc_v3, process_login_d_e_pc_v3, process_return_client_config,
|
||||
process_login_c_dc_pc_v3, process_login_d_e_dc_pc_v3, process_login_d_e_dc_pc_v3, process_return_client_config,
|
||||
|
||||
// A0
|
||||
process_change_ship, process_change_block, process_quest_list_request, nullptr,
|
||||
@@ -3028,9 +3136,10 @@ static process_command_t* handlers[6] = {
|
||||
void check_unlicensed_command(GameVersion version, uint8_t command) {
|
||||
switch (version) {
|
||||
case GameVersion::DC:
|
||||
// TODO: Fix this when we support DC. It will be pretty obvious during
|
||||
// testing that this should be fixed (and how to fix it).
|
||||
throw runtime_error("no unlicensed commands are valid on DC");
|
||||
// newserv doesn't actually know that DC clients are DC until it receives
|
||||
// an appropriate login command (93, 9A, or 9D), but those commands also
|
||||
// log the client in, so this case should never be executed.
|
||||
throw logic_error("cannot check unlicensed command for DC client");
|
||||
case GameVersion::PC:
|
||||
if (command != 0x9A && command != 0x9D) {
|
||||
throw runtime_error("only commands 9A and 9D may be sent before login");
|
||||
@@ -3038,8 +3147,14 @@ void check_unlicensed_command(GameVersion version, uint8_t command) {
|
||||
break;
|
||||
case GameVersion::GC:
|
||||
case GameVersion::XB:
|
||||
if (command != 0xDB && command != 0x9A && command != 0x9E) {
|
||||
throw runtime_error("only commands DB, 9A and 9E may be sent before login");
|
||||
// See comment in the DC case above for why DC commands are included here.
|
||||
if (command != 0x90 && // DC v1
|
||||
command != 0x93 && // DC v1
|
||||
command != 0x9A && // DC v2
|
||||
command != 0x9D && // DC v2, GC trial edition
|
||||
command != 0x9E && // GC non-trial
|
||||
command != 0xDB) { // GC non-trial
|
||||
throw runtime_error("only commands 90, 93, 9A, 9D, 9E, and DB may be sent before login");
|
||||
}
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
@@ -3070,10 +3185,10 @@ void process_command(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
// is allowed to access normal functionality. This check prevents clients from
|
||||
// sneakily sending commands to access functionality without logging in.
|
||||
if (!c->license.get()) {
|
||||
check_unlicensed_command(c->version, command);
|
||||
check_unlicensed_command(c->version(), command);
|
||||
}
|
||||
|
||||
auto fn = handlers[static_cast<size_t>(c->version)][command & 0xFF];
|
||||
auto fn = handlers[static_cast<size_t>(c->version())][command & 0xFF];
|
||||
if (fn) {
|
||||
fn(s, c, command, flag, data);
|
||||
} else {
|
||||
|
||||
+48
-28
@@ -115,17 +115,29 @@ static void process_subcommand_send_guild_card(shared_ptr<ServerState>,
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->version == GameVersion::PC) {
|
||||
const auto* cmd = check_size_sc<G_SendGuildCard_PC_6x06>(data);
|
||||
c->game_data.player()->guild_card_description = cmd->description;
|
||||
} else if ((c->version == GameVersion::GC) || (c->version == GameVersion::XB)) {
|
||||
const auto* cmd = check_size_sc<G_SendGuildCard_V3_6x06>(data);
|
||||
c->game_data.player()->guild_card_description = cmd->description;
|
||||
} else if (c->version == GameVersion::BB) {
|
||||
// Nothing to do... the command is blank; the server generates the guild
|
||||
// card to be sent
|
||||
} else {
|
||||
throw runtime_error("unsupported game version");
|
||||
switch (c->version()) {
|
||||
case GameVersion::DC: {
|
||||
const auto* cmd = check_size_sc<G_SendGuildCard_PC_6x06>(data);
|
||||
c->game_data.player()->guild_card_description = cmd->description;
|
||||
break;
|
||||
}
|
||||
case GameVersion::PC: {
|
||||
const auto* cmd = check_size_sc<G_SendGuildCard_PC_6x06>(data);
|
||||
c->game_data.player()->guild_card_description = cmd->description;
|
||||
break;
|
||||
}
|
||||
case GameVersion::GC:
|
||||
case GameVersion::XB: {
|
||||
const auto* cmd = check_size_sc<G_SendGuildCard_V3_6x06>(data);
|
||||
c->game_data.player()->guild_card_description = cmd->description;
|
||||
break;
|
||||
}
|
||||
case GameVersion::BB:
|
||||
// Nothing to do... the command is blank; the server generates the guild
|
||||
// card to be sent
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unsupported game version");
|
||||
}
|
||||
|
||||
send_guild_card(l->clients[flag], c);
|
||||
@@ -154,7 +166,7 @@ static void process_subcommand_word_select(shared_ptr<ServerState>,
|
||||
}
|
||||
|
||||
// TODO: bring this back if it turns out to be important; I suspect it's not
|
||||
//p->byte[2] = p->byte[3] = p->byte[(c->version == GameVersion::BB) ? 2 : 3];
|
||||
//p->byte[2] = p->byte[3] = p->byte[(c->version() == GameVersion::BB) ? 2 : 3];
|
||||
|
||||
for (size_t x = 1; x < 8; x++) {
|
||||
if ((p[x].word[0] > 0x1863) && (p[x].word[0] != 0xFFFF)) {
|
||||
@@ -179,7 +191,7 @@ static void process_subcommand_set_player_visibility(shared_ptr<ServerState>,
|
||||
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
|
||||
if (!l->is_game()) {
|
||||
if (!l->is_game() && !(c->flags & Client::Flag::DCV1)) {
|
||||
send_arrow_update(l);
|
||||
}
|
||||
}
|
||||
@@ -292,12 +304,13 @@ static void process_subcommand_player_drop_item(shared_ptr<ServerState>,
|
||||
static void process_subcommand_create_inventory_item(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const string& data) {
|
||||
const auto* cmd = check_size_sc<G_PlayerCreateInventoryItem_6x2B>(data);
|
||||
const auto* cmd = check_size_sc<G_PlayerCreateInventoryItem_DC_6x2B>(data,
|
||||
sizeof(G_PlayerCreateInventoryItem_DC_6x2B), sizeof(G_PlayerCreateInventoryItem_PC_V3_BB_6x2B));
|
||||
|
||||
if ((cmd->client_id != c->lobby_client_id)) {
|
||||
return;
|
||||
}
|
||||
if (c->version == GameVersion::BB) {
|
||||
if (c->version() == GameVersion::BB) {
|
||||
// BB should never send this command - inventory items should only be
|
||||
// created by the server in response to shop buy / bank withdraw / etc. reqs
|
||||
return;
|
||||
@@ -322,7 +335,8 @@ static void process_subcommand_create_inventory_item(shared_ptr<ServerState>,
|
||||
static void process_subcommand_drop_partial_stack(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const string& data) {
|
||||
const auto* cmd = check_size_sc<G_DropStackedItem_6x5D>(data);
|
||||
const auto* cmd = check_size_sc<G_DropStackedItem_DC_6x5D>(data,
|
||||
sizeof(G_DropStackedItem_DC_6x5D), sizeof(G_DropStackedItem_PC_V3_BB_6x5D));
|
||||
|
||||
// TODO: Should we check the client ID here too?
|
||||
if (!l->is_game()) {
|
||||
@@ -422,7 +436,8 @@ static void process_subcommand_buy_shop_item(shared_ptr<ServerState>,
|
||||
static void process_subcommand_box_or_enemy_item_drop(shared_ptr<ServerState>,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
const string& data) {
|
||||
const auto* cmd = check_size_sc<G_DropItem_6x5F>(data);
|
||||
const auto* cmd = check_size_sc<G_DropItem_DC_6x5F>(data,
|
||||
sizeof(G_DropItem_DC_6x5F), sizeof(G_DropItem_PC_V3_BB_6x5F));
|
||||
|
||||
if (!l->is_game() || (c->lobby_client_id != l->leader_id)) {
|
||||
return;
|
||||
@@ -755,7 +770,9 @@ static void process_subcommand_enemy_drop_item_request(shared_ptr<ServerState>,
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* cmd = check_size_sc<G_EnemyDropItemRequest_6x60>(data);
|
||||
const auto* cmd = check_size_sc<G_EnemyDropItemRequest_DC_6x60>(data,
|
||||
sizeof(G_EnemyDropItemRequest_DC_6x60),
|
||||
sizeof(G_EnemyDropItemRequest_PC_V3_BB_6x60));
|
||||
if (!drop_item(l, cmd->enemy_id, cmd->area, cmd->x, cmd->z, cmd->request_id)) {
|
||||
forward_subcommand(l, c, command, flag, data);
|
||||
}
|
||||
@@ -801,16 +818,19 @@ static void process_subcommand_phase_setup(shared_ptr<ServerState>,
|
||||
if (should_send_boss_drop_req) {
|
||||
auto c = l->clients.at(l->leader_id);
|
||||
if (c) {
|
||||
G_EnemyDropItemRequest_6x60 req = {
|
||||
0x60,
|
||||
0x06,
|
||||
0x1090,
|
||||
static_cast<uint8_t>(c->area),
|
||||
static_cast<uint8_t>((l->episode == 2) ? 0x4E : 0x2F),
|
||||
0x0B4F,
|
||||
(l->episode == 2) ? -9999.0f : 10160.58984375f,
|
||||
0.0f,
|
||||
0xE0AEDC0100000002,
|
||||
G_EnemyDropItemRequest_PC_V3_BB_6x60 req = {
|
||||
{
|
||||
0x60,
|
||||
0x06,
|
||||
0x1090,
|
||||
static_cast<uint8_t>(c->area),
|
||||
static_cast<uint8_t>((l->episode == 2) ? 0x4E : 0x2F),
|
||||
0x0B4F,
|
||||
(l->episode == 2) ? -9999.0f : 10160.58984375f,
|
||||
0.0f,
|
||||
0x00000002,
|
||||
},
|
||||
0xE0AEDC01,
|
||||
};
|
||||
send_command_t(c, 0x62, l->leader_id, req);
|
||||
}
|
||||
|
||||
+34
-23
@@ -48,12 +48,13 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
|
||||
auto version = this->clients.at(ev->client_id)->version;
|
||||
|
||||
auto check_pw = [&](const string& pw) {
|
||||
if (!this->required_password.empty() && (pw != this->required_password)) {
|
||||
if (!this->required_password.empty() && !pw.empty() && (pw != this->required_password)) {
|
||||
print_data(stderr, ev->data);
|
||||
throw runtime_error("sent password is incorrect");
|
||||
}
|
||||
};
|
||||
auto check_ak = [&](const string& ak) {
|
||||
if (this->required_access_key.empty()) {
|
||||
if (this->required_access_key.empty() || ak.empty()) {
|
||||
return;
|
||||
}
|
||||
string ref_access_key;
|
||||
@@ -63,6 +64,7 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
|
||||
ref_access_key = this->required_access_key;
|
||||
}
|
||||
if (ak != ref_access_key) {
|
||||
print_data(stderr, ev->data);
|
||||
throw runtime_error("sent access key is incorrect");
|
||||
}
|
||||
};
|
||||
@@ -78,9 +80,6 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
|
||||
size_t cmd_size = ev->data.size() - ((version == GameVersion::BB) ? 8 : 4);
|
||||
|
||||
switch (version) {
|
||||
case GameVersion::DC:
|
||||
// TODO
|
||||
throw logic_error("sent passwords cannot be checked on DC");
|
||||
case GameVersion::PATCH: {
|
||||
const auto& header = check_size_t<PSOCommandHeaderPC>(
|
||||
ev->data, sizeof(PSOCommandHeaderPC), 0xFFFF);
|
||||
@@ -98,6 +97,7 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
|
||||
check_ak(check_size_t<C_LegacyLogin_PC_V3_04>(cmd_data, cmd_size).access_key);
|
||||
} else if (header.command == 0x9A) {
|
||||
const auto& cmd = check_size_t<C_Login_DC_PC_V3_9A>(cmd_data, cmd_size);
|
||||
check_ak(cmd.v1_access_key);
|
||||
check_ak(cmd.access_key);
|
||||
check_ak(cmd.access_key2);
|
||||
} else if (header.command == 0x9C) {
|
||||
@@ -105,13 +105,15 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
|
||||
check_ak(cmd.access_key);
|
||||
check_pw(cmd.password);
|
||||
} else if (header.command == 0x9D) {
|
||||
const auto& cmd = check_size_t<C_Login_PC_GC_9D>(cmd_data, cmd_size,
|
||||
sizeof(C_Login_PC_GC_9D), sizeof(C_LoginExtended_PC_GC_9D));
|
||||
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(cmd_data, cmd_size,
|
||||
sizeof(C_Login_DC_PC_GC_9D), sizeof(C_LoginExtended_DC_PC_GC_9D));
|
||||
check_ak(cmd.v1_access_key);
|
||||
check_ak(cmd.access_key);
|
||||
check_ak(cmd.access_key2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GameVersion::DC:
|
||||
case GameVersion::GC:
|
||||
case GameVersion::XB: {
|
||||
const auto& header = check_size_t<PSOCommandHeaderDCV3>(
|
||||
@@ -121,15 +123,26 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
|
||||
} else if (header.command == 0x04) {
|
||||
check_ak(check_size_t<C_LegacyLogin_PC_V3_04>(cmd_data, cmd_size).access_key);
|
||||
} else if (header.command == 0x90) {
|
||||
check_ak(check_size_t<C_LegacyLogin_V3_90>(cmd_data, cmd_size).access_key);
|
||||
check_ak(check_size_t<C_LoginV1_DC_PC_V3_90>(cmd_data, cmd_size).access_key);
|
||||
} else if (header.command == 0x93) {
|
||||
const auto& cmd = check_size_t<C_LoginV1_DC_93>(cmd_data, cmd_size,
|
||||
sizeof(C_LoginV1_DC_93), sizeof(C_LoginExtendedV1_DC_93));
|
||||
check_ak(cmd.access_key);
|
||||
} else if (header.command == 0x9A) {
|
||||
const auto& cmd = check_size_t<C_Login_DC_PC_V3_9A>(cmd_data, cmd_size);
|
||||
check_ak(cmd.v1_access_key);
|
||||
check_ak(cmd.access_key);
|
||||
check_ak(cmd.access_key2);
|
||||
} else if (header.command == 0x9C) {
|
||||
const auto& cmd = check_size_t<C_Register_DC_PC_V3_9C>(cmd_data, cmd_size);
|
||||
check_ak(cmd.access_key);
|
||||
check_pw(cmd.password);
|
||||
} else if (header.command == 0x9D) {
|
||||
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(cmd_data, cmd_size,
|
||||
sizeof(C_Login_DC_PC_GC_9D), sizeof(C_LoginExtended_DC_PC_GC_9D));
|
||||
check_ak(cmd.v1_access_key);
|
||||
check_ak(cmd.access_key);
|
||||
check_ak(cmd.access_key2);
|
||||
} else if (header.command == 0x9E) {
|
||||
if (version == GameVersion::GC) {
|
||||
const auto& cmd = check_size_t<C_Login_GC_9E>(cmd_data, cmd_size,
|
||||
@@ -188,6 +201,7 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC:
|
||||
case GameVersion::GC:
|
||||
case GameVersion::XB: {
|
||||
@@ -211,9 +225,14 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
break;
|
||||
}
|
||||
case 0x19: {
|
||||
auto& cmd_mask = check_size_t<S_Reconnect_19>(cmd_data, cmd_size,
|
||||
sizeof(S_Reconnect_19), sizeof(S_ReconnectSplit_19));
|
||||
cmd_mask.address = 0;
|
||||
if (cmd_size == sizeof(S_ReconnectSplit_19)) {
|
||||
auto& cmd_mask = check_size_t<S_ReconnectSplit_19>(cmd_data, cmd_size);
|
||||
cmd_mask.pc_address = 0;
|
||||
cmd_mask.gc_address = 0;
|
||||
} else {
|
||||
auto& cmd_mask = check_size_t<S_Reconnect_19>(cmd_data, cmd_size);
|
||||
cmd_mask.address = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x64: {
|
||||
@@ -222,8 +241,8 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
cmd_mask.variations.clear(0);
|
||||
cmd_mask.rare_seed = 0;
|
||||
} else { // V3
|
||||
auto& cmd_mask = check_size_t<S_JoinGame_GC_64>(cmd_data, cmd_size,
|
||||
sizeof(S_JoinGame_GC_64), sizeof(S_JoinGame_GC_Ep3_64));
|
||||
auto& cmd_mask = check_size_t<S_JoinGame_DC_GC_64>(cmd_data, cmd_size,
|
||||
sizeof(S_JoinGame_DC_GC_64), sizeof(S_JoinGame_GC_Ep3_64));
|
||||
cmd_mask.variations.clear(0);
|
||||
cmd_mask.rare_seed = 0;
|
||||
}
|
||||
@@ -274,8 +293,6 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GameVersion::DC:
|
||||
throw logic_error("DC auto-masking is not implemented");
|
||||
default:
|
||||
throw logic_error("invalid game version");
|
||||
}
|
||||
@@ -323,12 +340,7 @@ ReplaySession::ReplaySession(
|
||||
if (parsing_command->type == Event::Type::RECEIVE) {
|
||||
this->apply_default_mask(parsing_command);
|
||||
} else if (parsing_command->type == Event::Type::SEND) {
|
||||
try {
|
||||
this->check_for_password(parsing_command);
|
||||
} catch (...) {
|
||||
print_data(stderr, parsing_command->data);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
parsing_command = nullptr;
|
||||
}
|
||||
@@ -553,8 +565,6 @@ void ReplaySession::on_command_received(
|
||||
|
||||
// If the command is an encryption init, set up encryption on the channel
|
||||
switch (c->version) {
|
||||
case GameVersion::DC:
|
||||
throw runtime_error("DC encryption is not supported during replays");
|
||||
case GameVersion::PATCH:
|
||||
if (command == 0x02) {
|
||||
auto& cmd = check_size_t<S_ServerInit_Patch_02>(data);
|
||||
@@ -562,13 +572,14 @@ void ReplaySession::on_command_received(
|
||||
c->channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key));
|
||||
}
|
||||
break;
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC:
|
||||
case GameVersion::GC:
|
||||
case GameVersion::XB:
|
||||
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
|
||||
auto& cmd = check_size_t<S_ServerInit_DC_PC_V3_02_17_91_9B>(data,
|
||||
offsetof(S_ServerInit_DC_PC_V3_02_17_91_9B, after_message), 0xFFFF);
|
||||
if (c->version == GameVersion::PC) {
|
||||
if ((c->version == GameVersion::DC) || (c->version == GameVersion::PC)) {
|
||||
c->channel.crypt_in.reset(new PSOV2Encryption(cmd.server_key));
|
||||
c->channel.crypt_out.reset(new PSOV2Encryption(cmd.client_key));
|
||||
} else { // V3
|
||||
|
||||
+189
-123
@@ -27,7 +27,7 @@ extern FileContentsCache file_cache;
|
||||
|
||||
|
||||
const unordered_set<uint32_t> v2_crypt_initial_client_commands({
|
||||
0x00290090, // (17) DCv1 license check
|
||||
0x00280090, // (17) DCv1 license check
|
||||
0x00B00093, // (02) DCv1 login
|
||||
0x01140093, // (02) DCv1 extended login
|
||||
0x00E0009A, // (17) DCv2 license check
|
||||
@@ -134,7 +134,7 @@ void send_server_init_dc_pc_v3(shared_ptr<Client> c, uint8_t flags) {
|
||||
server_key, client_key, initial_connection);
|
||||
send_command_t(c, command, 0x00, cmd);
|
||||
|
||||
switch (c->version) {
|
||||
switch (c->version()) {
|
||||
case GameVersion::PC:
|
||||
c->channel.crypt_in.reset(new PSOV2Encryption(client_key));
|
||||
c->channel.crypt_out.reset(new PSOV2Encryption(server_key));
|
||||
@@ -204,7 +204,7 @@ void send_server_init_patch(shared_ptr<Client> c) {
|
||||
|
||||
void send_server_init(
|
||||
shared_ptr<ServerState> s, shared_ptr<Client> c, uint8_t flags) {
|
||||
switch (c->version) {
|
||||
switch (c->version()) {
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC:
|
||||
case GameVersion::GC:
|
||||
@@ -224,7 +224,6 @@ void send_server_init(
|
||||
|
||||
|
||||
|
||||
// for non-BB clients, updates the client's guild card and security data
|
||||
void send_update_client_config(shared_ptr<Client> c) {
|
||||
S_UpdateClientConfig_DC_PC_V3_04 cmd;
|
||||
cmd.player_tag = 0x00010000;
|
||||
@@ -295,9 +294,7 @@ void send_function_call(
|
||||
|
||||
void send_reconnect(shared_ptr<Client> c, uint32_t address, uint16_t port) {
|
||||
S_Reconnect_19 cmd = {address, port, 0};
|
||||
// On the patch server, 14 is the reconnect command, but it works exactly the
|
||||
// same way as 19 on the game server.
|
||||
send_command_t(c, (c->version == GameVersion::PATCH) ? 0x14 : 0x19, 0x00, cmd);
|
||||
send_command_t(c, (c->version() == GameVersion::PATCH) ? 0x14 : 0x19, 0x00, cmd);
|
||||
}
|
||||
|
||||
void send_pc_console_split_reconnect(shared_ptr<Client> c, uint32_t address,
|
||||
@@ -535,7 +532,7 @@ void send_header_text(Channel& ch, uint16_t command,
|
||||
|
||||
void send_message_box(shared_ptr<Client> c, const u16string& text) {
|
||||
uint16_t command;
|
||||
switch (c->version) {
|
||||
switch (c->version()) {
|
||||
case GameVersion::PATCH:
|
||||
command = 0x13;
|
||||
break;
|
||||
@@ -602,7 +599,7 @@ void send_chat_message(Channel& ch, const u16string& text) {
|
||||
void send_chat_message(shared_ptr<Client> c, uint32_t from_guild_card_number,
|
||||
const u16string& from_name, const u16string& text) {
|
||||
u16string data;
|
||||
if (c->version == GameVersion::BB) {
|
||||
if (c->version() == GameVersion::BB) {
|
||||
data.append(u"\x09J");
|
||||
}
|
||||
data.append(remove_language_marker(from_name));
|
||||
@@ -643,16 +640,22 @@ void send_simple_mail_bb(
|
||||
|
||||
void send_simple_mail(shared_ptr<Client> c, uint32_t from_guild_card_number,
|
||||
const u16string& from_name, const u16string& text) {
|
||||
if ((c->version == GameVersion::GC) || (c->version == GameVersion::XB)) {
|
||||
send_simple_mail_t<SC_SimpleMail_V3_81>(
|
||||
c, from_guild_card_number, from_name, text);
|
||||
} else if (c->version == GameVersion::PC) {
|
||||
send_simple_mail_t<SC_SimpleMail_PC_81>(
|
||||
c, from_guild_card_number, from_name, text);
|
||||
} else if (c->version == GameVersion::BB) {
|
||||
send_simple_mail_bb(c, from_guild_card_number, from_name, text);
|
||||
} else {
|
||||
throw logic_error("unimplemented versioned command");
|
||||
switch (c->version()) {
|
||||
case GameVersion::DC:
|
||||
case GameVersion::GC:
|
||||
case GameVersion::XB:
|
||||
send_simple_mail_t<SC_SimpleMail_DC_V3_81>(
|
||||
c, from_guild_card_number, from_name, text);
|
||||
break;
|
||||
case GameVersion::PC:
|
||||
send_simple_mail_t<SC_SimpleMail_PC_81>(
|
||||
c, from_guild_card_number, from_name, text);
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
send_simple_mail_bb(c, from_guild_card_number, from_name, text);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unimplemented versioned command");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,8 +680,9 @@ void send_info_board_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
}
|
||||
|
||||
void send_info_board(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
if (c->version == GameVersion::PC || c->version == GameVersion::PATCH ||
|
||||
c->version == GameVersion::BB) {
|
||||
if (c->version() == GameVersion::PC ||
|
||||
c->version() == GameVersion::PATCH ||
|
||||
c->version() == GameVersion::BB) {
|
||||
send_info_board_t<char16_t>(c, l);
|
||||
} else {
|
||||
send_info_board_t<char>(c, l);
|
||||
@@ -698,7 +702,7 @@ void send_card_search_result_t(
|
||||
shared_ptr<Lobby> result_lobby) {
|
||||
static const vector<string> version_to_port_name({
|
||||
"console-lobby", "pc-lobby", "bb-lobby", "console-lobby", "console-lobby", "bb-lobby"});
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version()));
|
||||
|
||||
S_GuildCardSearchResult<CommandHeaderT, CharT> cmd;
|
||||
cmd.player_tag = 0x00010000;
|
||||
@@ -737,15 +741,15 @@ void send_card_search_result(
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<Client> result,
|
||||
shared_ptr<Lobby> result_lobby) {
|
||||
if ((c->version == GameVersion::DC) ||
|
||||
(c->version == GameVersion::GC) ||
|
||||
(c->version == GameVersion::XB)) {
|
||||
if ((c->version() == GameVersion::DC) ||
|
||||
(c->version() == GameVersion::GC) ||
|
||||
(c->version() == GameVersion::XB)) {
|
||||
send_card_search_result_t<PSOCommandHeaderDCV3, char>(
|
||||
s, c, result, result_lobby);
|
||||
} else if (c->version == GameVersion::PC) {
|
||||
} else if (c->version() == GameVersion::PC) {
|
||||
send_card_search_result_t<PSOCommandHeaderPC, char16_t>(
|
||||
s, c, result, result_lobby);
|
||||
} else if (c->version == GameVersion::BB) {
|
||||
} else if (c->version() == GameVersion::BB) {
|
||||
send_card_search_result_t<PSOCommandHeaderBB, char16_t>(
|
||||
s, c, result, result_lobby);
|
||||
} else {
|
||||
@@ -756,7 +760,7 @@ void send_card_search_result(
|
||||
|
||||
|
||||
template <typename CmdT>
|
||||
void send_guild_card_pc_v3_t(shared_ptr<Client> c, shared_ptr<Client> source) {
|
||||
void send_guild_card_dc_pc_v3_t(shared_ptr<Client> c, shared_ptr<Client> source) {
|
||||
CmdT cmd;
|
||||
cmd.subcommand = 0x06;
|
||||
cmd.size = sizeof(CmdT) / 4;
|
||||
@@ -790,12 +794,14 @@ void send_guild_card_bb(shared_ptr<Client> c, shared_ptr<Client> source) {
|
||||
}
|
||||
|
||||
void send_guild_card(shared_ptr<Client> c, shared_ptr<Client> source) {
|
||||
if (c->version == GameVersion::PC) {
|
||||
send_guild_card_pc_v3_t<G_SendGuildCard_PC_6x06>(c, source);
|
||||
} else if ((c->version == GameVersion::GC) ||
|
||||
(c->version == GameVersion::XB)) {
|
||||
send_guild_card_pc_v3_t<G_SendGuildCard_V3_6x06>(c, source);
|
||||
} else if (c->version == GameVersion::BB) {
|
||||
if (c->version() == GameVersion::DC) {
|
||||
send_guild_card_dc_pc_v3_t<G_SendGuildCard_DC_6x06>(c, source);
|
||||
} else if (c->version() == GameVersion::PC) {
|
||||
send_guild_card_dc_pc_v3_t<G_SendGuildCard_PC_6x06>(c, source);
|
||||
} else if ((c->version() == GameVersion::GC) ||
|
||||
(c->version() == GameVersion::XB)) {
|
||||
send_guild_card_dc_pc_v3_t<G_SendGuildCard_V3_6x06>(c, source);
|
||||
} else if (c->version() == GameVersion::BB) {
|
||||
send_guild_card_bb(c, source);
|
||||
} else {
|
||||
throw logic_error("unimplemented versioned command");
|
||||
@@ -825,11 +831,11 @@ void send_menu_t(
|
||||
}
|
||||
|
||||
for (const auto& item : items) {
|
||||
if (((c->version == GameVersion::DC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_DC)) ||
|
||||
((c->version == GameVersion::PC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_PC)) ||
|
||||
((c->version == GameVersion::GC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_GC)) ||
|
||||
((c->version == GameVersion::XB) && (item.flags & MenuItem::Flag::INVISIBLE_ON_XB)) ||
|
||||
((c->version == GameVersion::BB) && (item.flags & MenuItem::Flag::INVISIBLE_ON_BB)) ||
|
||||
if (((c->version() == GameVersion::DC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_DC)) ||
|
||||
((c->version() == GameVersion::PC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_PC)) ||
|
||||
((c->version() == GameVersion::GC) && (item.flags & MenuItem::Flag::INVISIBLE_ON_GC)) ||
|
||||
((c->version() == GameVersion::XB) && (item.flags & MenuItem::Flag::INVISIBLE_ON_XB)) ||
|
||||
((c->version() == GameVersion::BB) && (item.flags & MenuItem::Flag::INVISIBLE_ON_BB)) ||
|
||||
((item.flags & MenuItem::Flag::REQUIRES_MESSAGE_BOXES) && (c->flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION)) ||
|
||||
((item.flags & MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL) && (c->flags & Client::Flag::DOES_NOT_SUPPORT_SEND_FUNCTION_CALL)) ||
|
||||
((item.flags & MenuItem::Flag::REQUIRES_SAVE_DISABLED) && (c->flags & Client::Flag::SAVE_ENABLED))) {
|
||||
@@ -838,7 +844,7 @@ void send_menu_t(
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = menu_id;
|
||||
e.item_id = item.item_id;
|
||||
e.flags = (c->version == GameVersion::BB) ? 0x0004 : 0x0F04;
|
||||
e.flags = (c->version() == GameVersion::BB) ? 0x0004 : 0x0F04;
|
||||
e.text = item.name;
|
||||
}
|
||||
|
||||
@@ -847,8 +853,9 @@ void send_menu_t(
|
||||
|
||||
void send_menu(shared_ptr<Client> c, const u16string& menu_name,
|
||||
uint32_t menu_id, const vector<MenuItem>& items, bool is_info_menu) {
|
||||
if (c->version == GameVersion::PC || c->version == GameVersion::PATCH ||
|
||||
c->version == GameVersion::BB) {
|
||||
if (c->version() == GameVersion::PC ||
|
||||
c->version() == GameVersion::PATCH ||
|
||||
c->version() == GameVersion::BB) {
|
||||
send_menu_t<S_MenuEntry_PC_BB_07_1F>(c, menu_name, menu_id, items, is_info_menu);
|
||||
} else {
|
||||
send_menu_t<S_MenuEntry_DC_V3_07_1F>(c, menu_name, menu_id, items, is_info_menu);
|
||||
@@ -872,7 +879,7 @@ void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
||||
e.flags = 0x04;
|
||||
}
|
||||
for (shared_ptr<Lobby> l : s->all_lobbies()) {
|
||||
if (!l->is_game() || (l->version != c->version)) {
|
||||
if (!l->is_game() || (l->version != c->version())) {
|
||||
continue;
|
||||
}
|
||||
bool l_is_ep3 = !!(l->flags & Lobby::Flag::EPISODE_3_ONLY);
|
||||
@@ -880,17 +887,33 @@ void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
||||
if (l_is_ep3 != c_is_ep3) {
|
||||
continue;
|
||||
}
|
||||
if ((c->flags & Client::Flag::DCV1) && (l->flags & Lobby::Flag::DC_V2_ONLY)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = MenuID::GAME;
|
||||
e.game_id = l->lobby_id;
|
||||
e.difficulty_tag = (l_is_ep3 ? 0x0A : (l->difficulty + 0x22));
|
||||
e.num_players = l->count_clients();
|
||||
e.episode = ((c->version == GameVersion::BB) ? (l->max_clients << 4) : 0) | l->episode;
|
||||
if (c->version() == GameVersion::DC) {
|
||||
e.episode = (l->flags & Lobby::Flag::DC_V2_ONLY) ? 1 : 0;
|
||||
} else {
|
||||
e.episode = ((c->version() == GameVersion::BB) ? (l->max_clients << 4) : 0) | l->episode;
|
||||
}
|
||||
if (l->flags & Lobby::Flag::EPISODE_3_ONLY) {
|
||||
e.flags = (l->password.empty() ? 0 : 2);
|
||||
} else {
|
||||
e.flags = ((l->episode << 6) | ((l->mode % 3) << 4) | (l->password.empty() ? 0 : 2)) | ((l->mode == 3) ? 4 : 0);
|
||||
e.flags = ((l->episode << 6) | (l->password.empty() ? 0 : 2));
|
||||
if (l->flags & Lobby::Flag::BATTLE_MODE) {
|
||||
e.flags |= 0x10;
|
||||
}
|
||||
if (l->flags & Lobby::Flag::CHALLENGE_MODE) {
|
||||
e.flags |= 0x20;
|
||||
}
|
||||
if (l->flags & Lobby::Flag::SOLO_MODE) {
|
||||
e.flags |= 0x34;
|
||||
}
|
||||
}
|
||||
e.name = l->name;
|
||||
}
|
||||
@@ -899,9 +922,9 @@ void send_game_menu_t(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
||||
}
|
||||
|
||||
void send_game_menu(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
||||
if ((c->version == GameVersion::DC) ||
|
||||
(c->version == GameVersion::GC) ||
|
||||
(c->version == GameVersion::XB)) {
|
||||
if ((c->version() == GameVersion::DC) ||
|
||||
(c->version() == GameVersion::GC) ||
|
||||
(c->version() == GameVersion::XB)) {
|
||||
send_game_menu_t<char>(c, s);
|
||||
} else {
|
||||
send_game_menu_t<char16_t>(c, s);
|
||||
@@ -948,31 +971,43 @@ void send_quest_menu_t(
|
||||
|
||||
void send_quest_menu(shared_ptr<Client> c, uint32_t menu_id,
|
||||
const vector<shared_ptr<const Quest>>& quests, bool is_download_menu) {
|
||||
if (c->version == GameVersion::PC) {
|
||||
send_quest_menu_t<S_QuestMenuEntry_PC_A2_A4>(c, menu_id, quests, is_download_menu);
|
||||
} else if (c->version == GameVersion::GC) {
|
||||
send_quest_menu_t<S_QuestMenuEntry_GC_A2_A4>(c, menu_id, quests, is_download_menu);
|
||||
} else if (c->version == GameVersion::XB) {
|
||||
send_quest_menu_t<S_QuestMenuEntry_XB_A2_A4>(c, menu_id, quests, is_download_menu);
|
||||
} else if (c->version == GameVersion::BB) {
|
||||
send_quest_menu_t<S_QuestMenuEntry_BB_A2_A4>(c, menu_id, quests, is_download_menu);
|
||||
} else {
|
||||
throw logic_error("unimplemented versioned command");
|
||||
switch (c->version()) {
|
||||
case GameVersion::PC:
|
||||
send_quest_menu_t<S_QuestMenuEntry_PC_A2_A4>(c, menu_id, quests, is_download_menu);
|
||||
break;
|
||||
case GameVersion::DC:
|
||||
case GameVersion::GC:
|
||||
send_quest_menu_t<S_QuestMenuEntry_DC_GC_A2_A4>(c, menu_id, quests, is_download_menu);
|
||||
break;
|
||||
case GameVersion::XB:
|
||||
send_quest_menu_t<S_QuestMenuEntry_XB_A2_A4>(c, menu_id, quests, is_download_menu);
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
send_quest_menu_t<S_QuestMenuEntry_BB_A2_A4>(c, menu_id, quests, is_download_menu);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unimplemented versioned command");
|
||||
}
|
||||
}
|
||||
|
||||
void send_quest_menu(shared_ptr<Client> c, uint32_t menu_id,
|
||||
const vector<MenuItem>& items, bool is_download_menu) {
|
||||
if (c->version == GameVersion::PC) {
|
||||
send_quest_menu_t<S_QuestMenuEntry_PC_A2_A4>(c, menu_id, items, is_download_menu);
|
||||
} else if (c->version == GameVersion::GC) {
|
||||
send_quest_menu_t<S_QuestMenuEntry_GC_A2_A4>(c, menu_id, items, is_download_menu);
|
||||
} else if (c->version == GameVersion::XB) {
|
||||
send_quest_menu_t<S_QuestMenuEntry_XB_A2_A4>(c, menu_id, items, is_download_menu);
|
||||
} else if (c->version == GameVersion::BB) {
|
||||
send_quest_menu_t<S_QuestMenuEntry_BB_A2_A4>(c, menu_id, items, is_download_menu);
|
||||
} else {
|
||||
throw logic_error("unimplemented versioned command");
|
||||
switch (c->version()) {
|
||||
case GameVersion::PC:
|
||||
send_quest_menu_t<S_QuestMenuEntry_PC_A2_A4>(c, menu_id, items, is_download_menu);
|
||||
break;
|
||||
case GameVersion::DC:
|
||||
case GameVersion::GC:
|
||||
send_quest_menu_t<S_QuestMenuEntry_DC_GC_A2_A4>(c, menu_id, items, is_download_menu);
|
||||
break;
|
||||
case GameVersion::XB:
|
||||
send_quest_menu_t<S_QuestMenuEntry_XB_A2_A4>(c, menu_id, items, is_download_menu);
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
send_quest_menu_t<S_QuestMenuEntry_BB_A2_A4>(c, menu_id, items, is_download_menu);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unimplemented versioned command");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1030,7 +1065,7 @@ void send_join_game_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
cmd->lobby_data[x].name = l->clients[x]->game_data.player()->disp.name;
|
||||
if (cmd_ep3) {
|
||||
cmd_ep3->players_ep3[x].inventory = l->clients[x]->game_data.player()->inventory;
|
||||
cmd_ep3->players_ep3[x].disp = convert_player_disp_data<PlayerDispDataPCV3>(
|
||||
cmd_ep3->players_ep3[x].disp = convert_player_disp_data<PlayerDispDataDCPCV3>(
|
||||
l->clients[x]->game_data.player()->disp);
|
||||
}
|
||||
player_count++;
|
||||
@@ -1043,14 +1078,14 @@ void send_join_game_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
cmd->leader_id = l->leader_id;
|
||||
cmd->disable_udp = 0x01; // Unused on PC/XB/BB
|
||||
cmd->difficulty = l->difficulty;
|
||||
cmd->battle_mode = (l->mode == 1) ? 1 : 0;
|
||||
cmd->battle_mode = (l->flags & Lobby::Flag::BATTLE_MODE) ? 1 : 0;
|
||||
cmd->event = l->event;
|
||||
cmd->section_id = l->section_id;
|
||||
cmd->challenge_mode = (l->mode == 2) ? 1 : 0;
|
||||
cmd->challenge_mode = (l->flags & Lobby::Flag::CHALLENGE_MODE) ? 1 : 0;
|
||||
cmd->rare_seed = l->random_seed;
|
||||
cmd->episode = l->episode;
|
||||
cmd->unused2 = 0x01;
|
||||
cmd->solo_mode = (l->mode == 3);
|
||||
cmd->solo_mode = (l->flags & Lobby::Flag::SOLO_MODE) ? 1 : 0;
|
||||
cmd->unused3 = 0x00;
|
||||
|
||||
send_command(c, 0x64, player_count, data);
|
||||
@@ -1073,7 +1108,7 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
||||
uint8_t lobby_type = (l->type > 14) ? (l->block - 1) : l->type;
|
||||
// Allow non-canonical lobby types on GC. They may work on other versions too,
|
||||
// but I haven't verified which values don't crash on each version.
|
||||
if (c->version == GameVersion::GC) {
|
||||
if (c->version() == GameVersion::GC) {
|
||||
if (c->flags & Client::Flag::EPISODE_3) {
|
||||
if ((l->type > 0x14) && (l->type < 0xE9)) {
|
||||
lobby_type = l->block - 1;
|
||||
@@ -1098,6 +1133,7 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
||||
cmd.unknown_a1 = 0;
|
||||
cmd.event = l->event;
|
||||
cmd.unknown_a2 = 0;
|
||||
cmd.unused = 0;
|
||||
|
||||
vector<shared_ptr<Client>> lobby_clients;
|
||||
if (joining_client) {
|
||||
@@ -1119,8 +1155,8 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
||||
e.lobby_data.name = lc->game_data.player()->disp.name;
|
||||
e.inventory = lc->game_data.player()->inventory;
|
||||
e.disp = convert_player_disp_data<DispDataT>(lc->game_data.player()->disp);
|
||||
if (c->version == GameVersion::PC) {
|
||||
e.disp.enforce_pc_limits();
|
||||
if ((c->version() == GameVersion::PC) || (c->version() == GameVersion::DC)) {
|
||||
e.disp.enforce_v2_limits();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1129,28 +1165,40 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
||||
|
||||
void send_join_lobby(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
if (l->is_game()) {
|
||||
if (c->version == GameVersion::PC) {
|
||||
send_join_game_t<PlayerLobbyDataPC, PlayerDispDataPCV3>(c, l);
|
||||
} else if (c->version == GameVersion::GC) {
|
||||
send_join_game_t<PlayerLobbyDataGC, PlayerDispDataPCV3>(c, l);
|
||||
} else if (c->version == GameVersion::XB) {
|
||||
send_join_game_t<PlayerLobbyDataXB, PlayerDispDataPCV3>(c, l);
|
||||
} else if (c->version == GameVersion::BB) {
|
||||
send_join_game_t<PlayerLobbyDataBB, PlayerDispDataBB>(c, l);
|
||||
} else {
|
||||
throw logic_error("unimplemented versioned command");
|
||||
switch (c->version()) {
|
||||
case GameVersion::PC:
|
||||
send_join_game_t<PlayerLobbyDataPC, PlayerDispDataDCPCV3>(c, l);
|
||||
break;
|
||||
case GameVersion::DC:
|
||||
case GameVersion::GC:
|
||||
send_join_game_t<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3>(c, l);
|
||||
break;
|
||||
case GameVersion::XB:
|
||||
send_join_game_t<PlayerLobbyDataXB, PlayerDispDataDCPCV3>(c, l);
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
send_join_game_t<PlayerLobbyDataBB, PlayerDispDataBB>(c, l);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unimplemented versioned command");
|
||||
}
|
||||
} else {
|
||||
if (c->version == GameVersion::PC) {
|
||||
send_join_lobby_t<PlayerLobbyDataPC, PlayerDispDataPCV3>(c, l);
|
||||
} else if (c->version == GameVersion::GC) {
|
||||
send_join_lobby_t<PlayerLobbyDataGC, PlayerDispDataPCV3>(c, l);
|
||||
} else if (c->version == GameVersion::XB) {
|
||||
send_join_lobby_t<PlayerLobbyDataXB, PlayerDispDataPCV3>(c, l);
|
||||
} else if (c->version == GameVersion::BB) {
|
||||
send_join_lobby_t<PlayerLobbyDataBB, PlayerDispDataBB>(c, l);
|
||||
} else {
|
||||
throw logic_error("unimplemented versioned command");
|
||||
switch (c->version()) {
|
||||
case GameVersion::PC:
|
||||
send_join_lobby_t<PlayerLobbyDataPC, PlayerDispDataDCPCV3>(c, l);
|
||||
break;
|
||||
case GameVersion::DC:
|
||||
case GameVersion::GC:
|
||||
send_join_lobby_t<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3>(c, l);
|
||||
break;
|
||||
case GameVersion::XB:
|
||||
send_join_lobby_t<PlayerLobbyDataXB, PlayerDispDataDCPCV3>(c, l);
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
send_join_lobby_t<PlayerLobbyDataBB, PlayerDispDataBB>(c, l);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unimplemented versioned command");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1165,16 +1213,22 @@ void send_join_lobby(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
|
||||
void send_player_join_notification(shared_ptr<Client> c,
|
||||
shared_ptr<Lobby> l, shared_ptr<Client> joining_client) {
|
||||
if (c->version == GameVersion::PC) {
|
||||
send_join_lobby_t<PlayerLobbyDataPC, PlayerDispDataPCV3>(c, l, joining_client);
|
||||
} else if (c->version == GameVersion::GC) {
|
||||
send_join_lobby_t<PlayerLobbyDataGC, PlayerDispDataPCV3>(c, l, joining_client);
|
||||
} else if (c->version == GameVersion::XB) {
|
||||
send_join_lobby_t<PlayerLobbyDataXB, PlayerDispDataPCV3>(c, l, joining_client);
|
||||
} else if (c->version == GameVersion::BB) {
|
||||
send_join_lobby_t<PlayerLobbyDataBB, PlayerDispDataBB>(c, l, joining_client);
|
||||
} else {
|
||||
throw logic_error("unimplemented versioned command");
|
||||
switch (c->version()) {
|
||||
case GameVersion::PC:
|
||||
send_join_lobby_t<PlayerLobbyDataPC, PlayerDispDataDCPCV3>(c, l, joining_client);
|
||||
break;
|
||||
case GameVersion::DC:
|
||||
case GameVersion::GC:
|
||||
send_join_lobby_t<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3>(c, l, joining_client);
|
||||
break;
|
||||
case GameVersion::XB:
|
||||
send_join_lobby_t<PlayerLobbyDataXB, PlayerDispDataDCPCV3>(c, l, joining_client);
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
send_join_lobby_t<PlayerLobbyDataBB, PlayerDispDataBB>(c, l, joining_client);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unimplemented versioned command");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1229,7 +1283,12 @@ void send_arrow_update(shared_ptr<Lobby> l) {
|
||||
e.arrow_color = l->clients[x]->lobby_arrow_color;
|
||||
}
|
||||
|
||||
send_command_vt(l, 0x88, entries.size(), entries);
|
||||
for (size_t x = 0; x < l->max_clients; x++) {
|
||||
if (!l->clients[x] || (l->clients[x]->flags & Client::Flag::DCV1)) {
|
||||
continue;
|
||||
}
|
||||
send_command_vt(l->clients[x], 0x88, entries.size(), entries);
|
||||
}
|
||||
}
|
||||
|
||||
// tells the player that the joining player is done joining, and the game can resume
|
||||
@@ -1330,15 +1389,15 @@ void send_revive_player(shared_ptr<Lobby> l, shared_ptr<Client> c) {
|
||||
|
||||
void send_drop_item(Channel& ch, const ItemData& item,
|
||||
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id) {
|
||||
G_DropItem_6x5F cmd = {
|
||||
0x5F, 0x0B, 0x0000, area, from_enemy, request_id, x, z, 0, item, 0};
|
||||
G_DropItem_PC_V3_BB_6x5F cmd = {
|
||||
{0x5F, 0x0B, 0x0000, area, from_enemy, request_id, x, z, 0, item}, 0};
|
||||
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
void send_drop_item(shared_ptr<Lobby> l, const ItemData& item,
|
||||
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id) {
|
||||
G_DropItem_6x5F cmd = {
|
||||
0x5F, 0x0B, 0x0000, area, from_enemy, request_id, x, z, 0, item, 0};
|
||||
G_DropItem_PC_V3_BB_6x5F cmd = {
|
||||
{0x5F, 0x0B, 0x0000, area, from_enemy, request_id, x, z, 0, item}, 0};
|
||||
send_command_t(l, 0x60, 0x00, cmd);
|
||||
}
|
||||
|
||||
@@ -1348,8 +1407,8 @@ void send_drop_stacked_item(shared_ptr<Lobby> l, const ItemData& item,
|
||||
uint8_t area, float x, float z) {
|
||||
// TODO: Is this order correct? The original code sent {item, 0}, but it seems
|
||||
// GC sends {0, item} (the last two fields in the struct are switched).
|
||||
G_DropStackedItem_6x5D cmd = {
|
||||
0x5D, 0x0A, 0x00, 0x00, area, 0, x, z, item, 0};
|
||||
G_DropStackedItem_PC_V3_BB_6x5D cmd = {
|
||||
{0x5D, 0x0A, 0x00, 0x00, area, 0, x, z, item}, 0};
|
||||
send_command_t(l, 0x60, 0x00, cmd);
|
||||
}
|
||||
|
||||
@@ -1576,16 +1635,23 @@ void send_quest_file_chunk(
|
||||
void send_quest_file(shared_ptr<Client> c, const string& quest_name,
|
||||
const string& basename, const string& contents, QuestFileType type) {
|
||||
|
||||
if ((c->version == GameVersion::PC) ||
|
||||
(c->version == GameVersion::GC) ||
|
||||
(c->version == GameVersion::XB)) {
|
||||
send_quest_open_file_t<S_OpenFile_PC_V3_44_A6>(
|
||||
switch (c->version()) {
|
||||
case GameVersion::DC:
|
||||
send_quest_open_file_t<S_OpenFile_DC_44_A6>(
|
||||
c, quest_name, basename, contents.size(), type);
|
||||
break;
|
||||
case GameVersion::PC:
|
||||
case GameVersion::GC:
|
||||
case GameVersion::XB:
|
||||
send_quest_open_file_t<S_OpenFile_PC_V3_44_A6>(
|
||||
c, quest_name, basename, contents.size(), type);
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
send_quest_open_file_t<S_OpenFile_BB_44_A6>(
|
||||
c, quest_name, basename, contents.size(), type);
|
||||
} else if (c->version == GameVersion::BB) {
|
||||
send_quest_open_file_t<S_OpenFile_BB_44_A6>(
|
||||
c, quest_name, basename, contents.size(), type);
|
||||
} else {
|
||||
throw invalid_argument("cannot send quest files to this version of client");
|
||||
break;
|
||||
default:
|
||||
throw logic_error("cannot send quest files to this version of client");
|
||||
}
|
||||
|
||||
for (size_t offset = 0; offset < contents.size(); offset += 0x400) {
|
||||
@@ -1618,8 +1684,8 @@ void send_server_time(shared_ptr<Client> c) {
|
||||
|
||||
void send_change_event(shared_ptr<Client> c, uint8_t new_event) {
|
||||
// This command isn't supported on versions before V3, nor on Trial Edition.
|
||||
if ((c->version == GameVersion::DC) ||
|
||||
(c->version == GameVersion::PC) ||
|
||||
if ((c->version() == GameVersion::DC) ||
|
||||
(c->version() == GameVersion::PC) ||
|
||||
(c->flags & Client::Flag::GC_TRIAL_EDITION)) {
|
||||
return;
|
||||
}
|
||||
|
||||
+2
-2
@@ -69,8 +69,8 @@ Server commands:\n\
|
||||
bb-username=<username> (BB username)\n\
|
||||
bb-password=<password> (BB password)\n\
|
||||
gc-password=<password> (GC password)\n\
|
||||
access-key=<access-key> (GC/PC access key)\n\
|
||||
serial=<serial-number> (GC/PC serial number; required for all licenses)\n\
|
||||
access-key=<access-key> (DC/GC/PC access key)\n\
|
||||
serial=<serial-number> (decimal serial number; required for all licenses)\n\
|
||||
privileges=<privilege-mask> (can be normal, mod, admin, root, or numeric)\n\
|
||||
delete-license <serial-number>\n\
|
||||
Delete a license from the server.\n\
|
||||
|
||||
+39
-31
@@ -216,8 +216,8 @@ uint32_t ServerState::connect_address_for_client(std::shared_ptr<Client> c) {
|
||||
|
||||
|
||||
shared_ptr<const vector<MenuItem>> ServerState::information_menu_for_version(GameVersion version) {
|
||||
if (version == GameVersion::PC) {
|
||||
return this->information_menu_pc;
|
||||
if ((version == GameVersion::DC) || (version == GameVersion::PC)) {
|
||||
return this->information_menu_v2;
|
||||
} else if ((version == GameVersion::GC) || (version == GameVersion::XB)) {
|
||||
return this->information_menu_v3;
|
||||
}
|
||||
@@ -225,25 +225,33 @@ shared_ptr<const vector<MenuItem>> ServerState::information_menu_for_version(Gam
|
||||
}
|
||||
|
||||
const vector<MenuItem>& ServerState::proxy_destinations_menu_for_version(GameVersion version) {
|
||||
if (version == GameVersion::PC) {
|
||||
return this->proxy_destinations_menu_pc;
|
||||
} else if (version == GameVersion::GC) {
|
||||
return this->proxy_destinations_menu_gc;
|
||||
} else if (version == GameVersion::XB) {
|
||||
return this->proxy_destinations_menu_xb;
|
||||
switch (version) {
|
||||
case GameVersion::DC:
|
||||
return this->proxy_destinations_menu_dc;
|
||||
case GameVersion::PC:
|
||||
return this->proxy_destinations_menu_pc;
|
||||
case GameVersion::GC:
|
||||
return this->proxy_destinations_menu_gc;
|
||||
case GameVersion::XB:
|
||||
return this->proxy_destinations_menu_xb;
|
||||
default:
|
||||
throw out_of_range("no proxy destinations menu exists for this version");
|
||||
}
|
||||
throw out_of_range("no proxy destinations menu exists for this version");
|
||||
}
|
||||
|
||||
const vector<pair<string, uint16_t>>& ServerState::proxy_destinations_for_version(GameVersion version) {
|
||||
if (version == GameVersion::PC) {
|
||||
return this->proxy_destinations_pc;
|
||||
} else if (version == GameVersion::GC) {
|
||||
return this->proxy_destinations_gc;
|
||||
} else if (version == GameVersion::XB) {
|
||||
return this->proxy_destinations_xb;
|
||||
switch (version) {
|
||||
case GameVersion::DC:
|
||||
return this->proxy_destinations_dc;
|
||||
case GameVersion::PC:
|
||||
return this->proxy_destinations_pc;
|
||||
case GameVersion::GC:
|
||||
return this->proxy_destinations_gc;
|
||||
case GameVersion::XB:
|
||||
return this->proxy_destinations_xb;
|
||||
default:
|
||||
throw out_of_range("no proxy destinations menu exists for this version");
|
||||
}
|
||||
throw out_of_range("no proxy destinations menu exists for this version");
|
||||
}
|
||||
|
||||
|
||||
@@ -268,7 +276,7 @@ void ServerState::set_port_configuration(
|
||||
void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
const auto& d = config_json->as_dict();
|
||||
|
||||
shared_ptr<vector<MenuItem>> information_menu_pc(new vector<MenuItem>());
|
||||
shared_ptr<vector<MenuItem>> information_menu_v2(new vector<MenuItem>());
|
||||
shared_ptr<vector<MenuItem>> information_menu_v3(new vector<MenuItem>());
|
||||
shared_ptr<vector<u16string>> information_contents(new vector<u16string>());
|
||||
|
||||
@@ -278,7 +286,7 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
uint32_t item_id = 0;
|
||||
for (const auto& item : d.at("InformationMenuContents")->as_list()) {
|
||||
auto& v = item->as_list();
|
||||
information_menu_pc->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
|
||||
information_menu_v2->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
|
||||
decode_sjis(v.at(1)->as_string()), 0);
|
||||
information_menu_v3->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
|
||||
decode_sjis(v.at(1)->as_string()), MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
|
||||
@@ -286,7 +294,7 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
item_id++;
|
||||
}
|
||||
}
|
||||
this->information_menu_pc = information_menu_pc;
|
||||
this->information_menu_v2 = information_menu_v2;
|
||||
this->information_menu_v3 = information_menu_v3;
|
||||
this->information_contents = information_contents;
|
||||
|
||||
@@ -314,6 +322,10 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
} catch (const out_of_range&) { }
|
||||
};
|
||||
|
||||
generate_proxy_destinations_menu(
|
||||
this->proxy_destinations_menu_dc,
|
||||
this->proxy_destinations_dc,
|
||||
"ProxyDestinations-DC");
|
||||
generate_proxy_destinations_menu(
|
||||
this->proxy_destinations_menu_pc,
|
||||
this->proxy_destinations_pc,
|
||||
@@ -358,18 +370,14 @@ void ServerState::create_menus(shared_ptr<const JSONObject> config_json) {
|
||||
u"Join the lobby", 0);
|
||||
this->main_menu.emplace_back(MainMenuItemID::INFORMATION, u"Information",
|
||||
u"View server\ninformation", MenuItem::Flag::REQUIRES_MESSAGE_BOXES);
|
||||
if (!this->proxy_destinations_pc.empty()) {
|
||||
this->main_menu.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server",
|
||||
u"Connect to another\nserver", MenuItem::Flag::PC_ONLY);
|
||||
}
|
||||
if (!this->proxy_destinations_gc.empty()) {
|
||||
this->main_menu.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server",
|
||||
u"Connect to another\nserver", MenuItem::Flag::GC_ONLY);
|
||||
}
|
||||
if (!this->proxy_destinations_xb.empty()) {
|
||||
this->main_menu.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server",
|
||||
u"Connect to another\nserver", MenuItem::Flag::XB_ONLY);
|
||||
}
|
||||
uint32_t proxy_destinations_menu_item_flags =
|
||||
(this->proxy_destinations_dc.empty() ? MenuItem::Flag::INVISIBLE_ON_DC : 0) |
|
||||
(this->proxy_destinations_pc.empty() ? MenuItem::Flag::INVISIBLE_ON_PC : 0) |
|
||||
(this->proxy_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) |
|
||||
(this->proxy_destinations_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) |
|
||||
MenuItem::Flag::INVISIBLE_ON_BB;
|
||||
this->main_menu.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server",
|
||||
u"Connect to another\nserver", proxy_destinations_menu_item_flags);
|
||||
this->main_menu.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, u"Download quests",
|
||||
u"Download quests", MenuItem::Flag::INVISIBLE_ON_BB);
|
||||
if (!this->function_code_index->patch_menu_empty()) {
|
||||
|
||||
+3
-1
@@ -63,12 +63,14 @@ struct ServerState {
|
||||
std::shared_ptr<LicenseManager> license_manager;
|
||||
|
||||
std::vector<MenuItem> main_menu;
|
||||
std::shared_ptr<std::vector<MenuItem>> information_menu_pc;
|
||||
std::shared_ptr<std::vector<MenuItem>> information_menu_v2;
|
||||
std::shared_ptr<std::vector<MenuItem>> information_menu_v3;
|
||||
std::shared_ptr<std::vector<std::u16string>> information_contents;
|
||||
std::vector<MenuItem> proxy_destinations_menu_dc;
|
||||
std::vector<MenuItem> proxy_destinations_menu_pc;
|
||||
std::vector<MenuItem> proxy_destinations_menu_gc;
|
||||
std::vector<MenuItem> proxy_destinations_menu_xb;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_dc;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_pc;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_gc;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_xb;
|
||||
|
||||
@@ -533,22 +533,6 @@ char abbreviation_for_difficulty(uint8_t difficulty) {
|
||||
|
||||
|
||||
|
||||
const char* abbreviation_for_game_mode(uint8_t mode) {
|
||||
static const array<const char*, 4> names = {
|
||||
"Nml",
|
||||
"Btl",
|
||||
"Chl",
|
||||
"Solo",
|
||||
};
|
||||
try {
|
||||
return names.at(mode);
|
||||
} catch (const out_of_range&) {
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
size_t stack_size_for_item(uint8_t data0, uint8_t data1) {
|
||||
if (data0 == 4) {
|
||||
return 999999;
|
||||
|
||||
@@ -57,6 +57,4 @@ const char* abbreviation_for_char_class(uint8_t cls);
|
||||
const char* name_for_difficulty(uint8_t difficulty);
|
||||
char abbreviation_for_difficulty(uint8_t difficulty);
|
||||
|
||||
const char* abbreviation_for_game_mode(uint8_t);
|
||||
|
||||
std::string name_for_item(const ItemData& item, bool include_color_codes);
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ using namespace std;
|
||||
|
||||
uint16_t flags_for_version(GameVersion version, int64_t sub_version) {
|
||||
switch (sub_version) {
|
||||
case -1: // Initial check (before 9E recognition)
|
||||
case -1: // Initial check (before sub_version recognition)
|
||||
switch (version) {
|
||||
case GameVersion::DC:
|
||||
return Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION;
|
||||
|
||||
@@ -67,9 +67,11 @@
|
||||
"console-lobby": [5110, "gc", "lobby_server"],
|
||||
"pc-lobby": [5111, "pc", "lobby_server"],
|
||||
"bb-lobby": [5112, "bb", "lobby_server"],
|
||||
"console-proxy": [5120, "gc", "proxy_server"],
|
||||
"dc-proxy": [5120, "dc", "proxy_server"],
|
||||
"pc-proxy": [5121, "pc", "proxy_server"],
|
||||
"bb-proxy": [5122, "bb", "proxy_server"],
|
||||
"gc-proxy": [5122, "gc", "proxy_server"],
|
||||
"xb-proxy": [5123, "xb", "proxy_server"],
|
||||
"bb-proxy": [5124, "bb", "proxy_server"],
|
||||
"bb-data1": [12004, "bb", "data_server_bb"],
|
||||
"bb-data2": [12005, "bb", "data_server_bb"],
|
||||
},
|
||||
@@ -84,9 +86,10 @@
|
||||
// strings to this list to listen for tapserver connections on a TCP port.
|
||||
"IPStackListen": [],
|
||||
|
||||
// Other servers to support proxying to. If either of these is empty, the
|
||||
// proxy server is disabled for that game version. Entries are like
|
||||
// Other servers to support proxying to. If this is empty for any game
|
||||
// version, the proxy server is disabled for that version. Entries are like
|
||||
// "name": "address:port"; the names are used in the proxy server menu.
|
||||
"ProxyDestinations-DC": {},
|
||||
"ProxyDestinations-PC": {},
|
||||
"ProxyDestinations-GC": {},
|
||||
"ProxyDestinations-XB": {},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user