fix xbox redirect command

This commit is contained in:
Martin Michelsen
2023-11-07 13:15:00 -08:00
parent 71cfced5ee
commit d46e6de4f0
14 changed files with 204 additions and 173 deletions
+5 -1
View File
@@ -1888,6 +1888,9 @@ struct C_LoginExtended_PC_9D : C_Login_DC_PC_GC_9D {
// Not used on GC Episodes 1&2 Trial Edition.
// The extended version of this command is used in the same circumstances as
// when PSO PC uses the extended version of the 9D command.
// PSO XB does not send the client config (security data) in the 9E command,
// even though there is a space for it. The server must use 9F instead to
// retrieve the client config.
// header.flag is 1 if the client has UDP disabled.
struct C_Login_GC_9E : C_Login_DC_PC_GC_9D {
@@ -1897,7 +1900,8 @@ struct C_LoginExtended_GC_9E : C_Login_GC_9E {
SC_MeetUserExtension<TextEncoding::MARKED> extension;
} __packed__;
struct C_Login_XB_9E : C_Login_GC_9E {
struct C_Login_XB_9E : C_Login_DC_PC_GC_9D {
parray<uint8_t, 0x20> unused;
XBNetworkLocation netloc;
parray<le_uint32_t, 3> unknown_a1a;
le_uint32_t xb_user_id_high = 0;
+9 -11
View File
@@ -356,6 +356,7 @@ static HandlerResult S_V123P_02_17(
}
}
throw logic_error("DC/PC init command not handled");
case GameVersion::GC:
if (command == 0x17) {
C_VerifyLicense_V3_DB cmd;
@@ -411,6 +412,7 @@ static HandlerResult S_V123P_02_17(
return S_G_9A(ses, command, flag, data);
}
throw logic_error("GC init command not handled");
case GameVersion::XB: {
C_LoginExtended_XB_9E cmd;
if (ses->remote_guild_card_number < 0) {
@@ -434,20 +436,14 @@ static HandlerResult S_V123P_02_17(
} else {
cmd.name.encode(ses->character_name, ses->language());
}
cmd.client_config = ses->remote_client_config_data;
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.netloc = ses->xb_netloc;
cmd.unknown_a1a = ses->xb_9E_unknown_a1a;
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));
ses->server_channel.send(0x9E, 0x01, &cmd, sizeof(C_LoginExtended_XB_9E));
return HandlerResult::Type::SUPPRESS;
}
default:
throw logic_error("invalid game version in server init handler");
}
@@ -1538,7 +1534,9 @@ static HandlerResult S_AC(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
}
static HandlerResult S_66_69_E9(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
const auto& cmd = check_size_t<S_LeaveLobby_66_69_Ep3_E9>(data);
// Schtserv sends a large command here for unknown reasons. The client ignores
// the extra data, so we allow the large command here.
const auto& cmd = check_size_t<S_LeaveLobby_66_69_Ep3_E9>(data, 0xFFFF);
size_t index = cmd.client_id;
if (index >= ses->lobby_players.size()) {
ses->log.warning("Lobby leave command references missing position");
+82 -78
View File
@@ -259,12 +259,6 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
auto s = server->state;
bool should_close_unlinked_session = false;
shared_ptr<License> license;
uint32_t sub_version = 0;
string character_name;
Client::Config config;
string login_command_bb;
string hardware_id;
try {
if (ses->version == GameVersion::DC) {
@@ -272,18 +266,18 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
// anything else, disconnect
if (command == 0x93) {
const auto& cmd = check_size_t<C_LoginV1_DC_93>(data);
license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
sub_version = cmd.sub_version;
ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
character_name = cmd.name.decode(ses->channel.language);
hardware_id = cmd.hardware_id.decode();
config.set_flag(Client::Flag::IS_DC_V1);
ses->character_name = cmd.name.decode(ses->channel.language);
ses->hardware_id = cmd.hardware_id.decode();
ses->config.set_flag(Client::Flag::IS_DC_V1);
} else if (command == 0x9D) {
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_DC_GC_9D));
license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
sub_version = cmd.sub_version;
ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
character_name = cmd.name.decode(ses->channel.language);
ses->character_name = cmd.name.decode(ses->channel.language);
} else {
throw runtime_error("command is not 93 or 9D");
}
@@ -295,10 +289,10 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
throw runtime_error("command is not 9D");
}
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_PC_9D));
license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
sub_version = cmd.sub_version;
ses->license = s->license_index->verify_v1_v2(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
character_name = cmd.name.decode(ses->channel.language);
ses->character_name = cmd.name.decode(ses->channel.language);
} else if (ses->version == GameVersion::GC) {
// We should only get a 9E while the session is unlinked; if we get
@@ -308,11 +302,34 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
throw runtime_error("command is not 9E");
}
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_LoginExtended_GC_9E));
license = s->license_index->verify_gc(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
sub_version = cmd.sub_version;
ses->license = s->license_index->verify_gc(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode());
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
character_name = cmd.name.decode(ses->channel.language);
config.parse_from(cmd.client_config);
ses->character_name = cmd.name.decode(ses->channel.language);
ses->config.parse_from(cmd.client_config);
} else if (ses->version == GameVersion::XB) {
// We should only get a 9E or 9F while the session is unlinked; if we get
// anything else, disconnect
if (command == 0x9E) {
const auto& cmd = check_size_t<C_Login_XB_9E>(data, sizeof(C_LoginExtended_XB_9E));
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;
ses->license = s->license_index->verify_xb(xb_gamertag, xb_user_id, xb_account_id);
ses->sub_version = cmd.sub_version;
ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language);
ses->xb_netloc = cmd.netloc;
ses->xb_9E_unknown_a1a = cmd.unknown_a1a;
ses->channel.send(0x9F, 0x00);
return;
} else if (command == 0x9F) {
const auto& cmd = check_size_t<C_ClientConfig_V3_9F>(data);
ses->config.parse_from(cmd.data);
} else {
throw runtime_error("command is not 9E or 9F");
}
} else if (ses->version == GameVersion::XB) {
throw runtime_error("xbox licenses are not implemented");
@@ -325,7 +342,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
}
const auto& cmd = check_size_t<C_Login_BB_93>(data);
try {
license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode());
ses->license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode());
} catch (const LicenseIndex::missing_license&) {
if (!s->allow_unregistered_users) {
throw;
@@ -336,11 +353,11 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
l->bb_password = cmd.password.decode();
s->license_index->add(l);
l->save();
license = l;
ses->license = l;
string l_str = l->str();
ses->log.info("Created license %s", l_str.c_str());
}
login_command_bb = std::move(data);
ses->login_command_bb = std::move(data);
} else {
throw logic_error("unsupported unlinked session version");
@@ -358,7 +375,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
// If license is non-null, then the client has a password and can be connected
// to the remote lobby server.
if (license.get()) {
if (ses->license.get()) {
// At this point, we will always close the unlinked session, even if it
// doesn't get converted/merged to a linked session
should_close_unlinked_session = true;
@@ -366,18 +383,18 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
// Look up the linked session for this license (if any)
shared_ptr<LinkedSession> linked_ses;
try {
linked_ses = server->id_to_session.at(license->serial_number);
linked_ses = server->id_to_session.at(ses->license->serial_number);
linked_ses->log.info("Resuming linked session from unlinked session");
} catch (const out_of_range&) {
// If there's no open session for this license, then there must be a valid
// destination somewhere - either in the client config or in the unlinked
// session
if (config.proxy_destination_address != 0) {
linked_ses.reset(new LinkedSession(server, ses->local_port, ses->version, license, config));
if (ses->config.proxy_destination_address != 0) {
linked_ses.reset(new LinkedSession(server, ses->local_port, ses->version, ses->license, ses->config));
linked_ses->log.info("Opened licensed session for unlinked session based on client config");
} else if (ses->next_destination.ss_family == AF_INET) {
linked_ses.reset(new LinkedSession(server, ses->local_port, ses->version, license, ses->next_destination));
linked_ses.reset(new LinkedSession(server, ses->local_port, ses->version, ses->license, ses->next_destination));
linked_ses->log.info("Opened licensed session for unlinked session based on unlinked default destination");
} else {
ses->log.error("Cannot open linked session: no valid destination in client config or unlinked session");
@@ -385,7 +402,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
}
if (linked_ses.get()) {
server->id_to_session.emplace(license->serial_number, linked_ses);
server->id_to_session.emplace(ses->license->serial_number, linked_ses);
if (linked_ses->version() != ses->version) {
linked_ses->log.error("Linked session has different game version");
} else {
@@ -395,14 +412,16 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
linked_ses->resume(
std::move(ses->channel),
ses->detector_crypt,
std::move(login_command_bb));
std::move(ses->login_command_bb));
} else {
linked_ses->resume(
std::move(ses->channel),
ses->detector_crypt,
sub_version,
character_name,
hardware_id);
ses->sub_version,
ses->character_name,
ses->hardware_id,
ses->xb_netloc,
ses->xb_9E_unknown_a1a);
}
} catch (const exception& e) {
linked_ses->log.error("Failed to resume linked session: %s", e.what());
@@ -526,24 +545,19 @@ 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,
uint32_t sub_version,
const string& character_name,
const string& hardware_id) {
const string& hardware_id,
const XBNetworkLocation& xb_netloc,
const parray<le_uint32_t, 3>& xb_9E_unknown_a1a) {
this->sub_version = sub_version;
this->character_name = character_name;
this->hardware_id = hardware_id;
this->xb_netloc = xb_netloc;
this->xb_9E_unknown_a1a = xb_9E_unknown_a1a;
this->resume_inner(std::move(client_channel), detector_crypt);
}
@@ -720,43 +734,31 @@ 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));
}
if (this->version() == GameVersion::XB) {
if (!this->wrapped_client) {
throw logic_error("wrapped client is missing from XB proxy session");
}
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();
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");
}
reconnect_cmd.address.store_raw(dest_sin->sin_addr.s_addr);
} else {
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");
}
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);
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");
}
this->client_channel.send(0x19, 0x00, &reconnect_cmd, sizeof(reconnect_cmd));
this->disconnect_action = DisconnectAction::CLOSE_IMMEDIATELY;
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;
}
}
@@ -836,7 +838,9 @@ 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,
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));
auto emplace_ret = this->id_to_session.emplace(session->id, session);
+18 -3
View File
@@ -61,6 +61,8 @@ public:
std::string character_name;
std::string hardware_id; // Only used for DC sessions
std::string login_command_bb;
XBNetworkLocation xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
uint32_t challenge_rank_color_override;
std::string challenge_rank_title_override;
@@ -91,7 +93,6 @@ public:
bool is_in_quest;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
std::shared_ptr<Client> wrapped_client;
struct SavingFile {
std::string basename;
@@ -145,13 +146,14 @@ 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,
uint32_t sub_version,
const std::string& character_name,
const std::string& hardware_id);
const std::string& hardware_id,
const XBNetworkLocation& xb_netloc,
const parray<le_uint32_t, 3>& xb_9E_unknown_a1a);
void resume(
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
@@ -222,6 +224,19 @@ private:
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
// Temporary state used just before resuming a LinkedSession. These aren't
// just local variables inside on_input because XB requires two commands to
// get started (9E and 9F), so we need to store this state somewhere between
// those commands.
std::shared_ptr<License> license;
uint32_t sub_version = 0;
std::string character_name;
Client::Config config;
std::string login_command_bb;
std::string hardware_id;
XBNetworkLocation xb_netloc;
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
UnlinkedSession(std::shared_ptr<ProxyServer> server, struct bufferevent* bev, uint16_t port, GameVersion version);
std::shared_ptr<ProxyServer> require_server() const;
+64 -59
View File
@@ -94,28 +94,16 @@ static shared_ptr<const Menu> proxy_options_menu_for_client(shared_ptr<const Cli
}
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);
}
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);
}
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);
}
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) {
@@ -137,12 +125,7 @@ void send_client_to_proxy_server(shared_ptr<Client> c) {
ses->remote_guild_card_number = 0;
}
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);
}
send_reconnect(c, s->connect_address_for_client(c), local_port);
}
static void send_proxy_destinations_menu(shared_ptr<Client> c) {
@@ -887,13 +870,6 @@ static void on_9E_XB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
}
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;
@@ -947,8 +923,9 @@ static void on_9E_XB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
c->log.info("Created license %s", l_str.c_str());
}
send_update_client_config(c);
on_login_complete(c);
// The 9E command doesn't include the client config, so we need to request it
// separately with a 9F command. The 9F handler will call on_login_complete.
send_command(c, 0x9F, 0x00);
}
static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
@@ -1045,13 +1022,35 @@ static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
}
static void on_9F_V3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
if (c->version() == GameVersion::BB) {
const auto& cmd = check_size_t<C_ClientConfig_BB_9F>(data);
c->config.parse_from(cmd.data);
} else {
const auto& cmd = check_size_t<C_ClientConfig_V3_9F>(data);
c->config.parse_from(cmd.data);
static void on_9F(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
switch (c->version()) {
case GameVersion::GC: {
const auto& cmd = check_size_t<C_ClientConfig_V3_9F>(data);
c->config.parse_from(cmd.data);
break;
}
case GameVersion::XB: {
const auto& cmd = check_size_t<C_ClientConfig_V3_9F>(data);
// On XB, this command is part of the login sequence, so we may not be
// able to import the config the first time the client connects. If we
// can't import the config, assume that the client was not connected to
// newserv before, so we should show the welcome message.
try {
c->config.parse_from(cmd.data);
} catch (const invalid_argument&) {
c->config.set_flag(Client::Flag::AT_WELCOME_MESSAGE);
}
send_update_client_config(c);
on_login_complete(c);
break;
}
case GameVersion::BB: {
const auto& cmd = check_size_t<C_ClientConfig_BB_9F>(data);
c->config.parse_from(cmd.data);
break;
}
default:
throw logic_error("incorrect client version for 9F command");
}
}
@@ -1063,20 +1062,6 @@ static void on_96(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
static void on_B1(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
check_size_v(data.size(), 0);
send_server_time(c);
// The B1 command is sent in response to a 97 command, which is normally part
// of the pre-ship-select login sequence. However, newserv delays this until
// after the ship select menu so that loading a GameCube program doesn't cause
// the player's items to be deleted when they next play PSO. It's also not a
// good idea to send a 97 and 19 at the same time, because the memory card and
// BBA are on the same EXI bus on the GameCube and this seems to cause the SYN
// packet after a 19 to get dropped pretty often, which causes a delay in
// joining the lobby. This is why we delay the 19 command until the client
// responds after saving.
if (c->should_send_to_lobby_server) {
send_client_to_lobby_server(c);
} else if (c->should_send_to_proxy_server) {
send_client_to_proxy_server(c);
}
}
static void on_BA_Ep3(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
@@ -3811,7 +3796,7 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
add_next_game_client(l);
}
static void on_99_GC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
static void on_99(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
check_size_v(data.size(), 0);
// This is an odd place to send 6xB4x52, but there's a reason for it. If the
@@ -3829,6 +3814,22 @@ static void on_99_GC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
send_ep3_update_game_metadata(watched_l);
}
}
// The 99 command is sent in response to a B1 command, which is normally part
// of the pre-ship-select login sequence. However, newserv delays the 97
// command (and therefore the following B1 command) until after the ship
// select menu so that loading a GameCube program doesn't cause the player's
// items to be deleted when they next play PSO. It's also not a good idea to
// send a 97 and 19 at the same time, because the memory card and BBA are on
// the same EXI bus on the GameCube and this seems to cause the SYN packet
// after a 19 to get dropped pretty often, which causes a delay in joining the
// lobby. This is why we delay the 19 command until the client responds after
// saving.
if (c->should_send_to_lobby_server) {
send_client_to_lobby_server(c);
} else if (c->should_send_to_proxy_server) {
send_client_to_proxy_server(c);
}
}
static void on_D0_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
@@ -4381,13 +4382,13 @@ static on_command_t handlers[0x100][6] = {
/* 96 */ {nullptr, on_96, on_96, on_96, on_96, nullptr},
/* 97 */ {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr},
/* 98 */ {nullptr, on_61_98, on_61_98, on_61_98, on_61_98, on_61_98},
/* 99 */ {nullptr, on_ignored, on_ignored, on_99_GC, on_ignored, on_ignored},
/* 99 */ {nullptr, on_99, on_99, on_99, on_99, on_99},
/* 9A */ {nullptr, on_9A, on_9A, on_9A, nullptr, nullptr},
/* 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_9E_XB, nullptr},
/* 9F */ {nullptr, nullptr, nullptr, on_9F_V3, on_9F_V3, nullptr},
/* 9F */ {nullptr, nullptr, nullptr, on_9F, on_9F, on_9F},
// PATCH DC PC GC XB BB
/* A0 */ {nullptr, on_A0, on_A0, on_A0, on_A0, on_A0},
/* A1 */ {nullptr, on_A1, on_A1, on_A1, on_A1, on_A1},
@@ -4504,7 +4505,6 @@ static void check_unlicensed_command(GameVersion version, uint8_t command) {
}
break;
case GameVersion::GC:
case GameVersion::XB:
// See comment in the DC case above for why DC commands are included here.
if (command != 0x88 && // DC NTE
command != 0x8B && // DC NTE
@@ -4518,6 +4518,11 @@ static void check_unlicensed_command(GameVersion version, uint8_t command) {
throw runtime_error("only commands 88, 8B, 90, 93, 9A, 9C, 9D, 9E, and DB may be sent before login");
}
break;
case GameVersion::XB:
if (command != 0x9E && command != 0x9F) {
throw runtime_error("only commands 9E and 9F may be sent before login");
}
break;
case GameVersion::BB:
if (command != 0x93) {
throw runtime_error("only command 93 may be sent before login");
+3 -3
View File
@@ -9,11 +9,11 @@
using namespace std;
const vector<string> version_to_login_port_name = {
"bb-patch", "console-login", "pc-login", "console-login", "console-login", "bb-init"};
"bb-patch", "console-login", "pc-login", "console-login", "xb-login", "bb-init"};
const vector<string> version_to_lobby_port_name = {
"bb-patch", "console-lobby", "pc-lobby", "console-lobby", "console-lobby", "bb-lobby"};
"bb-patch", "console-lobby", "pc-lobby", "console-lobby", "xb-lobby", "bb-lobby"};
const vector<string> version_to_proxy_port_name = {
"", "dc-proxy", "pc-proxy", "gc-proxy", "xb", "bb-proxy"};
"", "dc-proxy", "pc-proxy", "gc-proxy", "xb-proxy", "bb-proxy"};
const char* name_for_version(GameVersion version) {
switch (version) {