fix some basic things on BB proxy server

This commit is contained in:
Martin Michelsen
2022-05-04 11:57:49 -07:00
parent 2faf511e0d
commit 294c328e7a
10 changed files with 124 additions and 66 deletions
+6 -2
View File
@@ -130,7 +130,7 @@ struct C_Login_Patch_04 {
parray<le_uint32_t, 3> unused;
ptext<char, 0x10> username;
ptext<char, 0x10> password;
ptext<char, 0x40> email; // Note: this field is not present on BB
ptext<char, 0x40> email; // Note: this field is blank on BB
};
// 05: Disconnect
@@ -684,7 +684,11 @@ struct C_Login_BB_93 {
// will be something like "Ver. 1.24.3". Note also that some old versions
// (before 1.23.8?) omit the unknown field before the client config, so the
// client config starts 8 bytes earlier on those versions.
ClientConfigBB client_config;
union ClientConfigFields {
ClientConfigBB cfg;
ptext<char, 0x28> version_string;
ClientConfigFields() : version_string() { }
} client_config;
};
// 94: Invalid command
+55 -31
View File
@@ -31,29 +31,31 @@ bool use_terminal_colors = false;
static const vector<PortConfiguration> default_port_to_behavior({
{"gc-jp10", 9000, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-jp11", 9001, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-jp3", 9003, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-us10", 9100, GameVersion::PC, ServerBehavior::SPLIT_RECONNECT},
{"gc-us3", 9103, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu10", 9200, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu11", 9201, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu3", 9203, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"pc-login", 9300, GameVersion::PC, ServerBehavior::LOGIN_SERVER},
{"pc-patch", 10000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER},
{"bb-patch", 11000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER},
{"bb-data", 12000, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
{"gc-jp10", 9000, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-jp11", 9001, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-jp3", 9003, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-us10", 9100, GameVersion::PC, ServerBehavior::SPLIT_RECONNECT},
{"gc-us3", 9103, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu10", 9200, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu11", 9201, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu3", 9203, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"pc-login", 9300, GameVersion::PC, ServerBehavior::LOGIN_SERVER},
{"pc-patch", 10000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER},
{"bb-patch", 11000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER},
{"bb-init", 12000, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
{"bb-patch2", 13000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER},
{"bb-init2", 14000, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
// these aren't hardcoded in any games; user can override them
{"bb-data1", 12004, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
{"bb-data2", 12005, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
{"bb-login", 12008, GameVersion::BB, ServerBehavior::LOGIN_SERVER},
{"pc-lobby", 9420, GameVersion::PC, ServerBehavior::LOBBY_SERVER},
{"gc-lobby", 9421, GameVersion::GC, ServerBehavior::LOBBY_SERVER},
{"bb-lobby", 9422, GameVersion::BB, ServerBehavior::LOBBY_SERVER},
{"pc-proxy", 9520, GameVersion::PC, ServerBehavior::PROXY_SERVER},
{"gc-proxy", 9521, GameVersion::GC, ServerBehavior::PROXY_SERVER},
{"bb-proxy", 9522, GameVersion::BB, ServerBehavior::PROXY_SERVER},
// These aren't hardcoded in clients; user should be allowed to override them
{"bb-data1", 12004, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
{"bb-data2", 12005, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
{"bb-login", 12008, GameVersion::BB, ServerBehavior::LOGIN_SERVER},
{"pc-lobby", 9420, GameVersion::PC, ServerBehavior::LOBBY_SERVER},
{"gc-lobby", 9421, GameVersion::GC, ServerBehavior::LOBBY_SERVER},
{"bb-lobby", 9422, GameVersion::BB, ServerBehavior::LOBBY_SERVER},
{"pc-proxy", 9520, GameVersion::PC, ServerBehavior::PROXY_SERVER},
{"gc-proxy", 9521, GameVersion::GC, ServerBehavior::PROXY_SERVER},
{"bb-proxy", 9522, GameVersion::BB, ServerBehavior::PROXY_SERVER},
});
@@ -157,6 +159,19 @@ void populate_state_from_config(shared_ptr<ServerState> s,
s->proxy_destination_patch.first = "";
s->proxy_destination_patch.second = 0;
}
try {
const string& netloc_str = d.at("ProxyDestination-BB")->as_string();
s->proxy_destination_bb = parse_netloc(netloc_str);
log(INFO, "BB proxy is enabled with destination %s", netloc_str.c_str());
for (auto& it : s->name_to_port_config) {
if (it.second->version == GameVersion::BB) {
it.second->behavior = ServerBehavior::PROXY_SERVER;
}
}
} catch (const out_of_range&) {
s->proxy_destination_bb.first = "";
s->proxy_destination_bb.second = 0;
}
s->main_menu.emplace_back(MAIN_MENU_GO_TO_LOBBY, u"Go to lobby",
u"Join the lobby", 0);
@@ -322,30 +337,39 @@ int main(int, char**) {
}
shared_ptr<Server> game_server;
if (!state->proxy_destinations_pc.empty() ||
!state->proxy_destinations_gc.empty() ||
(state->proxy_destination_patch.second != 0)) {
log(INFO, "Starting proxy server");
state->proxy_server.reset(new ProxyServer(base, state));
}
log(INFO, "Starting game server");
game_server.reset(new Server(base, state));
log(INFO, "Opening sockets");
for (const auto& it : state->name_to_port_config) {
if (it.second->behavior == ServerBehavior::PROXY_SERVER) {
if (!state->proxy_server.get()) {
log(INFO, "Starting proxy server");
state->proxy_server.reset(new ProxyServer(base, state));
}
if (state->proxy_server.get()) {
// For PC and GC, proxy sessions are dynamically created when a client
// picks a destination from the menu. For patch and BB clients, there's
// no way to ask the client which destination they want, so only one
// destination is supported, and we have to manually specify the
// destination netloc here.
if (it.second->version == GameVersion::PATCH) {
struct sockaddr_storage ss = make_sockaddr_storage(
state->proxy_destination_patch.first,
state->proxy_destination_patch.second).first;
state->proxy_server->listen(it.second->port, it.second->version, &ss);
} if (it.second->version == GameVersion::BB) {
struct sockaddr_storage ss = make_sockaddr_storage(
state->proxy_destination_bb.first,
state->proxy_destination_bb.second).first;
state->proxy_server->listen(it.second->port, it.second->version, &ss);
} else {
state->proxy_server->listen(it.second->port, it.second->version);
}
}
} else {
if (!game_server.get()) {
log(INFO, "Starting game server");
game_server.reset(new Server(base, state));
}
game_server->listen("", it.second->port, it.second->version, it.second->behavior);
}
}
+3 -5
View File
@@ -155,7 +155,7 @@ void for_each_received_command(
// BB pads commands to 8-byte boundaries, and this is not reflected in the
// size field
size_t command_physical_size = (version == GameVersion::BB)
? (command_logical_size + header_size - 1) & ~(header_size - 1)
? ((command_logical_size + header_size - 1) & ~(header_size - 1))
: command_logical_size;
if (evbuffer_get_length(buf) < command_physical_size) {
break;
@@ -165,19 +165,17 @@ void for_each_received_command(
evbuffer_drain(buf, header_size);
string command_data(command_logical_size - header_size, '\0');
string command_data(command_physical_size - header_size, '\0');
if (evbuffer_remove(buf, command_data.data(), command_data.size())
< static_cast<ssize_t>(command_data.size())) {
throw logic_error("enough bytes available, but could not remove them");
}
if (command_logical_size != command_physical_size) {
evbuffer_drain(buf, command_physical_size - command_logical_size);
}
if (crypt) {
crypt->skip(header_size);
crypt->decrypt(command_data.data(), command_data.size());
}
command_data.resize(command_logical_size - header_size);
fn(header.command(version), header.flag(version), command_data);
}
+38 -7
View File
@@ -115,8 +115,7 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
// Most servers don't include after_message or have a shorter
// after_message than newserv does, so don't require it
const auto& cmd = check_size_t<S_ServerInit_DC_PC_GC_02_17>(data,
offsetof(S_ServerInit_DC_PC_GC_02_17, after_message),
sizeof(S_ServerInit_DC_PC_GC_02_17));
offsetof(S_ServerInit_DC_PC_GC_02_17, after_message), 0xFFFF);
if (!session.license) {
session.log(INFO, "No license in linked session");
@@ -202,6 +201,38 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
}
}
static bool process_server_bb_03(shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) {
// Most servers don't include after_message or have a shorter
// after_message than newserv does, so don't require it
const auto& cmd = check_size_t<S_ServerInit_BB_03>(data,
offsetof(S_ServerInit_BB_03, after_message), 0xFFFF);
// Unlike on PC/GC, BB linked sessions never have licenses.
// TODO: Is there any way we can support this in the future? Probably not, due
// to BB's login and character select flow, right?
if (session.license) {
throw runtime_error("BB linked session has license");
}
session.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.
forward_command(session, false, command, flag, data);
// BB encryption is stateless after it's initialized, unlike previous
// versions, so we can get away with only two instances instead of four here.
session.server_input_crypt.reset(new PSOBBEncryption(
s->default_key_file, cmd.server_key.data(), sizeof(cmd.server_key)));
session.server_output_crypt.reset(new PSOBBEncryption(
s->default_key_file, cmd.client_key.data(), sizeof(cmd.client_key)));
session.client_input_crypt = session.server_output_crypt;
session.client_output_crypt = session.server_input_crypt;
return false;
}
static bool process_server_dc_pc_gc_04(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
// Some servers send a short 04 command if they don't use all of the 0x20
@@ -401,7 +432,7 @@ static bool process_server_gc_1A_D5(shared_ptr<ServerState>,
// If the client has the no-close-confirmation flag set in its
// newserv client config, send a fake confirmation to the remote
// server immediately.
if (session.newserv_client_config.flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION) {
if (session.newserv_client_config.cfg.flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION) {
session.send_to_end(true, 0xD6, 0x00, "", 0);
}
return true;
@@ -514,8 +545,8 @@ static bool process_server_65_67_68(shared_ptr<ServerState>,
// behavior in the client config, so if it happens during a proxy session,
// update the client config that we'll restore if the client uses the change
// ship or change block command.
if (session.newserv_client_config.flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN) {
session.newserv_client_config.flags |= Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION;
if (session.newserv_client_config.cfg.flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN) {
session.newserv_client_config.cfg.flags |= Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION;
}
}
@@ -714,7 +745,7 @@ static bool process_client_dc_pc_gc_A0_A1(shared_ptr<ServerState> s,
S_UpdateClientConfig_DC_PC_GC_04 update_client_config_cmd = {
0x00010000,
session.license->serial_number,
session.newserv_client_config,
session.newserv_client_config.cfg,
};
session.send_to_end(false, 0x04, 0x00, &update_client_config_cmd, sizeof(update_client_config_cmd));
@@ -824,7 +855,7 @@ static process_command_t gc_server_handlers[0x100] = {
/* F0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
};
static process_command_t bb_server_handlers[0x100] = {
/* 00 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 00 */ defh, defh, defh, process_server_bb_03, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, defh, defh, process_server_19, defh, defh, defh, defh, defh, defh,
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
+7 -7
View File
@@ -240,7 +240,7 @@ void ProxyServer::UnlinkedSession::on_client_input() {
shared_ptr<const License> license;
uint32_t sub_version = 0;
string character_name;
ClientConfig client_config;
ClientConfigBB client_config;
try {
for_each_received_command(this->bev.get(), this->version, this->crypt_in.get(),
@@ -271,7 +271,7 @@ void ProxyServer::UnlinkedSession::on_client_input() {
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
sub_version = cmd.sub_version;
character_name = cmd.name;
client_config = cmd.client_config.cfg;
client_config.cfg = cmd.client_config.cfg;
} else {
throw logic_error("unsupported unlinked session version");
@@ -302,7 +302,7 @@ void ProxyServer::UnlinkedSession::on_client_input() {
// If there's no open session for this license, then there must be a valid
// destination in the client config. If there is, open a new linked
// session and set its initial destination
if (client_config.magic != CLIENT_CONFIG_MAGIC) {
if (client_config.cfg.magic != CLIENT_CONFIG_MAGIC) {
this->log(ERROR, "Client configuration is invalid; cannot open session");
} else {
session.reset(new LinkedSession(
@@ -388,15 +388,15 @@ ProxyServer::LinkedSession::LinkedSession(
uint16_t local_port,
GameVersion version,
std::shared_ptr<const License> license,
const ClientConfig& newserv_client_config)
const ClientConfigBB& newserv_client_config)
: LinkedSession(server, license->serial_number, local_port, version) {
this->license = license;
this->newserv_client_config = newserv_client_config;
memset(&this->next_destination, 0, sizeof(this->next_destination));
struct sockaddr_in* dest_sin = reinterpret_cast<struct sockaddr_in*>(&this->next_destination);
dest_sin->sin_family = AF_INET;
dest_sin->sin_port = htons(this->newserv_client_config.proxy_destination_port);
dest_sin->sin_addr.s_addr = htonl(this->newserv_client_config.proxy_destination_address);
dest_sin->sin_port = htons(this->newserv_client_config.cfg.proxy_destination_port);
dest_sin->sin_addr.s_addr = htonl(this->newserv_client_config.cfg.proxy_destination_address);
}
ProxyServer::LinkedSession::LinkedSession(
@@ -668,7 +668,7 @@ shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session() {
std::shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
std::shared_ptr<const License> l, uint16_t local_port, GameVersion version,
const ClientConfig& newserv_client_config) {
const ClientConfigBB& newserv_client_config) {
shared_ptr<LinkedSession> session(new LinkedSession(
this, local_port, version, l, newserv_client_config));
auto emplace_ret = this->id_to_session.emplace(session->id, session);
+3 -3
View File
@@ -53,7 +53,7 @@ public:
uint32_t remote_guild_card_number;
parray<uint8_t, 0x20> remote_client_config_data;
ClientConfig newserv_client_config;
ClientConfigBB newserv_client_config;
bool suppress_newserv_commands;
bool enable_chat_filter;
bool enable_switch_assist;
@@ -101,7 +101,7 @@ public:
uint16_t local_port,
GameVersion version,
std::shared_ptr<const License> license,
const ClientConfig& newserv_client_config);
const ClientConfigBB& newserv_client_config);
LinkedSession(
ProxyServer* server,
uint64_t id,
@@ -148,7 +148,7 @@ public:
std::shared_ptr<const License> l,
uint16_t local_port,
GameVersion version,
const ClientConfig& newserv_client_config);
const ClientConfigBB& newserv_client_config);
void delete_session(uint64_t id);
private:
+4 -7
View File
@@ -367,7 +367,7 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
}
try {
c->import_config(cmd.client_config.cfg);
c->import_config(cmd.client_config.cfg.cfg);
c->bb_game_state++;
} catch (const invalid_argument&) {
c->bb_game_state = 0;
@@ -749,7 +749,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
s->proxy_server->delete_session(c->license->serial_number);
s->proxy_server->create_licensed_session(
c->license, local_port, c->version, c->export_config());
c->license, local_port, c->version, c->export_config_bb());
send_reconnect(c, s->connect_address_for_client(c), local_port);
}
@@ -1751,13 +1751,10 @@ void process_encryption_ok_patch(shared_ptr<ServerState>, shared_ptr<Client> c,
void process_login_patch(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) {
const auto& cmd = check_size_t<C_Login_Patch_04>(data,
offsetof(C_Login_Patch_04, email), sizeof(C_Login_Patch_04));
const auto& cmd = check_size_t<C_Login_Patch_04>(data);
if (data.size() == offsetof(C_Login_Patch_04, email)) {
if (cmd.email.len() == 0) {
c->flags |= Client::Flag::BB_PATCH;
} else if (data.size() != sizeof(C_Login_Patch_04)) {
throw runtime_error("unknown patch server login format");
}
// On BB we can use colors and newlines should be \n; on PC we can't use
+4 -4
View File
@@ -66,7 +66,7 @@ Server commands:\n\
license is deleted by reloading, they will not be disconnected immediately.\n\
add-license <parameters>\n\
Add a license to the server. <parameters> is some subset of the following:\n\
username=<username> (BB username)\n\
bb-username=<username> (BB username)\n\
bb-password=<password> (BB password)\n\
gc-password=<password> (GC password)\n\
access-key=<access-key> (GC/PC access key)\n\
@@ -179,11 +179,11 @@ Proxy commands (these will only work when exactly one client is connected):\n\
shared_ptr<License> l(new License());
for (const string& token : split(command_args, ' ')) {
if (starts_with(token, "username=")) {
if (token.size() >= 29) {
if (starts_with(token, "bb-username=")) {
if (token.size() >= 32) {
throw invalid_argument("username too long");
}
l->username = token.substr(9);
l->username = token.substr(12);
} else if (starts_with(token, "bb-password=")) {
if (token.size() >= 32) {
+1
View File
@@ -61,6 +61,7 @@ struct ServerState {
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_pc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations_gc;
std::pair<std::string, uint16_t> proxy_destination_patch;
std::pair<std::string, uint16_t> proxy_destination_bb;
std::u16string welcome_message;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
+3
View File
@@ -34,6 +34,9 @@
// patch server (for PC and BB) is bypassed, and any client that connects to
// the patch server is instead proxied to this destination.
// "ProxyDestination-Patch": "",
// Proxy destination for BB clients. If this is given, all BB clients that
// connect to newserv will be proxied to this destination.
// "ProxyDestination-BB": "",
// By default, the interactive shell runs if stdin is a terminal, and doesn't
// run if it's not. This option, if present, overrides that behavior.