add support for PSO DC v1/v2

This commit is contained in:
Martin Michelsen
2022-08-27 01:34:49 -07:00
parent 4abd91cb8f
commit 5d3d1e1900
30 changed files with 4004 additions and 585 deletions
+12 -11
View File
@@ -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
+9
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
-1
View File
@@ -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
View File
@@ -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
+6 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
-16
View File
@@ -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;
-2
View File
@@ -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
View File
@@ -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;
+7 -4
View File
@@ -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