add xbox support
This commit is contained in:
+3
-5
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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");
|
||||
|
||||
@@ -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
@@ -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},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) +
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user