add xbox support

This commit is contained in:
Martin Michelsen
2023-11-06 23:06:16 -08:00
parent 4b1f5420f2
commit 71cfced5ee
337 changed files with 2315 additions and 403 deletions
+3 -5
View File
@@ -383,9 +383,7 @@ static void server_command_exit(shared_ptr<Client> c, const std::string&) {
send_message_box(c, "");
}
const auto& port_name = version_to_login_port_name.at(static_cast<size_t>(c->version()));
auto s = c->require_server_state();
send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at(port_name)->port);
send_client_to_login_server(c);
}
}
@@ -431,7 +429,7 @@ static void proxy_command_get_player_card(shared_ptr<ProxyServer::LinkedSession>
bool any_card_sent = false;
for (const auto& p : ses->lobby_players) {
if (!p.name.empty() && args == p.name) {
send_guild_card(ses->client_channel, p.guild_card_number, p.name, "", "", p.language, p.section_id, p.char_class);
send_guild_card(ses->client_channel, p.guild_card_number, p.guild_card_number, p.name, "", "", p.language, p.section_id, p.char_class);
any_card_sent = true;
}
}
@@ -441,7 +439,7 @@ static void proxy_command_get_player_card(shared_ptr<ProxyServer::LinkedSession>
size_t index = stoull(args, nullptr, 0);
const auto& p = ses->lobby_players.at(index);
if (!p.name.empty()) {
send_guild_card(ses->client_channel, p.guild_card_number, p.name, "", "", p.language, p.section_id, p.char_class);
send_guild_card(ses->client_channel, p.guild_card_number, p.guild_card_number, p.name, "", "", p.language, p.section_id, p.char_class);
}
} catch (const exception& e) {
send_text_message_printf(ses->client_channel, "Error: %s", e.what());
+10 -1
View File
@@ -35,6 +35,8 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers
case GameVersion::GC:
break;
case GameVersion::XB:
// TODO: Do all versions of XB need this flag? US does, at least.
this->set_flag(Flag::NO_D6_AFTER_LOBBY);
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
break;
case GameVersion::PC:
@@ -81,7 +83,7 @@ void Client::Config::set_flags_for_version(GameVersion version, int64_t sub_vers
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
break;
case 0x30: // GC Ep1&2 GameJam demo, GC Ep1&2 JP v1.02, at least one version of PSO XB
case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, GC EU v1.00, GC JP v1.00
case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, GC EU v1.00, GC JP v1.00, XB US
case 0x34: // GC Ep1&2 JP v1.03
// In the case of GC Trial Edition, the IS_GC_TRIAL_EDITION flag is
// already set when we get here (because the client has used V2 encryption
@@ -145,6 +147,7 @@ Client::Client(
should_send_to_lobby_server(false),
should_send_to_proxy_server(false),
bb_connection_phase(0xFF),
sub_version(-1),
x(0.0f),
z(0.0f),
area(0),
@@ -307,3 +310,9 @@ void Client::idle_timeout() {
this->log.info("Server is deleted; cannot disconnect client");
}
}
void Client::suspend_timeouts() {
event_del(this->send_ping_event.get());
event_del(this->idle_timeout_event.get());
this->log.info("Timeouts suspended");
}
+5
View File
@@ -163,6 +163,8 @@ struct Client : public std::enable_shared_from_this<Client> {
bool should_send_to_lobby_server;
bool should_send_to_proxy_server;
std::unordered_map<std::string, std::function<void()>> disconnect_hooks;
std::shared_ptr<XBNetworkLocation> xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
uint8_t bb_connection_phase;
// Patch server
@@ -170,6 +172,7 @@ struct Client : public std::enable_shared_from_this<Client> {
// Lobby/positioning
Config config;
int32_t sub_version;
float x;
float z;
uint32_t area;
@@ -229,4 +232,6 @@ struct Client : public std::enable_shared_from_this<Client> {
void send_ping();
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
void idle_timeout();
void suspend_timeouts();
};
+47 -24
View File
@@ -38,9 +38,9 @@
// DC = Both DCv1 and DCv2
// PC = PSO PC (v2)
// GC = PSO GC Episodes 1&2 and/or Episode 3
// XB = PSO XBOX Episodes 1&2
// XB = PSO Xbox Episodes 1&2
// BB = PSO Blue Burst
// V3 = PSO GC and PSO XBOX (these versions are similar and share many formats)
// V3 = PSO GC and PSO Xbox (these versions are similar and share many formats)
// For variable-length commands, generally a zero-length array is included on
// the end of the struct if the command is received by newserv, and is omitted
@@ -988,7 +988,9 @@ struct S_OpenFile_PC_GC_44_A6 {
// those extra bytes are unused, and the client does not fail if they're
// omitted.
struct S_OpenFile_XB_44_A6 : S_OpenFile_PC_GC_44_A6 {
parray<uint8_t, 0x18> unused2;
pstring<TextEncoding::ASCII, 0x10> xb_filename;
le_uint32_t content_meta;
parray<uint8_t, 4> unused2;
} __packed__;
struct S_OpenFile_BB_44_A6 {
@@ -1858,21 +1860,22 @@ struct C_Register_BB_9C {
// by its menu ID and item ID.
struct C_Login_DC_PC_GC_9D {
le_uint32_t player_tag = 0x00010000; // 0x00010000 if guild card is set (via 04)
le_uint32_t guild_card_number = 0; // 0xFFFFFFFF if not set
le_uint32_t unused1 = 0;
le_uint32_t unused2 = 0;
le_uint32_t sub_version = 0;
uint8_t is_extended = 0; // If 1, structure has extended format
uint8_t language = 0; // 0 = JP, 1 = EN, 2 = DE, 3 = FR, 4 = ES
parray<uint8_t, 0x2> unused3; // Always zeroes
pstring<TextEncoding::ASCII, 0x10> v1_serial_number;
pstring<TextEncoding::ASCII, 0x10> v1_access_key;
pstring<TextEncoding::ASCII, 0x10> serial_number; // On XB, this is the XBL gamertag
pstring<TextEncoding::ASCII, 0x10> access_key; // On XB, this is the XBL user ID
pstring<TextEncoding::ASCII, 0x30> serial_number2; // On XB, this is the XBL gamertag
pstring<TextEncoding::ASCII, 0x30> access_key2; // On XB, this is the XBL user ID
pstring<TextEncoding::ASCII, 0x10> name;
/* 04 */ le_uint32_t player_tag = 0x00010000; // 0x00010000 if guild card is set (via 04)
/* 08 */ le_uint32_t guild_card_number = 0; // 0xFFFFFFFF if not set
/* 0C */ le_uint32_t unused1 = 0;
/* 10 */ le_uint32_t unused2 = 0;
/* 14 */ le_uint32_t sub_version = 0;
/* 18 */ uint8_t is_extended = 0; // If 1, structure has extended format
/* 19 */ uint8_t language = 0; // 0 = JP, 1 = EN, 2 = DE, 3 = FR, 4 = ES
/* 1A */ parray<uint8_t, 0x2> unused3; // Always zeroes
/* 1C */ pstring<TextEncoding::ASCII, 0x10> v1_serial_number;
/* 2C */ pstring<TextEncoding::ASCII, 0x10> v1_access_key;
/* 3C */ pstring<TextEncoding::ASCII, 0x10> serial_number; // On XB, this is the XBL gamertag
/* 4C */ pstring<TextEncoding::ASCII, 0x10> access_key; // On XB, this is the XBL user ID
/* 5C */ pstring<TextEncoding::ASCII, 0x30> serial_number2; // On XB, this is the XBL gamertag
/* 8C */ pstring<TextEncoding::ASCII, 0x30> access_key2; // On XB, this is the XBL user ID
/* BC */ pstring<TextEncoding::ASCII, 0x10> name;
/* CC */
} __packed__;
struct C_LoginExtended_DC_GC_9D : C_Login_DC_PC_GC_9D {
SC_MeetUserExtension<TextEncoding::MARKED> extension;
@@ -1896,7 +1899,10 @@ struct C_LoginExtended_GC_9E : C_Login_GC_9E {
struct C_Login_XB_9E : C_Login_GC_9E {
XBNetworkLocation netloc;
parray<le_uint32_t, 6> unknown_a1;
parray<le_uint32_t, 3> unknown_a1a;
le_uint32_t xb_user_id_high = 0;
le_uint32_t xb_user_id_low = 0;
le_uint32_t unknown_a1b = 0;
} __packed__;
struct C_LoginExtended_XB_9E : C_Login_XB_9E {
SC_MeetUserExtension<TextEncoding::MARKED> extension;
@@ -2480,6 +2486,10 @@ struct C_SetBlockedSenders_BB_C6 : C_SetBlockedSenders_C6<28> {
// commands), except for map data requests. This differs from Sega's original
// implementation, which sent CA responses via 60 commands instead.
// C9 (C->S): Change connection status (Xbox)
// header.flag specifies if the player's online status should be hidden; 1 means
// shown, 2 means hidden.
// CA (C->S): Server data request (Episode 3)
// Internal name: SndCardServerData
// The CA command format is the same as that of the 6xB3 commands, and the
@@ -3699,9 +3709,14 @@ struct G_SendGuildCard_PC_6x06 {
GuildCardPC guild_card;
} __packed__;
struct G_SendGuildCard_V3_6x06 {
struct G_SendGuildCard_GC_6x06 {
G_UnusedHeader header;
GuildCardV3 guild_card;
GuildCardGC guild_card;
} __packed__;
struct G_SendGuildCard_XB_6x06 {
G_UnusedHeader header;
GuildCardXB guild_card;
} __packed__;
struct G_SendGuildCard_BB_6x06 {
@@ -4554,7 +4569,7 @@ struct G_SetQuestFlags_6x6F {
// Episode 3 does not send this command at all since the relevant data is sent
// to the joining player in the 64 command instead.
struct G_SyncPlayerDispAndInventory_DC_PC_V3_6x70 {
struct G_SyncPlayerDispAndInventory_DC_PC_GC_6x70 {
// Offsets in this struct are relative to the overall command header
/* 0004 */ G_ExtendedHeader<G_UnusedHeader> header;
/* 000C */ le_uint16_t client_id = 0;
@@ -4589,6 +4604,14 @@ struct G_SyncPlayerDispAndInventory_DC_PC_V3_6x70 {
/* 0498 */
} __packed__;
struct G_SyncPlayerDispAndInventory_XB_6x70 : G_SyncPlayerDispAndInventory_DC_PC_GC_6x70 {
// Offsets in this struct are relative to the overall command header
/* 0498 */ le_uint32_t xb_user_id_high = 0;
/* 049C */ le_uint32_t xb_user_id_low = 0;
/* 04A0 */ le_uint32_t unknown_a16 = 0;
/* 04A4 */
} __packed__;
// 6x71: Unknown (used while loading into game)
struct G_Unknown_6x71 {
@@ -5130,7 +5153,7 @@ struct G_Unknown_6xB2 {
le_uint32_t unknown_a3 = 0; // PSO GC puts 0x00051720 (333600) here
} __packed__;
// 6xB3: Unknown (XBOX; voice chat)
// 6xB3: Unknown (Xbox; voice chat)
// 6xB3: CARD battle server data request (Episode 3)
@@ -5176,7 +5199,7 @@ struct G_CardServerDataCommandHeader {
/* 10 */
} __packed__;
// 6xB4: Unknown (XBOX; voice chat)
// 6xB4: Unknown (Xbox; voice chat)
// 6xB4: CARD battle server response (Episode 3) - see 6xB3 above
// 6xB5: CARD battle client command (Episode 3) - see 6xB3 above
+42 -1
View File
@@ -6,7 +6,6 @@
#include <phosg/Time.hh>
#include "License.hh"
#include "Loggers.hh"
using namespace std;
@@ -19,6 +18,9 @@ License::License(const JSON& json)
this->serial_number = json.get_int("SerialNumber");
this->access_key = json.get_string("AccessKey", "");
this->gc_password = json.get_string("GCPassword", "");
this->xb_gamertag = json.get_string("XBGamerTag", "");
this->xb_user_id = json.get_int("XBUserID", 0);
this->xb_account_id = json.get_int("XBAccountID", 0);
this->bb_username = json.get_string("BBUsername", "");
this->bb_password = json.get_string("BBPassword", "");
this->flags = json.get_int("Flags", 0);
@@ -32,6 +34,9 @@ JSON License::json() const {
{"SerialNumber", this->serial_number},
{"AccessKey", this->access_key},
{"GCPassword", this->gc_password},
{"XBGamerTag", this->xb_gamertag},
{"XBUserID", this->xb_user_id},
{"XBAccountID", this->xb_account_id},
{"BBUsername", this->bb_username},
{"BBPassword", this->bb_password},
{"Flags", this->flags},
@@ -62,6 +67,15 @@ string License::str() const {
if (!this->gc_password.empty()) {
tokens.emplace_back("gc_password=" + this->gc_password);
}
if (!this->xb_gamertag.empty()) {
tokens.emplace_back("xb_gamertag=" + this->xb_gamertag);
}
if (this->xb_user_id != 0) {
tokens.emplace_back(string_printf("xb_user_id=%016" PRIX64, this->xb_user_id));
}
if (this->xb_account_id != 0) {
tokens.emplace_back(string_printf("xb_account_id=%016" PRIX64, this->xb_account_id));
}
if (!this->bb_username.empty()) {
tokens.emplace_back("bb_username=" + this->bb_username);
}
@@ -156,6 +170,9 @@ void LicenseIndex::add(shared_ptr<License> l) {
if (!l->bb_username.empty()) {
this->bb_username_to_license[l->bb_username] = l;
}
if (!l->xb_gamertag.empty()) {
this->xb_gamertag_to_license[l->xb_gamertag] = l;
}
}
void LicenseIndex::remove(uint32_t serial_number) {
@@ -164,6 +181,9 @@ void LicenseIndex::remove(uint32_t serial_number) {
if (!l->bb_username.empty()) {
this->bb_username_to_license.erase(l->bb_username);
}
if (!l->xb_gamertag.empty()) {
this->xb_gamertag_to_license.erase(l->xb_gamertag);
}
}
shared_ptr<License> LicenseIndex::verify_v1_v2(uint32_t serial_number, const string& access_key) const {
@@ -223,6 +243,27 @@ shared_ptr<License> LicenseIndex::verify_gc(uint32_t serial_number, const string
}
}
shared_ptr<License> LicenseIndex::verify_xb(const string& gamertag, uint64_t user_id, uint64_t account_id) const {
if (user_id == 0 || account_id == 0) {
throw incorrect_access_key();
}
try {
auto& license = this->xb_gamertag_to_license.at(gamertag);
if (license->xb_user_id && (license->xb_user_id != user_id)) {
throw incorrect_access_key();
}
if (license->xb_account_id && (license->xb_account_id != account_id)) {
throw incorrect_access_key();
}
if (license->ban_end_time && (license->ban_end_time >= now())) {
throw invalid_argument("user is banned");
}
return license;
} catch (const out_of_range&) {
throw missing_license();
}
}
shared_ptr<License> LicenseIndex::verify_bb(const string& username, const string& password) const {
if (username.empty() || password.empty()) {
throw no_username();
+5
View File
@@ -33,6 +33,9 @@ struct License {
uint32_t serial_number = 0;
std::string access_key;
std::string gc_password;
std::string xb_gamertag;
uint64_t xb_user_id = 0;
uint64_t xb_account_id = 0;
std::string bb_username;
std::string bb_password;
@@ -84,9 +87,11 @@ public:
std::shared_ptr<License> verify_v1_v2(uint32_t serial_number, const std::string& access_key) const;
std::shared_ptr<License> verify_gc(uint32_t serial_number, const std::string& access_key) const;
std::shared_ptr<License> verify_gc(uint32_t serial_number, const std::string& access_key, const std::string& password) const;
std::shared_ptr<License> verify_xb(const std::string& gamertag, uint64_t user_id, uint64_t account_id) const;
std::shared_ptr<License> verify_bb(const std::string& username, const std::string& password) const;
protected:
std::unordered_map<std::string, std::shared_ptr<License>> bb_username_to_license;
std::unordered_map<std::string, std::shared_ptr<License>> xb_gamertag_to_license;
std::unordered_map<uint32_t, std::shared_ptr<License>> serial_number_to_license;
};
+3 -2
View File
@@ -257,9 +257,10 @@ void XBNetworkLocation::clear() {
this->external_ipv4_address = 0;
this->port = 0;
this->mac_address.clear(0);
this->unknown_a1.clear(0);
this->unknown_a1 = 0;
this->unknown_a2 = 0;
this->account_id = 0;
this->unknown_a2.clear(0);
this->unknown_a3.clear(0);
}
void PlayerLobbyDataXB::clear() {
+19 -6
View File
@@ -210,9 +210,7 @@ struct GuildCardPC {
/* F0 */
} __attribute__((packed));
// TODO: Is this the same for XB as it is for GC? (This struct is based on the
// GC format)
struct GuildCardV3 {
struct GuildCardGC {
/* 00 */ le_uint32_t player_tag = 0;
/* 04 */ le_uint32_t guild_card_number = 0;
/* 08 */ pstring<TextEncoding::ASCII, 0x18> name;
@@ -224,6 +222,20 @@ struct GuildCardV3 {
/* 90 */
} __attribute__((packed));
struct GuildCardXB {
/* 0000 */ le_uint32_t player_tag = 0;
/* 0004 */ le_uint32_t guild_card_number = 0;
/* 0008 */ le_uint32_t xb_user_id_high = 0;
/* 000C */ le_uint32_t xb_user_id_low = 0;
/* 0010 */ pstring<TextEncoding::ASCII, 0x18> name;
/* 0028 */ pstring<TextEncoding::MARKED, 0x200> description;
/* 0228 */ uint8_t present = 0;
/* 0229 */ uint8_t language = 0;
/* 022A */ uint8_t section_id = 0;
/* 022B */ uint8_t char_class = 0;
/* 022C */
} __attribute__((packed));
struct GuildCardBB {
/* 0000 */ le_uint32_t guild_card_number = 0;
/* 0004 */ pstring<TextEncoding::UTF16, 0x18> name;
@@ -266,11 +278,12 @@ struct PlayerLobbyDataDCGC {
struct XBNetworkLocation {
le_uint32_t internal_ipv4_address = 0x0A0A0A0A;
le_uint32_t external_ipv4_address = 0x23232323;
le_uint16_t port = 9100;
le_uint16_t port = 9500;
parray<uint8_t, 6> mac_address = 0x77;
parray<le_uint32_t, 2> unknown_a1;
le_uint32_t unknown_a1;
le_uint32_t unknown_a2;
le_uint64_t account_id = 0xFFFFFFFFFFFFFFFF;
parray<le_uint32_t, 4> unknown_a2;
parray<le_uint32_t, 4> unknown_a3;
void clear();
} __attribute__((packed));
+148 -112
View File
@@ -227,12 +227,11 @@ static HandlerResult S_V123P_02_17(
if (!ses->license) {
ses->log.info("No license in linked session");
// We have to forward the command before setting up encryption, so the
// client will be able to understand it.
// We have to forward the command BEFORE setting up encryption, so the
// client will be able to understand what we sent.
forward_command(ses, false, command, flag, data);
if ((ses->version() == GameVersion::GC) ||
(ses->version() == GameVersion::XB)) {
if ((ses->version() == GameVersion::GC) || (ses->version() == GameVersion::XB)) {
ses->server_channel.crypt_in.reset(new PSOV3Encryption(cmd.server_key));
ses->server_channel.crypt_out.reset(new PSOV3Encryption(cmd.client_key));
ses->client_channel.crypt_in.reset(new PSOV3Encryption(cmd.client_key));
@@ -270,130 +269,164 @@ static HandlerResult S_V123P_02_17(
// because it believes it already did (when it was in an unlinked session, or
// in the patch server case, during the current session due to a hidden
// redirect).
if (ses->version() == GameVersion::PATCH) {
ses->server_channel.send(0x02);
return HandlerResult::Type::SUPPRESS;
switch (ses->version()) {
case GameVersion::PATCH:
ses->server_channel.send(0x02);
return HandlerResult::Type::SUPPRESS;
} else if ((ses->version() == GameVersion::DC) || (ses->version() == GameVersion::PC)) {
if (ses->config.check_flag(Client::Flag::IS_DC_V1)) {
if (command == 0x17) {
C_LoginV1_DC_PC_V3_90 cmd;
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
ses->server_channel.send(0x90, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
} else {
C_LoginV1_DC_93 cmd;
if (ses->remote_guild_card_number < 0) {
cmd.player_tag = 0xFFFF0000;
cmd.guild_card_number = 0xFFFFFFFF;
case GameVersion::DC:
case GameVersion::PC:
if (ses->config.check_flag(Client::Flag::IS_DC_V1)) {
if (command == 0x17) {
C_LoginV1_DC_PC_V3_90 cmd;
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
ses->server_channel.send(0x90, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
} else {
cmd.player_tag = 0x00010000;
cmd.guild_card_number = ses->remote_guild_card_number;
C_LoginV1_DC_93 cmd;
if (ses->remote_guild_card_number < 0) {
cmd.player_tag = 0xFFFF0000;
cmd.guild_card_number = 0xFFFFFFFF;
} else {
cmd.player_tag = 0x00010000;
cmd.guild_card_number = ses->remote_guild_card_number;
}
cmd.unknown_a1 = 0;
cmd.unknown_a2 = 0;
cmd.sub_version = ses->sub_version;
cmd.is_extended = 0;
cmd.language = ses->language();
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
cmd.hardware_id.encode(ses->hardware_id);
cmd.name.encode(ses->character_name);
ses->server_channel.send(0x93, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
}
} else { // DCv2 or PC
if (command == 0x17) {
C_Login_DC_PC_V3_9A cmd;
if (ses->remote_guild_card_number < 0) {
cmd.player_tag = 0xFFFF0000;
cmd.guild_card_number = 0xFFFFFFFF;
} else {
cmd.player_tag = 0x00010000;
cmd.guild_card_number = ses->remote_guild_card_number;
}
cmd.sub_version = ses->sub_version;
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
// TODO: We probably should set email_address, but we currently don't
// keep that value anywhere in the session object, nor is it saved in
// the License object.
ses->server_channel.send(0x9A, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
} else {
C_Login_DC_PC_GC_9D cmd;
if (ses->remote_guild_card_number < 0) {
cmd.player_tag = 0xFFFF0000;
cmd.guild_card_number = 0xFFFFFFFF;
} else {
cmd.player_tag = 0x00010000;
cmd.guild_card_number = ses->remote_guild_card_number;
}
cmd.unused1 = 0;
cmd.unused2 = 0;
cmd.sub_version = ses->sub_version;
cmd.is_extended = 0;
cmd.language = ses->language();
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) {
cmd.name.encode(" ", ses->language());
} else {
cmd.name.encode(ses->character_name);
}
ses->server_channel.send(0x9D, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
}
cmd.unknown_a1 = 0;
cmd.unknown_a2 = 0;
cmd.sub_version = ses->sub_version;
cmd.is_extended = 0;
cmd.language = ses->language();
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
cmd.hardware_id.encode(ses->hardware_id);
cmd.name.encode(ses->character_name);
ses->server_channel.send(0x93, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
}
} else { // DCv2 or PC
throw logic_error("DC/PC init command not handled");
case GameVersion::GC:
if (command == 0x17) {
C_Login_DC_PC_V3_9A cmd;
if (ses->remote_guild_card_number < 0) {
cmd.player_tag = 0xFFFF0000;
cmd.guild_card_number = 0xFFFFFFFF;
} else {
cmd.player_tag = 0x00010000;
cmd.guild_card_number = ses->remote_guild_card_number;
}
cmd.sub_version = ses->sub_version;
C_VerifyLicense_V3_DB cmd;
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
cmd.sub_version = ses->sub_version;
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
// TODO: We probably should set email_address, but we currently don't
// keep that value anywhere in the session object, nor is it saved in
// the License object.
ses->server_channel.send(0x9A, 0x00, &cmd, sizeof(cmd));
cmd.password.encode(ses->license->gc_password);
ses->server_channel.send(0xDB, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
} else {
C_Login_DC_PC_GC_9D cmd;
if (ses->remote_guild_card_number < 0) {
cmd.player_tag = 0xFFFF0000;
cmd.guild_card_number = 0xFFFFFFFF;
} else if (ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN)) {
uint32_t guild_card_number;
if (ses->remote_guild_card_number >= 0) {
guild_card_number = ses->remote_guild_card_number;
log_info("Using Guild Card number %" PRIu32 " from session", guild_card_number);
} else {
cmd.player_tag = 0x00010000;
cmd.guild_card_number = ses->remote_guild_card_number;
guild_card_number = random_object<uint32_t>();
log_info("Using Guild Card number %" PRIu32 " from random generator", guild_card_number);
}
uint32_t fake_serial_number = random_object<uint32_t>() & 0x7FFFFFFF;
uint64_t fake_access_key = random_object<uint64_t>();
string fake_access_key_str = string_printf("00000000000%" PRIu64, fake_access_key);
if (fake_access_key_str.size() > 12) {
fake_access_key_str = fake_access_key_str.substr(fake_access_key_str.size() - 12);
}
C_LoginExtended_GC_9E cmd;
cmd.player_tag = 0x00010000;
cmd.guild_card_number = guild_card_number;
cmd.unused1 = 0;
cmd.unused2 = 0;
cmd.sub_version = ses->sub_version;
cmd.is_extended = 0;
cmd.language = ses->language();
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.access_key.clear_after(8);
cmd.serial_number.encode(string_printf("%08" PRIX32, fake_serial_number));
cmd.access_key.encode(fake_access_key_str);
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) {
cmd.name.encode(" ", ses->language());
} else {
cmd.name.encode(ses->character_name);
cmd.name.encode(ses->character_name, ses->language());
}
ses->server_channel.send(0x9D, 0x00, &cmd, sizeof(cmd));
cmd.client_config = ses->remote_client_config_data;
ses->server_channel.send(0x9E, 0x01, &cmd, sizeof(C_Login_GC_9E));
return HandlerResult::Type::SUPPRESS;
}
}
} else if (ses->version() == GameVersion::GC) {
if (command == 0x17) {
C_VerifyLicense_V3_DB cmd;
cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number));
cmd.access_key.encode(ses->license->access_key);
cmd.sub_version = ses->sub_version;
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
cmd.password.encode(ses->license->gc_password);
ses->server_channel.send(0xDB, 0x00, &cmd, sizeof(cmd));
return HandlerResult::Type::SUPPRESS;
} else if (ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN)) {
uint32_t guild_card_number;
if (ses->remote_guild_card_number >= 0) {
guild_card_number = ses->remote_guild_card_number;
log_info("Using Guild Card number %" PRIu32 " from session", guild_card_number);
} else {
guild_card_number = random_object<uint32_t>();
log_info("Using Guild Card number %" PRIu32 " from random generator", guild_card_number);
// For command 02, send the same as if we had received 9A from the server
return S_G_9A(ses, command, flag, data);
}
uint32_t fake_serial_number = random_object<uint32_t>() & 0x7FFFFFFF;
uint64_t fake_access_key = random_object<uint64_t>();
string fake_access_key_str = string_printf("00000000000%" PRIu64, fake_access_key);
if (fake_access_key_str.size() > 12) {
fake_access_key_str = fake_access_key_str.substr(fake_access_key_str.size() - 12);
throw logic_error("GC init command not handled");
case GameVersion::XB: {
C_LoginExtended_XB_9E cmd;
if (ses->remote_guild_card_number < 0) {
cmd.player_tag = 0xFFFF0000;
cmd.guild_card_number = 0xFFFFFFFF;
} else {
cmd.player_tag = 0x00010000;
cmd.guild_card_number = ses->remote_guild_card_number;
}
C_LoginExtended_GC_9E cmd;
cmd.player_tag = 0x00010000;
cmd.guild_card_number = guild_card_number;
cmd.unused1 = 0;
cmd.unused2 = 0;
cmd.sub_version = ses->sub_version;
cmd.is_extended = 0;
cmd.is_extended = (ses->remote_guild_card_number < 0) ? 1 : 0;
cmd.language = ses->language();
cmd.serial_number.encode(string_printf("%08" PRIX32, fake_serial_number));
cmd.access_key.encode(fake_access_key_str);
cmd.serial_number.encode(ses->license->xb_gamertag);
cmd.access_key.encode(string_printf("%016" PRIX64, ses->license->xb_user_id));
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) {
@@ -402,19 +435,21 @@ static HandlerResult S_V123P_02_17(
cmd.name.encode(ses->character_name, ses->language());
}
cmd.client_config = ses->remote_client_config_data;
ses->server_channel.send(0x9E, 0x01, &cmd, sizeof(C_Login_GC_9E));
if (ses->wrapped_client && ses->wrapped_client->xb_netloc) {
cmd.netloc = *ses->wrapped_client->xb_netloc;
cmd.unknown_a1a = ses->wrapped_client->xb_9E_unknown_a1a;
} else {
cmd.netloc.account_id = ses->license->xb_account_id;
}
cmd.xb_user_id_high = (ses->license->xb_user_id >> 32) & 0xFFFFFFFF;
cmd.xb_user_id_low = ses->license->xb_user_id & 0xFFFFFFFF;
ses->server_channel.send(
0x9E, 0x01, &cmd,
cmd.is_extended ? sizeof(C_LoginExtended_XB_9E) : sizeof(C_Login_XB_9E));
return HandlerResult::Type::SUPPRESS;
} else {
// For command 02, send the same as if we had received 9A from the server
return S_G_9A(ses, command, flag, data);
}
} else if (ses->version() == GameVersion::XB) {
throw runtime_error("xbox licenses are not implemented");
} else {
throw logic_error("invalid game version in server init handler");
default:
throw logic_error("invalid game version in server init handler");
}
}
@@ -1679,7 +1714,8 @@ static HandlerResult C_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t c
constexpr on_command_t C_D_6x = &C_6x<G_SendGuildCard_DC_6x06>;
constexpr on_command_t C_P_6x = &C_6x<G_SendGuildCard_PC_6x06>;
constexpr on_command_t C_GX_6x = &C_6x<G_SendGuildCard_V3_6x06>;
constexpr on_command_t C_G_6x = &C_6x<G_SendGuildCard_GC_6x06>;
constexpr on_command_t C_X_6x = &C_6x<G_SendGuildCard_XB_6x06>;
constexpr on_command_t C_B_6x = &C_6x<G_SendGuildCard_BB_6x06>;
template <>
@@ -1818,9 +1854,9 @@ static on_command_t handlers[0x100][6][2] = {
/* 5E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}},
/* 5F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}},
// CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB
/* 60 */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_GX_6x}, {S_6x, C_GX_6x}, {S_6x, C_B_6x}},
/* 60 */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}},
/* 61 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, C_GXB_61}, {S_invalid, C_GXB_61}, {S_invalid, C_GXB_61}},
/* 62 */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_GX_6x}, {S_6x, C_GX_6x}, {S_6x, C_B_6x}},
/* 62 */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}},
/* 63 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}},
/* 64 */ {{S_invalid, nullptr}, {S_D_64, nullptr}, {S_P_64, nullptr}, {S_G_64, nullptr}, {S_X_64, nullptr}, {S_B_64, nullptr}},
/* 65 */ {{S_invalid, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_P_65_67_68, nullptr}, {S_DG_65_67_68_EB, nullptr}, {S_X_65_67_68, nullptr}, {S_B_65_67_68, nullptr}},
@@ -1830,8 +1866,8 @@ static on_command_t handlers[0x100][6][2] = {
/* 69 */ {{S_invalid, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}, {S_66_69_E9, nullptr}},
/* 6A */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}},
/* 6B */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}},
/* 6C */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_GX_6x}, {S_6x, C_GX_6x}, {S_6x, C_B_6x}},
/* 6D */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_GX_6x}, {S_6x, C_GX_6x}, {S_6x, C_B_6x}},
/* 6C */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}},
/* 6D */ {{S_invalid, nullptr}, {S_6x, C_D_6x}, {S_6x, C_P_6x}, {S_6x, C_G_6x}, {S_6x, C_X_6x}, {S_6x, C_B_6x}},
/* 6E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}},
/* 6F */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}},
/* 70 */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}},
+45 -26
View File
@@ -526,6 +526,15 @@ std::shared_ptr<ServerState> ProxyServer::LinkedSession::require_server_state()
return this->require_server()->state;
}
void ProxyServer::LinkedSession::resume_xb(shared_ptr<Client> c) {
this->sub_version = c->sub_version;
this->character_name = c->game_data.player()->disp.name.decode();
this->config = c->config;
this->wrapped_client = c;
this->resume_inner(std::move(c->channel), detector_crypt);
c->suspend_timeouts();
}
void ProxyServer::LinkedSession::resume(
Channel&& client_channel,
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
@@ -711,31 +720,43 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message)
this->client_channel.send(0x04, 0x00, &update_client_config_cmd, sizeof(update_client_config_cmd));
}
const auto& port_name = version_to_login_port_name.at(static_cast<size_t>(this->version()));
S_Reconnect_19 reconnect_cmd = {{0, s->name_to_port_config.at(port_name)->port, 0}};
// If the client is on a virtual connection, we can use any address
// here and they should be able to connect back to the game server. If
// the client is on a real connection, we'll use the sockname of the
// existing connection (like we do in the server 19 command handler).
if (this->client_channel.is_virtual_connection) {
struct sockaddr_in* dest_sin = reinterpret_cast<struct sockaddr_in*>(&this->next_destination);
if (dest_sin->sin_family != AF_INET) {
throw logic_error("ss not AF_INET");
if (this->version() == GameVersion::XB) {
if (!this->wrapped_client) {
throw logic_error("wrapped client is missing from XB proxy session");
}
reconnect_cmd.address.store_raw(dest_sin->sin_addr.s_addr);
this->wrapped_client->should_disconnect = false;
s->game_server->connect_client(this->wrapped_client, std::move(this->client_channel));
on_login_complete(this->wrapped_client);
this->disconnect_action = DisconnectAction::CLOSE_IMMEDIATELY;
this->disconnect();
} else {
const struct sockaddr_in* sin = reinterpret_cast<const struct sockaddr_in*>(
&this->client_channel.local_addr);
if (sin->sin_family != AF_INET) {
throw logic_error("existing connection is not ipv4");
}
reconnect_cmd.address.store_raw(sin->sin_addr.s_addr);
}
const auto& port_name = version_to_login_port_name.at(static_cast<size_t>(this->version()));
this->client_channel.send(0x19, 0x00, &reconnect_cmd, sizeof(reconnect_cmd));
this->disconnect_action = DisconnectAction::CLOSE_IMMEDIATELY;
S_Reconnect_19 reconnect_cmd = {{0, s->name_to_port_config.at(port_name)->port, 0}};
// If the client is on a virtual connection, we can use any address
// here and they should be able to connect back to the game server. If
// the client is on a real connection, we'll use the sockname of the
// existing connection (like we do in the server 19 command handler).
if (this->client_channel.is_virtual_connection) {
struct sockaddr_in* dest_sin = reinterpret_cast<struct sockaddr_in*>(&this->next_destination);
if (dest_sin->sin_family != AF_INET) {
throw logic_error("ss not AF_INET");
}
reconnect_cmd.address.store_raw(dest_sin->sin_addr.s_addr);
} else {
const struct sockaddr_in* sin = reinterpret_cast<const struct sockaddr_in*>(
&this->client_channel.local_addr);
if (sin->sin_family != AF_INET) {
throw logic_error("existing connection is not ipv4");
}
reconnect_cmd.address.store_raw(sin->sin_addr.s_addr);
}
this->client_channel.send(0x19, 0x00, &reconnect_cmd, sizeof(reconnect_cmd));
this->disconnect_action = DisconnectAction::CLOSE_IMMEDIATELY;
}
}
}
@@ -762,8 +783,7 @@ void ProxyServer::LinkedSession::disconnect() {
// Set a timeout to delete the session entirely (in case the client doesn't
// reconnect)
struct timeval tv = usecs_to_timeval(this->timeout_for_disconnect_action(
this->disconnect_action));
struct timeval tv = usecs_to_timeval(this->timeout_for_disconnect_action(this->disconnect_action));
event_add(this->timeout_event.get(), &tv);
}
@@ -818,8 +838,7 @@ shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session_by_name(
shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
shared_ptr<License> l, uint16_t local_port, GameVersion version,
const Client::Config& config) {
shared_ptr<LinkedSession> session(new LinkedSession(
this->shared_from_this(), local_port, version, l, config));
shared_ptr<LinkedSession> session(new LinkedSession(this->shared_from_this(), local_port, version, l, config));
auto emplace_ret = this->id_to_session.emplace(session->id, session);
if (!emplace_ret.second) {
throw runtime_error("session already exists for this license");
+4 -1
View File
@@ -75,8 +75,9 @@ public:
struct LobbyPlayer {
uint32_t guild_card_number = 0;
uint64_t xb_user_id = 0;
std::string name;
uint8_t language;
uint8_t language = 0;
uint8_t section_id = 0;
uint8_t char_class = 0;
};
@@ -90,6 +91,7 @@ public:
bool is_in_quest;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
std::shared_ptr<Client> wrapped_client;
struct SavingFile {
std::string basename;
@@ -143,6 +145,7 @@ public:
return this->client_channel.language;
}
void resume_xb(std::shared_ptr<Client> wrapped_client);
void resume(
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
+72 -33
View File
@@ -352,7 +352,7 @@ string VersionedQuest::bin_filename() const {
if (this->episode == Episode::EP3) {
return string_printf("m%06" PRIu32 "p_e.bin", this->quest_number);
} else {
return string_printf("q%" PRIu32 ".bin", this->quest_number);
return string_printf("quest%" PRIu32 ".bin", this->quest_number);
}
}
@@ -360,7 +360,15 @@ string VersionedQuest::dat_filename() const {
if (this->episode == Episode::EP3) {
throw logic_error("Episode 3 quests do not have .dat files");
} else {
return string_printf("q%" PRIu32 ".dat", this->quest_number);
return string_printf("quest%" PRIu32 ".dat", this->quest_number);
}
}
string VersionedQuest::xb_filename() const {
if (this->episode == Episode::EP3) {
throw logic_error("Episode 3 quests do not have Xbox filenames");
} else {
return string_printf("quest%" PRIu32 "_%c.dat", this->quest_number, tolower(char_for_language_code(this->language)));
}
}
@@ -370,6 +378,7 @@ string VersionedQuest::encode_qst() const {
*this->dat_contents,
this->name,
this->quest_number,
this->language,
this->version,
this->is_dlq_encoded);
}
@@ -1115,14 +1124,15 @@ pair<string, string> decode_qst_data(const string& data) {
// the first 4 bytes in the file:
// - BB: 58 00 44 00 or 58 00 A6 00
// - PC: 3C 00 44 ?? or 3C 00 A6 ??
// - DC/V3: 44 ?? 3C 00 or A6 ?? 3C 00
// - DC/GC: 44 ?? 3C 00 or A6 ?? 3C 00
// - XB: 44 ?? 54 00 or A6 ?? 54 00
StringReader r(data);
uint32_t signature = r.get_u32b();
if (signature == 0x58004400 || signature == 0x5800A600) {
if ((signature == 0x58004400) || (signature == 0x5800A600)) {
return decode_qst_data_t<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(data);
} else if ((signature & 0xFFFFFF00) == 0x3C004400 || (signature & 0xFFFFFF00) == 0x3C00A600) {
} else if (((signature & 0xFFFFFF00) == 0x3C004400) || ((signature & 0xFFFFFF00) == 0x3C00A600)) {
return decode_qst_data_t<PSOCommandHeaderPC, S_OpenFile_PC_GC_44_A6>(data);
} else if ((signature & 0xFF00FFFF) == 0x44003C00 || (signature & 0xFF00FFFF) == 0xA6003C00) {
} else if (((signature & 0xFF00FFFF) == 0x44003C00) || ((signature & 0xFF00FFFF) == 0xA6003C00)) {
// In PSO DC, the type field is only one byte, but in V3 it's two bytes and
// the filename was shifted over by one byte. To detect this, we check if
// the V3 type field has a reasonable value, and if not, we assume the file
@@ -1132,7 +1142,7 @@ pair<string, string> decode_qst_data(const string& data) {
} else {
return decode_qst_data_t<PSOCommandHeaderDCV3, S_OpenFile_PC_GC_44_A6>(data);
}
} else if ((signature & 0xFF00FFFF) == 0x44005400 || (signature & 0xFF00FFFF) == 0xA6005400) {
} else if (((signature & 0xFF00FFFF) == 0x44005400) || ((signature & 0xFF00FFFF) == 0xA6005400)) {
return decode_qst_data_t<PSOCommandHeaderDCV3, S_OpenFile_XB_44_A6>(data);
} else {
throw runtime_error("invalid qst file format");
@@ -1150,7 +1160,14 @@ void add_command_header(
}
template <typename HeaderT, typename CmdT>
void add_open_file_command(StringWriter& w, const std::string& name, const std::string& filename, size_t file_size, bool is_download) {
void add_open_file_command_t(
StringWriter& w,
const std::string& name,
const std::string& filename,
const std::string&,
uint32_t,
size_t file_size,
bool is_download) {
add_command_header<HeaderT>(w, is_download ? 0xA6 : 0x44, 0x00, sizeof(CmdT));
CmdT cmd;
cmd.name.assign_raw("PSO/" + name);
@@ -1162,8 +1179,28 @@ void add_open_file_command(StringWriter& w, const std::string& name, const std::
w.put(cmd);
}
template <>
void add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_XB_44_A6>(
StringWriter& w,
const std::string& name,
const std::string& filename,
const std::string& xb_filename,
uint32_t quest_number,
size_t file_size,
bool is_download) {
add_command_header<PSOCommandHeaderDCV3>(w, is_download ? 0xA6 : 0x44, 0x00, sizeof(S_OpenFile_XB_44_A6));
S_OpenFile_XB_44_A6 cmd;
cmd.name.assign_raw("PSO/" + name);
cmd.filename.encode(filename);
cmd.type = 0;
cmd.file_size = file_size;
cmd.xb_filename.encode(xb_filename);
cmd.content_meta = 0x30000000 | quest_number;
w.put(cmd);
}
template <typename HeaderT>
void add_write_file_commands(
void add_write_file_commands_t(
StringWriter& w,
const string& filename,
const string& data,
@@ -1191,12 +1228,14 @@ string encode_qst_file(
const string& dat_data,
const string& name,
uint32_t quest_number,
uint8_t language,
QuestScriptVersion version,
bool is_dlq_encoded) {
StringWriter w;
string bin_filename = string_printf("q%" PRIu32 ".bin", quest_number);
string dat_filename = string_printf("q%" PRIu32 ".dat", quest_number);
string bin_filename = string_printf("quest%" PRIu32 ".bin", quest_number);
string dat_filename = string_printf("quest%" PRIu32 ".dat", quest_number);
string xb_filename = string_printf("quest%" PRIu32 "_%c.dat", quest_number, tolower(char_for_language_code(language)));
// Some tools expect both open file commands at the beginning, hence this
// unfortunate abstraction-breaking.
@@ -1204,39 +1243,39 @@ string encode_qst_file(
case QuestScriptVersion::DC_NTE:
case QuestScriptVersion::DC_V1:
case QuestScriptVersion::DC_V2:
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(w, name, bin_filename, bin_data.size(), is_dlq_encoded);
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(w, name, dat_filename, dat_data.size(), is_dlq_encoded);
add_write_file_commands<PSOCommandHeaderDCV3>(w, bin_filename, bin_data, is_dlq_encoded, false);
add_write_file_commands<PSOCommandHeaderDCV3>(w, dat_filename, dat_data, is_dlq_encoded, false);
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(w, name, bin_filename, xb_filename, quest_number, bin_data.size(), is_dlq_encoded);
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_DC_44_A6>(w, name, dat_filename, xb_filename, quest_number, dat_data.size(), is_dlq_encoded);
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, bin_filename, bin_data, is_dlq_encoded, false);
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, dat_filename, dat_data, is_dlq_encoded, false);
break;
case QuestScriptVersion::PC_V2:
add_open_file_command<PSOCommandHeaderPC, S_OpenFile_PC_GC_44_A6>(w, name, bin_filename, bin_data.size(), is_dlq_encoded);
add_open_file_command<PSOCommandHeaderPC, S_OpenFile_PC_GC_44_A6>(w, name, dat_filename, dat_data.size(), is_dlq_encoded);
add_write_file_commands<PSOCommandHeaderPC>(w, bin_filename, bin_data, is_dlq_encoded, false);
add_write_file_commands<PSOCommandHeaderPC>(w, dat_filename, dat_data, is_dlq_encoded, false);
add_open_file_command_t<PSOCommandHeaderPC, S_OpenFile_PC_GC_44_A6>(w, name, bin_filename, xb_filename, quest_number, bin_data.size(), is_dlq_encoded);
add_open_file_command_t<PSOCommandHeaderPC, S_OpenFile_PC_GC_44_A6>(w, name, dat_filename, xb_filename, quest_number, dat_data.size(), is_dlq_encoded);
add_write_file_commands_t<PSOCommandHeaderPC>(w, bin_filename, bin_data, is_dlq_encoded, false);
add_write_file_commands_t<PSOCommandHeaderPC>(w, dat_filename, dat_data, is_dlq_encoded, false);
break;
case QuestScriptVersion::GC_NTE:
case QuestScriptVersion::GC_V3:
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_PC_GC_44_A6>(w, name, bin_filename, bin_data.size(), is_dlq_encoded);
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_PC_GC_44_A6>(w, name, dat_filename, dat_data.size(), is_dlq_encoded);
add_write_file_commands<PSOCommandHeaderDCV3>(w, bin_filename, bin_data, is_dlq_encoded, false);
add_write_file_commands<PSOCommandHeaderDCV3>(w, dat_filename, dat_data, is_dlq_encoded, false);
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_PC_GC_44_A6>(w, name, bin_filename, xb_filename, quest_number, bin_data.size(), is_dlq_encoded);
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_PC_GC_44_A6>(w, name, dat_filename, xb_filename, quest_number, dat_data.size(), is_dlq_encoded);
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, bin_filename, bin_data, is_dlq_encoded, false);
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, dat_filename, dat_data, is_dlq_encoded, false);
break;
case QuestScriptVersion::GC_EP3:
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_PC_GC_44_A6>(w, name, bin_filename, bin_data.size(), is_dlq_encoded);
add_write_file_commands<PSOCommandHeaderDCV3>(w, bin_filename, bin_data, is_dlq_encoded, false);
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_PC_GC_44_A6>(w, name, bin_filename, xb_filename, quest_number, bin_data.size(), is_dlq_encoded);
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, bin_filename, bin_data, is_dlq_encoded, false);
break;
case QuestScriptVersion::XB_V3:
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_XB_44_A6>(w, name, bin_filename, bin_data.size(), is_dlq_encoded);
add_open_file_command<PSOCommandHeaderDCV3, S_OpenFile_XB_44_A6>(w, name, dat_filename, dat_data.size(), is_dlq_encoded);
add_write_file_commands<PSOCommandHeaderDCV3>(w, bin_filename, bin_data, is_dlq_encoded, false);
add_write_file_commands<PSOCommandHeaderDCV3>(w, dat_filename, dat_data, is_dlq_encoded, false);
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_XB_44_A6>(w, name, bin_filename, xb_filename, quest_number, bin_data.size(), is_dlq_encoded);
add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_XB_44_A6>(w, name, dat_filename, xb_filename, quest_number, dat_data.size(), is_dlq_encoded);
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, bin_filename, bin_data, is_dlq_encoded, false);
add_write_file_commands_t<PSOCommandHeaderDCV3>(w, dat_filename, dat_data, is_dlq_encoded, false);
break;
case QuestScriptVersion::BB_V4:
add_open_file_command<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(w, name, bin_filename, bin_data.size(), is_dlq_encoded);
add_open_file_command<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(w, name, dat_filename, dat_data.size(), is_dlq_encoded);
add_write_file_commands<PSOCommandHeaderBB>(w, bin_filename, bin_data, is_dlq_encoded, true);
add_write_file_commands<PSOCommandHeaderBB>(w, dat_filename, dat_data, is_dlq_encoded, true);
add_open_file_command_t<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(w, name, bin_filename, xb_filename, quest_number, bin_data.size(), is_dlq_encoded);
add_open_file_command_t<PSOCommandHeaderBB, S_OpenFile_BB_44_A6>(w, name, dat_filename, xb_filename, quest_number, dat_data.size(), is_dlq_encoded);
add_write_file_commands_t<PSOCommandHeaderBB>(w, bin_filename, bin_data, is_dlq_encoded, true);
add_write_file_commands_t<PSOCommandHeaderBB>(w, dat_filename, dat_data, is_dlq_encoded, true);
break;
default:
throw logic_error("invalid game version");
+2
View File
@@ -81,6 +81,7 @@ struct VersionedQuest {
std::string bin_filename() const;
std::string dat_filename() const;
std::string xb_filename() const;
std::shared_ptr<VersionedQuest> create_download_quest(uint8_t override_language = 0xFF) const;
std::string encode_qst() const;
@@ -149,5 +150,6 @@ std::string encode_qst_file(
const std::string& dat_data,
const std::string& name,
uint32_t quest_number,
uint8_t language,
QuestScriptVersion version,
bool is_dlq_encoded);
+146 -44
View File
@@ -30,6 +30,8 @@ const char* BATTLE_TABLE_DISCONNECT_HOOK_NAME = "battle_table_state";
const char* QUEST_BARRIER_DISCONNECT_HOOK_NAME = "quest_barrier";
const char* ADD_NEXT_CLIENT_DISCONNECT_HOOK_NAME = "add_next_game_client";
void on_login_complete(shared_ptr<Client> c);
static shared_ptr<const Menu> proxy_options_menu_for_client(shared_ptr<const Client> c) {
auto s = c->require_server_state();
@@ -91,14 +93,32 @@ static shared_ptr<const Menu> proxy_options_menu_for_client(shared_ptr<const Cli
return ret;
}
static void send_client_to_lobby_server(shared_ptr<Client> c) {
auto s = c->require_server_state();
const auto& port_name = version_to_lobby_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);
void send_client_to_login_server(shared_ptr<Client> c) {
if (c->version() == GameVersion::XB) {
c->server_behavior = ServerBehavior::LOGIN_SERVER;
on_login_complete(c);
} else {
const auto& port_name = version_to_login_port_name.at(static_cast<size_t>(c->version()));
auto s = c->require_server_state();
send_reconnect(c, s->connect_address_for_client(c), s->name_to_port_config.at(port_name)->port);
}
}
static void send_client_to_proxy_server(shared_ptr<Client> c) {
void send_client_to_lobby_server(shared_ptr<Client> c) {
if (c->version() == GameVersion::XB) {
c->server_behavior = ServerBehavior::LOBBY_SERVER;
on_login_complete(c);
} else {
auto s = c->require_server_state();
const auto& port_name = version_to_lobby_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);
}
}
void send_client_to_proxy_server(shared_ptr<Client> c) {
auto s = c->require_server_state();
const auto& port_name = version_to_proxy_port_name.at(static_cast<size_t>(c->version()));
@@ -117,7 +137,12 @@ static void send_client_to_proxy_server(shared_ptr<Client> c) {
ses->remote_guild_card_number = 0;
}
send_reconnect(c, s->connect_address_for_client(c), local_port);
if (c->version() == GameVersion::XB) {
ses->resume_xb(c);
c->should_disconnect = true;
} else {
send_reconnect(c, s->connect_address_for_client(c), local_port);
}
}
static void send_proxy_destinations_menu(shared_ptr<Client> c) {
@@ -239,7 +264,7 @@ static void send_main_menu(shared_ptr<Client> c) {
"Disconnect", 0);
main_menu->items.emplace_back(MainMenuItemID::CLEAR_LICENSE, "Clear license",
"Disconnect with an\ninvalid license error\nso you can enter a\ndifferent serial\nnumber, access key,\nor password",
MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB);
MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_XB | MenuItem::Flag::INVISIBLE_ON_BB);
send_menu(c, main_menu);
}
@@ -312,6 +337,10 @@ void on_disconnect(shared_ptr<Client> c) {
////////////////////////////////////////////////////////////////////////////////
static void on_05(shared_ptr<Client> c, uint16_t, uint32_t, string&) {
c->should_disconnect = true;
}
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) {
@@ -323,6 +352,7 @@ static void set_console_client_flags(shared_ptr<Client> c, uint32_t sub_version)
}
}
c->config.set_flags_for_version(c->version(), sub_version);
c->sub_version = sub_version;
if (c->config.specific_version == default_specific_version_for_version(c->version(), -1)) {
c->config.specific_version = default_specific_version_for_version(c->version(), sub_version);
}
@@ -747,21 +777,7 @@ static void on_9D_9E(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
}
} else if (command == 0x9E) {
// GC and XB send different amounts of data in this command. This is how
// newserv determines if a V3 client is GC or XB.
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_LoginExtended_XB_9E));
switch (data.size()) {
case sizeof(C_Login_GC_9E):
case sizeof(C_LoginExtended_GC_9E):
break;
case sizeof(C_Login_XB_9E):
case sizeof(C_LoginExtended_XB_9E):
c->channel.version = GameVersion::XB;
c->log.info("Game version set to XB");
break;
default:
throw runtime_error("invalid size for 9E command");
}
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_LoginExtended_GC_9E));
base_cmd = &cmd;
if (cmd.is_extended) {
const auto& cmd = check_size_t<C_LoginExtended_GC_9E>(data);
@@ -832,12 +848,11 @@ static void on_9D_9E(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
return;
} catch (const LicenseIndex::missing_license& e) {
// On V3, the client should have sent a different command containing the
// On GC, the client should have sent a different command containing the
// password already, which should have created and added a 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) {
send_command(c, 0x04, 0x04);
c->should_disconnect = true;
return;
@@ -861,6 +876,81 @@ static void on_9D_9E(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
on_login_complete(c);
}
static void on_9E_XB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
auto s = c->require_server_state();
const auto& cmd = check_size_t<C_Login_XB_9E>(data, sizeof(C_LoginExtended_XB_9E));
if (cmd.is_extended) {
const auto& cmd = check_size_t<C_LoginExtended_XB_9E>(data);
if (cmd.extension.lobby_refs[0].menu_id == MenuID::LOBBY) {
c->preferred_lobby_id = cmd.extension.lobby_refs[0].item_id;
}
}
try {
c->config.parse_from(cmd.client_config);
} catch (const invalid_argument&) {
// If we can't import the config, assume that the client was not connected
// to newserv before, so we should show the welcome message.
c->config.set_flag(Client::Flag::AT_WELCOME_MESSAGE);
}
c->xb_netloc.reset(new XBNetworkLocation(cmd.netloc));
c->xb_9E_unknown_a1a = cmd.unknown_a1a;
c->channel.language = cmd.language;
c->config.set_flags_for_version(c->version(), -1);
set_console_client_flags(c, cmd.sub_version);
string xb_gamertag = cmd.serial_number.decode();
uint64_t xb_user_id = stoull(cmd.access_key.decode(), nullptr, 16);
uint64_t xb_account_id = cmd.netloc.account_id;
try {
shared_ptr<License> l = s->license_index->verify_xb(xb_gamertag, xb_user_id, xb_account_id);
bool should_save = false;
if (l->xb_user_id == 0) {
l->xb_user_id = xb_user_id;
c->log.info("Set license XB user ID to %016" PRIX64, l->xb_user_id);
should_save = true;
}
if (l->xb_account_id == 0) {
l->xb_account_id = xb_account_id;
c->log.info("Set license XB account ID to %016" PRIX64, l->xb_account_id);
should_save = true;
}
if (should_save && !s->is_replay) {
l->save();
}
c->set_license(l);
} catch (const LicenseIndex::no_username& e) {
send_command(c, 0x04, 0x03);
c->should_disconnect = true;
return;
} catch (const LicenseIndex::incorrect_access_key& e) {
send_command(c, 0x04, 0x03);
c->should_disconnect = true;
return;
} catch (const LicenseIndex::missing_license& e) {
shared_ptr<License> l(new License());
l->serial_number = fnv1a32(xb_gamertag) & 0x7FFFFFFF;
l->xb_gamertag = xb_gamertag;
l->xb_user_id = xb_user_id;
l->xb_account_id = xb_account_id;
s->license_index->add(l);
if (!s->is_replay) {
l->save();
}
c->set_license(l);
string l_str = l->str();
c->log.info("Created license %s", l_str.c_str());
}
send_update_client_config(c);
on_login_complete(c);
}
static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
const auto& cmd = check_size_t<C_Login_BB_93>(data, sizeof(C_Login_BB_93) - 8, sizeof(C_Login_BB_93));
auto s = c->require_server_state();
@@ -1784,6 +1874,9 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
case MainMenuItemID::PROXY_DESTINATIONS:
if (!c->game_data.player(false, false)) {
send_get_player_info(c);
}
send_proxy_destinations_menu(c);
break;
@@ -1851,6 +1944,11 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
break;
case MainMenuItemID::DISCONNECT:
if (c->version() == GameVersion::XB) {
// On XB (at least via Insignia) the server has to explicitly tell
// the client to disconnect by sending this command.
send_command(c, 0x05, 0x00);
}
c->should_disconnect = true;
break;
@@ -2140,8 +2238,9 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
string bin_filename = vq->bin_filename();
string dat_filename = vq->dat_filename();
send_open_quest_file(lc, bin_filename, bin_filename, vq->bin_contents, QuestFileType::ONLINE);
send_open_quest_file(lc, dat_filename, dat_filename, vq->dat_contents, QuestFileType::ONLINE);
string xb_filename = vq->xb_filename();
send_open_quest_file(lc, bin_filename, bin_filename, xb_filename, vq->quest_number, QuestFileType::ONLINE, vq->bin_contents);
send_open_quest_file(lc, dat_filename, dat_filename, xb_filename, vq->quest_number, QuestFileType::ONLINE, vq->dat_contents);
// There is no such thing as command AC on PSO V1 and V2 - quests just
// start immediately when they're done downloading. (This is also the
@@ -2171,11 +2270,12 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
// TODO: This is not true for Episode 3 Trial Edition. We also would
// have to convert the map to a MapDefinitionTrial, though.
if (vq->version == QuestScriptVersion::GC_EP3) {
send_open_quest_file(c, q->name, vq->bin_filename(), vq->bin_contents, QuestFileType::EPISODE_3);
send_open_quest_file(c, q->name, vq->bin_filename(), "", vq->quest_number, QuestFileType::EPISODE_3, vq->bin_contents);
} else {
vq = vq->create_download_quest(c->language());
send_open_quest_file(c, q->name, vq->bin_filename(), vq->bin_contents, QuestFileType::DOWNLOAD);
send_open_quest_file(c, q->name, vq->dat_filename(), vq->dat_contents, QuestFileType::DOWNLOAD);
string xb_filename = vq->xb_filename();
send_open_quest_file(c, q->name, vq->bin_filename(), xb_filename, vq->quest_number, QuestFileType::DOWNLOAD, vq->bin_contents);
send_open_quest_file(c, q->name, vq->dat_filename(), xb_filename, vq->quest_number, QuestFileType::DOWNLOAD, vq->dat_contents);
}
}
break;
@@ -2354,11 +2454,7 @@ static void on_A0(shared_ptr<Client> c, uint16_t, uint32_t, string&) {
send_message_box(c, "");
}
const auto& port_name = version_to_login_port_name.at(static_cast<size_t>(c->version()));
auto s = c->require_server_state();
send_reconnect(c, s->connect_address_for_client(c),
s->name_to_port_config.at(port_name)->port);
send_client_to_login_server(c);
}
static void on_A1(shared_ptr<Client> c, uint16_t command, uint32_t flag, string& data) {
@@ -2555,7 +2651,7 @@ static void on_D7_GC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
try {
static FileContentsCache gba_file_cache(300 * 1000 * 1000);
auto f = gba_file_cache.get_or_load("system/gba/" + filename).file;
send_open_quest_file(c, "", filename, f->data, QuestFileType::GBA_DEMO);
send_open_quest_file(c, "", filename, "", 0, QuestFileType::GBA_DEMO, f->data);
} catch (const out_of_range&) {
send_command(c, 0xD7, 0x00);
} catch (const cannot_open_file&) {
@@ -3281,6 +3377,11 @@ static void on_C6(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
}
static void on_C9_XB(shared_ptr<Client> c, uint16_t, uint32_t flag, string& data) {
check_size_v(data.size(), 0);
c->log.warning("Ignoring connection status change command (%02" PRIX32 ")", flag);
}
shared_ptr<Lobby> create_game_generic(
shared_ptr<ServerState> s,
shared_ptr<Client> c,
@@ -3681,12 +3782,13 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
if (!vq) {
throw runtime_error("JOINABLE_QUEST_IN_PROGRESS is set, but lobby has no quest for client version");
}
string bin_basename = vq->bin_filename();
string dat_basename = vq->dat_filename();
string bin_filename = vq->bin_filename();
string dat_filename = vq->dat_filename();
send_open_quest_file(c, bin_basename + ".bin", bin_basename, vq->bin_contents, QuestFileType::ONLINE);
send_open_quest_file(c, dat_basename + ".dat", dat_basename, vq->dat_contents, QuestFileType::ONLINE);
send_open_quest_file(c, bin_filename, bin_filename, "", vq->quest_number, QuestFileType::ONLINE, vq->bin_contents);
send_open_quest_file(c, dat_filename, dat_filename, "", vq->quest_number, QuestFileType::ONLINE, vq->dat_contents);
c->config.set_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST);
} else if (l->map) {
send_rare_enemy_index_list(c, l->map->rare_enemy_indexes);
}
@@ -4127,7 +4229,7 @@ static on_command_t handlers[0x100][6] = {
/* 02 */ {on_02_P, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 03 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 04 */ {on_04_P, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 05 */ {nullptr, on_ignored, on_ignored, on_ignored, on_ignored, on_ignored},
/* 05 */ {nullptr, on_05, on_05, on_05, on_05, on_05},
/* 06 */ {nullptr, on_06, on_06, on_06, on_06, on_06},
/* 07 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 08 */ {nullptr, on_08_E6, on_08_E6, on_08_E6, on_08_E6, on_08_E6},
@@ -4284,7 +4386,7 @@ static on_command_t handlers[0x100][6] = {
/* 9B */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 9C */ {nullptr, on_9C, on_9C, on_9C, on_9C, nullptr},
/* 9D */ {nullptr, on_9D_9E, on_9D_9E, on_9D_9E, on_9D_9E, nullptr},
/* 9E */ {nullptr, nullptr, on_9D_9E, on_9D_9E, on_9D_9E, nullptr},
/* 9E */ {nullptr, nullptr, on_9D_9E, on_9D_9E, on_9E_XB, nullptr},
/* 9F */ {nullptr, nullptr, nullptr, on_9F_V3, on_9F_V3, nullptr},
// PATCH DC PC GC XB BB
/* A0 */ {nullptr, on_A0, on_A0, on_A0, on_A0, on_A0},
@@ -4329,7 +4431,7 @@ static on_command_t handlers[0x100][6] = {
/* C6 */ {nullptr, nullptr, on_C6, on_C6, on_C6, on_C6},
/* C7 */ {nullptr, nullptr, on_C7, on_C7, on_C7, on_C7},
/* C8 */ {nullptr, nullptr, on_C8, on_C8, on_C8, on_C8},
/* C9 */ {nullptr, nullptr, nullptr, on_6x_C9_CB, nullptr, nullptr},
/* C9 */ {nullptr, nullptr, nullptr, on_6x_C9_CB, on_C9_XB, nullptr},
/* CA */ {nullptr, nullptr, nullptr, on_CA_Ep3, nullptr, nullptr},
/* CB */ {nullptr, nullptr, nullptr, on_6x_C9_CB, nullptr, nullptr},
/* CC */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
+6
View File
@@ -18,5 +18,11 @@ std::shared_ptr<Lobby> create_game_generic(
void on_connect(std::shared_ptr<Client> c);
void on_disconnect(std::shared_ptr<Client> c);
void on_login_complete(std::shared_ptr<Client> c);
void on_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, std::string& data);
void on_command_with_header(std::shared_ptr<Client> c, const std::string& data);
void send_client_to_login_server(std::shared_ptr<Client> c);
void send_client_to_lobby_server(std::shared_ptr<Client> c);
void send_client_to_proxy_server(std::shared_ptr<Client> c);
+35 -9
View File
@@ -249,9 +249,11 @@ static void on_sync_joining_player_item_state(shared_ptr<Client> c, uint8_t comm
static void on_sync_joining_player_disp_and_inventory(
shared_ptr<Client> c, uint8_t command, uint8_t flag, const void* data, size_t size) {
auto l = c->require_lobby();
auto s = c->require_server_state();
// In V1/V2 games, this command sometimes is sent after the new client has
// finished loading, so we don't check l->any_client_loading() here.
auto l = c->require_lobby();
if (!l->is_game()) {
return;
}
@@ -283,15 +285,34 @@ static void on_sync_joining_player_disp_and_inventory(
bool target_is_gc = (target->version() == GameVersion::GC);
if (target_is_gc == sender_is_gc) {
send_command(target, command, flag, data, size);
} else {
auto out_cmd = check_size_t<G_SyncPlayerDispAndInventory_DC_PC_V3_6x70>(data, size);
for (size_t z = 0; z < 30; z++) {
// NOTE: If we use this codepath for non-V3 in the future, we'll need to
// change this hardcoded version. This only works because GC's mag
// encoding/decoding is symmetric (encode and decode do the same thing).
} else if (sender_is_gc) {
// Convert GC command to XB command
G_SyncPlayerDispAndInventory_XB_6x70 out_cmd = {check_size_t<G_SyncPlayerDispAndInventory_DC_PC_GC_6x70>(data, size), 0, 0, 0};
if (c->license->xb_user_id) {
out_cmd.xb_user_id_high = static_cast<uint32_t>((c->license->xb_user_id >> 32) & 0xFFFFFFFF);
out_cmd.xb_user_id_low = static_cast<uint32_t>(c->license->xb_user_id & 0xFFFFFFFF);
} else {
out_cmd.xb_user_id_high = 0xAE000000;
out_cmd.xb_user_id_low = c->license->serial_number;
}
for (size_t z = 0; z < out_cmd.inventory.num_items; z++) {
out_cmd.inventory.items[z].data.decode_for_version(GameVersion::GC);
out_cmd.inventory.items[z].data.encode_for_version(GameVersion::XB, s->item_parameter_table_for_version(GameVersion::XB));
}
send_command_t(target, command, flag, out_cmd);
} else {
// Comvert XB command to GC command
static_assert(
sizeof(G_SyncPlayerDispAndInventory_DC_PC_GC_6x70) < sizeof(G_SyncPlayerDispAndInventory_XB_6x70),
"GC 6x70 command is larger than XB 6x70 command");
auto out_cmd = check_size_t<G_SyncPlayerDispAndInventory_XB_6x70>(data, size);
for (size_t z = 0; z < out_cmd.inventory.num_items; z++) {
out_cmd.inventory.items[z].data.decode_for_version(GameVersion::XB);
out_cmd.inventory.items[z].data.encode_for_version(GameVersion::GC, s->item_parameter_table_for_version(GameVersion::GC));
}
send_command(target, command, flag, &out_cmd, sizeof(G_SyncPlayerDispAndInventory_DC_PC_GC_6x70));
}
}
@@ -417,9 +438,13 @@ static void on_send_guild_card(shared_ptr<Client> c, uint8_t command, uint8_t fl
c->game_data.player(true, false)->guild_card_description = cmd.guild_card.description;
break;
}
case GameVersion::GC:
case GameVersion::GC: {
const auto& cmd = check_size_t<G_SendGuildCard_GC_6x06>(data, size);
c->game_data.player(true, false)->guild_card_description.encode(cmd.guild_card.description.decode(c->language()), c->language());
break;
}
case GameVersion::XB: {
const auto& cmd = check_size_t<G_SendGuildCard_V3_6x06>(data, size);
const auto& cmd = check_size_t<G_SendGuildCard_XB_6x06>(data, size);
c->game_data.player(true, false)->guild_card_description.encode(cmd.guild_card.description.decode(c->language()), c->language());
break;
}
@@ -882,6 +907,7 @@ static void on_buy_shop_item(shared_ptr<Client> c, uint8_t command, uint8_t flag
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
auto p = c->game_data.player();
ItemData item = cmd.item_data;
item.data2d = 0; // Clear the price field
item.decode_for_version(c->version());
l->on_item_id_generated_externally(item.id);
p->add_item(item);
+6 -3
View File
@@ -273,9 +273,12 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
auto& mask = check_size_t<S_JoinGame_PC_64>(mask_data, mask_size);
mask.variations.clear(0);
mask.rare_seed = 0;
} else { // V3
auto& mask = check_size_t<S_JoinGame_DC_64>(
mask_data, mask_size, sizeof(S_JoinGame_GC_Ep3_64));
} else if (version == GameVersion::XB) {
auto& mask = check_size_t<S_JoinGame_XB_64>(mask_data, mask_size);
mask.variations.clear(0);
mask.rare_seed = 0;
} else if (version == GameVersion::DC || version == GameVersion::GC) {
auto& mask = check_size_t<S_JoinGame_DC_64>(mask_data, mask_size, sizeof(S_JoinGame_GC_Ep3_64));
mask.variations.clear(0);
mask.rare_seed = 0;
for (size_t offset = sizeof(S_JoinGame_GC_64) +
+8 -8
View File
@@ -256,7 +256,7 @@ struct PSOGCCharacterFile {
/* 0460:0044 */ parray<parray<uint8_t, 0x80>, 4> quest_flags;
/* 0660:0244 */ be_uint32_t death_count;
/* 0664:0248 */ PlayerBank bank;
/* 192C:1510 */ GuildCardV3 guild_card;
/* 192C:1510 */ GuildCardGC guild_card;
/* 19BC:15A0 */ parray<PSOGCSaveFileSymbolChatEntry, 12> symbol_chats;
/* 1DDC:19C0 */ parray<PSOGCSaveFileChatShortcutEntry, 20> chat_shortcuts;
/* 246C:2050 */ pstring<TextEncoding::SJIS, 0xAC> auto_reply;
@@ -313,7 +313,7 @@ struct PSOGCEp3CharacterFile {
/* 0864:0448 */ be_uint32_t num_bank_items;
/* 0868:044C */ be_uint32_t bank_meseta;
/* 086C:0450 */ parray<PlayerBankItem, 4> bank_items;
/* 08CC:04B0 */ GuildCardV3 guild_card;
/* 08CC:04B0 */ GuildCardGC guild_card;
/* 095C:0540 */ parray<PSOGCSaveFileSymbolChatEntry, 12> symbol_chats;
/* 0D7C:0960 */ parray<PSOGCSaveFileChatShortcutEntry, 20> chat_shortcuts;
/* 140C:0FF0 */ pstring<TextEncoding::SJIS, 0xAC> auto_reply;
@@ -356,12 +356,12 @@ struct PSOGCGuildCardFile {
/* 0000 */ be_uint32_t checksum;
/* 0004 */ parray<uint8_t, 0xC0> unknown_a1;
struct GuildCardBE {
// Note: This struct (up through offset 0x90) is identical to GuildCardV3
// except for 32-bit fields, which are big-endian here.
// Note: This struct (up through offset 0x90) is identical to GuildCardGC
// except for the 32-bit fields, which are big-endian here.
/* 0000 */ be_uint32_t player_tag; // == 0x00000001 (not 0x00010000)
/* 0004 */ be_uint32_t guild_card_number;
/* 0008 */ pstring<TextEncoding::SJIS, 0x18> name;
/* 0020 */ pstring<TextEncoding::SJIS, 0x6C> description;
/* 0008 */ pstring<TextEncoding::ASCII, 0x18> name;
/* 0020 */ pstring<TextEncoding::MARKED, 0x6C> description;
/* 008C */ uint8_t present;
/* 008D */ uint8_t language;
/* 008E */ uint8_t section_id;
@@ -374,7 +374,7 @@ struct PSOGCGuildCardFile {
/* 0091 */ uint8_t unknown_a2;
/* 0092 */ uint8_t unknown_a3;
/* 0093 */ uint8_t unknown_a4;
/* 0094 */ pstring<TextEncoding::SJIS, 0x6C> comment;
/* 0094 */ pstring<TextEncoding::MARKED, 0x6C> comment;
/* 0100 */
} __attribute__((packed));
/* 00C4 */ parray<GuildCardEntry, 0xD2> entries;
@@ -396,7 +396,7 @@ struct PSOGCSnapshotFile {
/* 1800A */ be_int16_t max_players;
/* 1800C */ parray<be_uint32_t, 12> players_present;
/* 1803C */ parray<be_uint32_t, 12> player_levels;
/* 1806C */ parray<pstring<TextEncoding::SJIS, 0x18>, 12> player_names;
/* 1806C */ parray<pstring<TextEncoding::ASCII, 0x18>, 12> player_names;
/* 1818C */
bool checksum_correct() const;
+215 -70
View File
@@ -163,14 +163,17 @@ void send_server_init_dc_pc_v3(shared_ptr<Client> c, uint8_t flags) {
c->channel.crypt_out.reset(new PSOV2Encryption(server_key));
break;
case GameVersion::DC:
case GameVersion::GC:
case GameVersion::XB: {
case GameVersion::GC: {
shared_ptr<PSOV2OrV3DetectorEncryption> det_crypt(new PSOV2OrV3DetectorEncryption(
client_key, v2_crypt_initial_client_commands, v3_crypt_initial_client_commands));
c->channel.crypt_in = det_crypt;
c->channel.crypt_out.reset(new PSOV2OrV3ImitatorEncryption(server_key, det_crypt));
break;
}
case GameVersion::XB:
c->channel.crypt_in.reset(new PSOV3Encryption(client_key));
c->channel.crypt_out.reset(new PSOV3Encryption(server_key));
break;
default:
throw invalid_argument("incorrect client version");
}
@@ -273,44 +276,6 @@ void send_update_client_config(shared_ptr<Client> c) {
}
}
template <typename CommandT>
void send_quest_open_file_t(
shared_ptr<Client> c,
const string& quest_name,
const string& filename,
uint32_t file_size,
QuestFileType type) {
CommandT cmd;
uint8_t command_num;
switch (type) {
case QuestFileType::ONLINE:
command_num = 0x44;
cmd.name.encode("PSO/" + quest_name);
cmd.type = 0;
break;
case QuestFileType::GBA_DEMO:
command_num = 0xA6;
cmd.name.encode("GBA Demo");
cmd.type = 2;
break;
case QuestFileType::DOWNLOAD:
command_num = 0xA6;
cmd.name.encode("PSO/" + quest_name);
cmd.type = 0;
break;
case QuestFileType::EPISODE_3:
command_num = 0xA6;
cmd.name.encode("PSO/" + quest_name);
cmd.type = 3;
break;
default:
throw logic_error("invalid quest file type");
}
cmd.file_size = file_size;
cmd.filename.encode(filename);
send_command_t(c, command_num, 0x00, cmd);
}
void send_quest_buffer_overflow(shared_ptr<Client> c) {
// PSO Episode 3 USA doesn't natively support the B2 command, but we can add
// it back to the game with some tricky commands. For details on how this
@@ -320,18 +285,21 @@ void send_quest_buffer_overflow(shared_ptr<Client> c) {
throw runtime_error("Episode 3 buffer overflow code must be a single segment");
}
static const string filename = "m999999p_e.bin";
send_quest_open_file_t<S_OpenFile_PC_GC_44_A6>(
c, "BufferOverflow", filename, 0x18, QuestFileType::EPISODE_3);
S_OpenFile_PC_GC_44_A6 open_cmd;
open_cmd.name.encode("PSO/BufferOverflow");
open_cmd.type = 3;
open_cmd.file_size = 0x18;
open_cmd.filename.encode("m999999p_e.bin");
send_command_t(c, 0xA6, 0x00, open_cmd);
S_WriteFile_13_A7 cmd;
cmd.filename.encode(filename);
memcpy(cmd.data.data(), fn->code.data(), fn->code.size());
S_WriteFile_13_A7 write_cmd;
write_cmd.filename.encode("m999999p_e.bin");
memcpy(write_cmd.data.data(), fn->code.data(), fn->code.size());
if (fn->code.size() < 0x400) {
memset(&cmd.data[fn->code.size()], 0, 0x400 - fn->code.size());
memset(&write_cmd.data[fn->code.size()], 0, 0x400 - fn->code.size());
}
cmd.data_size = fn->code.size();
send_command_t(c, 0xA7, 0x00, cmd);
write_cmd.data_size = fn->code.size();
send_command_t(c, 0xA7, 0x00, write_cmd);
}
void empty_function_call_response_handler(uint32_t, uint32_t) {}
@@ -1054,7 +1022,7 @@ void send_card_search_result(
}
template <typename CmdT>
void send_guild_card_dc_pc_v3_t(
void send_guild_card_dc_pc_gc_t(
Channel& ch,
uint32_t guild_card_number,
const string& name,
@@ -1077,6 +1045,32 @@ void send_guild_card_dc_pc_v3_t(
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
}
void send_guild_card_xb(
Channel& ch,
uint32_t guild_card_number,
uint64_t xb_user_id,
const string& name,
const string& description,
uint8_t language,
uint8_t section_id,
uint8_t char_class) {
G_SendGuildCard_XB_6x06 cmd;
cmd.header.subcommand = 0x06;
cmd.header.size = sizeof(G_SendGuildCard_XB_6x06) / 4;
cmd.header.unused = 0x0000;
cmd.guild_card.player_tag = 0x00010000;
cmd.guild_card.guild_card_number = guild_card_number;
cmd.guild_card.xb_user_id_high = (xb_user_id >> 32) & 0xFFFFFFFF;
cmd.guild_card.xb_user_id_low = xb_user_id & 0xFFFFFFFF;
cmd.guild_card.name.encode(name, ch.language);
cmd.guild_card.description.encode(description, ch.language);
cmd.guild_card.present = 1;
cmd.guild_card.language = language;
cmd.guild_card.section_id = section_id;
cmd.guild_card.char_class = char_class;
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
}
static void send_guild_card_bb(
Channel& ch,
uint32_t guild_card_number,
@@ -1104,6 +1098,7 @@ static void send_guild_card_bb(
void send_guild_card(
Channel& ch,
uint32_t guild_card_number,
uint64_t xb_user_id,
const string& name,
const string& team_name,
const string& description,
@@ -1112,18 +1107,21 @@ void send_guild_card(
uint8_t char_class) {
switch (ch.version) {
case GameVersion::DC:
send_guild_card_dc_pc_v3_t<G_SendGuildCard_DC_6x06>(
send_guild_card_dc_pc_gc_t<G_SendGuildCard_DC_6x06>(
ch, guild_card_number, name, description, language, section_id, char_class);
break;
case GameVersion::PC:
send_guild_card_dc_pc_v3_t<G_SendGuildCard_PC_6x06>(
send_guild_card_dc_pc_gc_t<G_SendGuildCard_PC_6x06>(
ch, guild_card_number, name, description, language, section_id, char_class);
break;
case GameVersion::GC:
case GameVersion::XB:
send_guild_card_dc_pc_v3_t<G_SendGuildCard_V3_6x06>(
send_guild_card_dc_pc_gc_t<G_SendGuildCard_GC_6x06>(
ch, guild_card_number, name, description, language, section_id, char_class);
break;
case GameVersion::XB:
send_guild_card_xb(
ch, guild_card_number, xb_user_id, name, description, language, section_id, char_class);
break;
case GameVersion::BB:
send_guild_card_bb(ch, guild_card_number, name, team_name, description, language, section_id, char_class);
break;
@@ -1139,13 +1137,16 @@ void send_guild_card(shared_ptr<Client> c, shared_ptr<Client> source) {
auto source_p = source->game_data.player(true, false);
uint32_t guild_card_number = source->license->serial_number;
uint64_t xb_user_id = source->license->xb_user_id
? source->license->xb_user_id
: (0xAE00000000000000 | guild_card_number);
uint8_t language = source_p->inventory.language;
string name = source_p->disp.name.decode(language);
string description = source_p->guild_card_description.decode(language);
uint8_t section_id = source_p->disp.visual.section_id;
uint8_t char_class = source_p->disp.visual.char_class;
send_guild_card(c->channel, guild_card_number, name, "", description, language, section_id, char_class);
send_guild_card(c->channel, guild_card_number, xb_user_id, name, "", description, language, section_id, char_class);
}
////////////////////////////////////////////////////////////////////////////////
@@ -1694,6 +1695,16 @@ void send_join_game(shared_ptr<Client> c, shared_ptr<Lobby> l) {
case GameVersion::XB: {
S_JoinGame_XB_64 cmd;
size_t player_count = populate_v3_cmd(cmd);
for (size_t x = 0; x < 4; x++) {
auto lc = l->clients[x];
if (lc) {
if (lc->xb_netloc) {
cmd.lobby_data[x].netloc = *lc->xb_netloc;
} else {
cmd.lobby_data[x].netloc.account_id = 0xAE00000000000000 | lc->license->serial_number;
}
}
}
send_command_t(c, 0x64, player_count, cmd);
break;
}
@@ -1802,6 +1813,79 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cli
send_command(c, command, used_entries, &cmd, cmd.size(used_entries));
}
void send_join_lobby_xb(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Client> joining_client = nullptr) {
auto s = c->require_server_state();
uint8_t command;
if (l->is_game()) {
if (joining_client) {
command = 0x65;
} else {
throw logic_error("send_join_lobby_xb should not be used for primary game join command");
}
} else {
command = joining_client ? 0x68 : 0x67;
}
send_player_records_t<PlayerRecordsEntry_V3>(c, l, joining_client);
uint8_t lobby_type;
if (c->config.override_lobby_number != 0x80) {
lobby_type = c->config.override_lobby_number;
} else if (l->check_flag(Lobby::Flag::IS_OVERFLOW)) {
lobby_type = c->config.check_flag(Client::Flag::IS_EPISODE_3) ? 15 : 0;
} else {
lobby_type = l->block - 1;
}
if ((lobby_type > 0x11) && (lobby_type != 0x67) && (lobby_type != 0xD4) && (lobby_type < 0xFC)) {
lobby_type = l->block - 1;
}
S_JoinLobby_XB_65_67_68 cmd;
cmd.lobby_flags.client_id = c->lobby_client_id;
cmd.lobby_flags.leader_id = l->leader_id;
cmd.lobby_flags.disable_udp = 0x01;
cmd.lobby_flags.lobby_number = lobby_type;
cmd.lobby_flags.block_number = l->block;
cmd.lobby_flags.unknown_a1 = 0;
cmd.lobby_flags.event = l->event;
cmd.lobby_flags.unknown_a2 = 0;
cmd.lobby_flags.unused = 0;
vector<shared_ptr<Client>> lobby_clients;
if (joining_client) {
lobby_clients.emplace_back(joining_client);
} else {
for (auto lc : l->clients) {
if (lc) {
lobby_clients.emplace_back(lc);
}
}
}
size_t used_entries = 0;
for (const auto& lc : lobby_clients) {
auto lp = lc->game_data.player();
auto& e = cmd.entries[used_entries++];
e.lobby_data.player_tag = 0x00010000;
e.lobby_data.guild_card_number = lc->license->serial_number;
if (lc->xb_netloc) {
e.lobby_data.netloc = *lc->xb_netloc;
} else {
e.lobby_data.netloc.account_id = 0xAE00000000000000 | lc->license->serial_number;
}
e.lobby_data.client_id = lc->lobby_client_id;
e.lobby_data.name.encode(lp->disp.name.decode(lp->inventory.language), c->language());
e.inventory = lp->inventory;
e.inventory.encode_for_version(c->version(), s->item_parameter_table_for_version(c->version()));
e.disp = convert_player_disp_data<PlayerDispDataDCPCV3>(lp->disp, c->language(), lp->inventory.language);
e.disp.enforce_lobby_join_limits(c->version());
}
send_command(c, command, used_entries, &cmd, cmd.size(used_entries));
}
void send_join_lobby_dc_nte(shared_ptr<Client> c, shared_ptr<Lobby> l,
shared_ptr<Client> joining_client = nullptr) {
uint8_t command;
@@ -1869,7 +1953,7 @@ void send_join_lobby(shared_ptr<Client> c, shared_ptr<Lobby> l) {
send_join_lobby_t<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3, PlayerRecordsEntry_V3, false>(c, l);
break;
case GameVersion::XB:
send_join_lobby_t<PlayerLobbyDataXB, PlayerDispDataDCPCV3, PlayerRecordsEntry_V3, false>(c, l);
send_join_lobby_xb(c, l);
break;
case GameVersion::BB:
send_join_lobby_t<PlayerLobbyDataBB, PlayerDispDataBB, PlayerRecordsEntry_BB, true>(c, l);
@@ -1904,7 +1988,7 @@ void send_player_join_notification(shared_ptr<Client> c,
send_join_lobby_t<PlayerLobbyDataDCGC, PlayerDispDataDCPCV3, PlayerRecordsEntry_V3, false>(c, l, joining_client);
break;
case GameVersion::XB:
send_join_lobby_t<PlayerLobbyDataXB, PlayerDispDataDCPCV3, PlayerRecordsEntry_V3, false>(c, l, joining_client);
send_join_lobby_xb(c, l, joining_client);
break;
case GameVersion::BB:
send_join_lobby_t<PlayerLobbyDataBB, PlayerDispDataBB, PlayerRecordsEntry_BB, true>(c, l, joining_client);
@@ -2830,26 +2914,87 @@ void send_quest_file_chunk(
send_command_t(c, is_download_quest ? 0xA7 : 0x13, chunk_index, cmd);
}
void send_open_quest_file(shared_ptr<Client> c, const string& quest_name,
const string& basename, shared_ptr<const string> contents, QuestFileType type) {
template <typename CommandT>
void send_open_quest_file_t(
shared_ptr<Client> c,
const string& quest_name,
const string& filename,
const string&,
uint32_t file_size,
uint32_t,
QuestFileType type) {
CommandT cmd;
uint8_t command_num;
switch (type) {
case QuestFileType::ONLINE:
command_num = 0x44;
cmd.name.encode("PSO/" + quest_name);
cmd.type = 0;
break;
case QuestFileType::GBA_DEMO:
command_num = 0xA6;
cmd.name.encode("GBA Demo");
cmd.type = 2;
break;
case QuestFileType::DOWNLOAD:
command_num = 0xA6;
cmd.name.encode("PSO/" + quest_name);
cmd.type = 0;
break;
case QuestFileType::EPISODE_3:
command_num = 0xA6;
cmd.name.encode("PSO/" + quest_name);
cmd.type = 3;
break;
default:
throw logic_error("invalid quest file type");
}
cmd.file_size = file_size;
cmd.filename.encode(filename);
send_command_t(c, command_num, 0x00, cmd);
}
template <>
void send_open_quest_file_t<S_OpenFile_XB_44_A6>(
shared_ptr<Client> c,
const string& quest_name,
const string& filename,
const string& xb_filename,
uint32_t file_size,
uint32_t quest_number,
QuestFileType type) {
S_OpenFile_XB_44_A6 cmd;
cmd.name.encode("PSO/" + quest_name);
cmd.type = 0;
cmd.file_size = file_size;
cmd.filename.encode(filename);
cmd.xb_filename.encode(xb_filename);
cmd.content_meta = 0x30000000 | quest_number;
send_command_t(c, (type == QuestFileType::ONLINE) ? 0x44 : 0xA6, 0x00, cmd);
}
void send_open_quest_file(
shared_ptr<Client> c,
const string& quest_name,
const string& filename,
const string& xb_filename,
uint32_t quest_number,
QuestFileType type,
shared_ptr<const string> contents) {
switch (c->version()) {
case GameVersion::DC:
send_quest_open_file_t<S_OpenFile_DC_44_A6>(
c, quest_name, basename, contents->size(), type);
send_open_quest_file_t<S_OpenFile_DC_44_A6>(c, quest_name, filename, xb_filename, contents->size(), quest_number, type);
break;
case GameVersion::PC:
case GameVersion::GC:
send_quest_open_file_t<S_OpenFile_PC_GC_44_A6>(
c, quest_name, basename, contents->size(), type);
send_open_quest_file_t<S_OpenFile_PC_GC_44_A6>(c, quest_name, filename, xb_filename, contents->size(), quest_number, type);
break;
case GameVersion::XB:
send_quest_open_file_t<S_OpenFile_XB_44_A6>(
c, quest_name, basename, contents->size(), type);
send_open_quest_file_t<S_OpenFile_XB_44_A6>(c, quest_name, filename, xb_filename, contents->size(), quest_number, type);
break;
case GameVersion::BB:
send_quest_open_file_t<S_OpenFile_BB_44_A6>(
c, quest_name, basename, contents->size(), type);
send_open_quest_file_t<S_OpenFile_BB_44_A6>(c, quest_name, filename, xb_filename, contents->size(), quest_number, type);
break;
default:
throw logic_error("cannot send quest files to this version of client");
@@ -2863,12 +3008,12 @@ void send_open_quest_file(shared_ptr<Client> c, const string& quest_name,
if (chunk_bytes > 0x400) {
chunk_bytes = 0x400;
}
send_quest_file_chunk(c, basename.c_str(), offset / 0x400,
send_quest_file_chunk(c, filename.c_str(), offset / 0x400,
contents->data() + offset, chunk_bytes, (type != QuestFileType::ONLINE));
}
} else {
c->sending_files.emplace(basename, contents);
c->log.info("Opened file %s", basename.c_str());
c->sending_files.emplace(filename, contents);
c->log.info("Opened file %s", filename.c_str());
}
}
+6 -3
View File
@@ -242,6 +242,7 @@ void send_card_search_result(
void send_guild_card(
Channel& ch,
uint32_t guild_card_number,
uint64_t xb_user_id,
const std::string& name,
const std::string& team_name,
const std::string& description,
@@ -362,9 +363,11 @@ enum class QuestFileType {
void send_open_quest_file(
std::shared_ptr<Client> c,
const std::string& quest_name,
const std::string& basename,
std::shared_ptr<const std::string> contents,
QuestFileType type);
const std::string& filename,
const std::string& xb_filename,
uint32_t quest_number,
QuestFileType type,
std::shared_ptr<const std::string> contents);
void send_quest_file_chunk(
std::shared_ptr<Client> c,
const std::string& filename,
+10 -6
View File
@@ -29,13 +29,11 @@ using namespace std::placeholders;
void Server::disconnect_client(shared_ptr<Client> c) {
if (c->channel.is_virtual_connection) {
server_log.info(
"Client disconnected: C-%" PRIX64 " on virtual connection %p",
c->id, c->channel.bev.get());
server_log.info("Client disconnected: C-%" PRIX64 " on virtual connection %p", c->id, c->channel.bev.get());
} else if (c->channel.bev) {
server_log.info("Client disconnected: C-%" PRIX64 " on fd %d", c->id, bufferevent_getfd(c->channel.bev.get()));
} else {
server_log.info(
"Client disconnected: C-%" PRIX64 " on fd %d",
c->id, bufferevent_getfd(c->channel.bev.get()));
server_log.info("Client C-%" PRIX64 " removed from game server", c->id);
}
this->state->channel_to_client.erase(&c->channel);
@@ -165,6 +163,12 @@ void Server::connect_client(
}
}
void Server::connect_client(shared_ptr<Client> c, Channel&& ch) {
c->channel.replace_with(std::move(ch), Server::on_client_input, Server::on_client_error, this, string_printf("C-%" PRIX64, c->id));
this->state->channel_to_client.emplace(&c->channel, c);
server_log.info("Client C-%" PRIX64 " added to game server", c->id);
}
void Server::on_listen_error(struct evconnlistener* listener) {
int err = EVUTIL_SOCKET_ERROR();
server_log.error("Failure on listening socket %d: %d (%s)",
+2 -2
View File
@@ -15,8 +15,7 @@ public:
Server() = delete;
Server(const Server&) = delete;
Server(Server&&) = delete;
Server(std::shared_ptr<struct event_base> base,
std::shared_ptr<ServerState> state);
Server(std::shared_ptr<struct event_base> base, std::shared_ptr<ServerState> state);
virtual ~Server() = default;
void listen(const std::string& addr_str, const std::string& socket_path, GameVersion version, ServerBehavior initial_state);
@@ -27,6 +26,7 @@ public:
void connect_client(struct bufferevent* bev, uint32_t address,
uint16_t client_port, uint16_t server_port,
GameVersion version, ServerBehavior initial_state);
void connect_client(std::shared_ptr<Client> c, Channel&& ch);
void disconnect_client(std::shared_ptr<Client> c);
std::shared_ptr<Client> get_client() const;
+17 -34
View File
@@ -141,6 +141,9 @@ Server commands:\n\
Add a license to the server. <parameters> is some subset of the following:\n\
bb-username=<username> (BB username)\n\
bb-password=<password> (BB password)\n\
xb-gamertag=<gamertag> (Xbox gamertag)\n\
xb-user-id=<user-id> (Xbox user ID)\n\
xb-account-id=<account-id> (Xbox account ID)\n\
gc-password=<password> (GC password)\n\
access-key=<access-key> (DC/GC/PC access key)\n\
serial=<serial-number> (decimal serial number; required for all licenses)\n\
@@ -319,31 +322,21 @@ Proxy session commands:\n\
for (const string& token : split(command_args, ' ')) {
if (starts_with(token, "bb-username=")) {
if (token.size() >= 32) {
throw invalid_argument("username too long");
}
l->bb_username = token.substr(12);
} else if (starts_with(token, "bb-password=")) {
if (token.size() >= 32) {
throw invalid_argument("bb-password too long");
}
l->bb_password = token.substr(12);
} else if (starts_with(token, "xb-gamertag=")) {
l->xb_gamertag = token.substr(12);
} else if (starts_with(token, "xb-user-id=")) {
l->xb_user_id = stoull(token.substr(11), nullptr, 16);
} else if (starts_with(token, "xb-account-id=")) {
l->xb_account_id = stoull(token.substr(14), nullptr, 16);
} else if (starts_with(token, "gc-password=")) {
if (token.size() > 20) {
throw invalid_argument("gc-password too long");
}
l->gc_password = token.substr(12);
} else if (starts_with(token, "access-key=")) {
if (token.size() > 23) {
throw invalid_argument("access-key is too long");
}
l->access_key = token.substr(11);
} else if (starts_with(token, "serial=")) {
l->serial_number = stoul(token.substr(7));
l->serial_number = stoul(token.substr(7), nullptr, 0);
} else if (starts_with(token, "flags=")) {
string mask = token.substr(6);
@@ -386,31 +379,21 @@ Proxy session commands:\n\
try {
for (const string& token : tokens) {
if (starts_with(token, "bb-username=")) {
if (token.size() >= 32) {
throw invalid_argument("username too long");
}
l->bb_username = token.substr(12);
} else if (starts_with(token, "bb-password=")) {
if (token.size() >= 32) {
throw invalid_argument("bb-password too long");
}
l->bb_password = token.substr(12);
} else if (starts_with(token, "xb-gamertag=")) {
l->xb_gamertag = token.substr(12);
} else if (starts_with(token, "xb-user-id=")) {
l->xb_user_id = stoull(token.substr(11), nullptr, 16);
} else if (starts_with(token, "xb-account-id=")) {
l->xb_account_id = stoull(token.substr(14), nullptr, 16);
} else if (starts_with(token, "gc-password=")) {
if (token.size() > 20) {
throw invalid_argument("gc-password too long");
}
l->gc_password = token.substr(12);
} else if (starts_with(token, "access-key=")) {
if (token.size() > 23) {
throw invalid_argument("access-key is too long");
}
l->access_key = token.substr(11);
} else if (starts_with(token, "serial=")) {
l->serial_number = stoul(token.substr(7));
l->serial_number = stoul(token.substr(7), nullptr, 0);
} else if (starts_with(token, "flags=")) {
string mask = token.substr(6);
+1 -1
View File
@@ -13,7 +13,7 @@ const vector<string> version_to_login_port_name = {
const vector<string> version_to_lobby_port_name = {
"bb-patch", "console-lobby", "pc-lobby", "console-lobby", "console-lobby", "bb-lobby"};
const vector<string> version_to_proxy_port_name = {
"", "dc-proxy", "pc-proxy", "gc-proxy", "xb-proxy", "bb-proxy"};
"", "dc-proxy", "pc-proxy", "gc-proxy", "xb", "bb-proxy"};
const char* name_for_version(GameVersion version) {
switch (version) {