support multiple versions on proxy server

This commit is contained in:
Martin Michelsen
2022-04-01 21:24:07 -07:00
parent 37b8f1cffa
commit 3f5f2fc61d
10 changed files with 520 additions and 245 deletions
+1
View File
@@ -96,6 +96,7 @@ struct C_Login_Patch_04 {
parray<le_uint32_t, 3> unused; parray<le_uint32_t, 3> unused;
ptext<char, 0x10> username; ptext<char, 0x10> username;
ptext<char, 0x10> password; ptext<char, 0x10> password;
ptext<char, 0x40> email; // Note: this field is not present on BB
}; };
// 05: Disconnect // 05: Disconnect
+2 -2
View File
@@ -759,9 +759,9 @@ void IPStackSimulator::open_server_connection(
// Link the client to the server - the server sees this as a normal TCP // Link the client to the server - the server sees this as a normal TCP
// connection and treats it as if the client connected to one of its listening // connection and treats it as if the client connected to one of its listening
// sockets // sockets
const PortConfiguration* port_config; shared_ptr<const PortConfiguration> port_config;
try { try {
port_config = &this->state->numbered_port_configuration.at(conn.server_port); port_config = this->state->number_to_port_config.at(conn.server_port);
} catch (const out_of_range&) { } catch (const out_of_range&) {
bufferevent_free(bevs[1]); bufferevent_free(bevs[1]);
throw logic_error("client connected to port missing from configuration"); throw logic_error("client connected to port missing from configuration");
+65 -31
View File
@@ -29,30 +29,30 @@ FileContentsCache file_cache;
static const unordered_map<string, PortConfiguration> default_port_to_behavior({ static const vector<PortConfiguration> default_port_to_behavior({
{"gc-jp10", {9000, GameVersion::GC, ServerBehavior::LOGIN_SERVER}}, {"gc-jp10", 9000, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-jp11", {9001, GameVersion::GC, ServerBehavior::LOGIN_SERVER}}, {"gc-jp11", 9001, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-jp3", {9003, GameVersion::GC, ServerBehavior::LOGIN_SERVER}}, {"gc-jp3", 9003, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-us10", {9100, GameVersion::PC, ServerBehavior::SPLIT_RECONNECT}}, {"gc-us10", 9100, GameVersion::PC, ServerBehavior::SPLIT_RECONNECT},
{"gc-us3", {9103, GameVersion::GC, ServerBehavior::LOGIN_SERVER}}, {"gc-us3", 9103, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu10", {9200, GameVersion::GC, ServerBehavior::LOGIN_SERVER}}, {"gc-eu10", 9200, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu11", {9201, GameVersion::GC, ServerBehavior::LOGIN_SERVER}}, {"gc-eu11", 9201, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"gc-eu3", {9203, GameVersion::GC, ServerBehavior::LOGIN_SERVER}}, {"gc-eu3", 9203, GameVersion::GC, ServerBehavior::LOGIN_SERVER},
{"pc-login", {9300, GameVersion::PC, ServerBehavior::LOGIN_SERVER}}, {"pc-login", 9300, GameVersion::PC, ServerBehavior::LOGIN_SERVER},
{"pc-patch", {10000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER}}, {"pc-patch", 10000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER},
{"bb-patch", {11000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER}}, {"bb-patch", 11000, GameVersion::PATCH, ServerBehavior::PATCH_SERVER},
{"bb-data", {12000, GameVersion::BB, ServerBehavior::DATA_SERVER_BB}}, {"bb-data", 12000, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
// these aren't hardcoded in any games; user can override them // these aren't hardcoded in any games; user can override them
{"bb-data1", {12004, GameVersion::BB, ServerBehavior::DATA_SERVER_BB}}, {"bb-data1", 12004, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
{"bb-data2", {12005, GameVersion::BB, ServerBehavior::DATA_SERVER_BB}}, {"bb-data2", 12005, GameVersion::BB, ServerBehavior::DATA_SERVER_BB},
{"bb-login", {12008, GameVersion::BB, ServerBehavior::LOGIN_SERVER}}, {"bb-login", 12008, GameVersion::BB, ServerBehavior::LOGIN_SERVER},
{"pc-lobby", {9420, GameVersion::PC, ServerBehavior::LOBBY_SERVER}}, {"pc-lobby", 9420, GameVersion::PC, ServerBehavior::LOBBY_SERVER},
{"gc-lobby", {9421, GameVersion::GC, ServerBehavior::LOBBY_SERVER}}, {"gc-lobby", 9421, GameVersion::GC, ServerBehavior::LOBBY_SERVER},
{"bb-lobby", {9422, GameVersion::BB, ServerBehavior::LOBBY_SERVER}}, {"bb-lobby", 9422, GameVersion::BB, ServerBehavior::LOBBY_SERVER},
{"pc-proxy", {9520, GameVersion::PC, ServerBehavior::PROXY_SERVER}}, {"pc-proxy", 9520, GameVersion::PC, ServerBehavior::PROXY_SERVER},
{"gc-proxy", {9521, GameVersion::GC, ServerBehavior::PROXY_SERVER}}, {"gc-proxy", 9521, GameVersion::GC, ServerBehavior::PROXY_SERVER},
{"bb-proxy", {9522, GameVersion::BB, ServerBehavior::PROXY_SERVER}}, {"bb-proxy", 9522, GameVersion::BB, ServerBehavior::PROXY_SERVER},
}); });
@@ -115,18 +115,43 @@ void populate_state_from_config(shared_ptr<ServerState> s,
s->information_menu = information_menu; s->information_menu = information_menu;
s->information_contents = information_contents; s->information_contents = information_contents;
s->proxy_destinations_menu.emplace_back(PROXY_DESTINATIONS_MENU_GO_BACK, s->proxy_destinations_menu_pc.emplace_back(PROXY_DESTINATIONS_MENU_GO_BACK,
u"Go back", u"Return to the\nmain menu", 0);
s->proxy_destinations_menu_gc.emplace_back(PROXY_DESTINATIONS_MENU_GO_BACK,
u"Go back", u"Return to the\nmain menu", 0); u"Go back", u"Return to the\nmain menu", 0);
{ {
uint32_t item_id = 0; uint32_t item_id = 0;
for (const auto& item : d.at("ProxyDestinations")->as_dict()) { for (const auto& item : d.at("ProxyDestinations-GC")->as_dict()) {
const string& netloc_str = item.second->as_string(); const string& netloc_str = item.second->as_string();
s->proxy_destinations_menu.emplace_back(item_id, decode_sjis(item.first), s->proxy_destinations_menu_gc.emplace_back(item_id, decode_sjis(item.first),
decode_sjis(netloc_str), 0); decode_sjis(netloc_str), 0);
s->proxy_destinations.emplace_back(parse_netloc(netloc_str)); s->proxy_destinations_gc.emplace_back(parse_netloc(netloc_str));
item_id++; item_id++;
} }
} }
{
uint32_t item_id = 0;
for (const auto& item : d.at("ProxyDestinations-PC")->as_dict()) {
const string& netloc_str = item.second->as_string();
s->proxy_destinations_menu_pc.emplace_back(item_id, decode_sjis(item.first),
decode_sjis(netloc_str), 0);
s->proxy_destinations_pc.emplace_back(parse_netloc(netloc_str));
item_id++;
}
}
try {
const string& netloc_str = d.at("ProxyDestination-Patch")->as_string();
s->proxy_destination_patch = parse_netloc(netloc_str);
log(INFO, "Patch server proxy is enabled with destination %s", netloc_str.c_str());
for (auto& it : s->name_to_port_config) {
if (it.second->version == GameVersion::PATCH) {
it.second->behavior = ServerBehavior::PROXY_SERVER;
}
}
} catch (const out_of_range&) {
s->proxy_destination_patch.first = "";
s->proxy_destination_patch.second = 0;
}
s->main_menu.emplace_back(MAIN_MENU_GO_TO_LOBBY, u"Go to lobby", s->main_menu.emplace_back(MAIN_MENU_GO_TO_LOBBY, u"Go to lobby",
u"Join the lobby", 0); u"Join the lobby", 0);
@@ -288,7 +313,9 @@ int main(int, char**) {
} }
shared_ptr<Server> game_server; shared_ptr<Server> game_server;
if (!state->proxy_destinations.empty()) { if (!state->proxy_destinations_pc.empty() ||
!state->proxy_destinations_gc.empty() ||
(state->proxy_destination_patch.second != 0)) {
log(INFO, "Starting proxy server"); log(INFO, "Starting proxy server");
state->proxy_server.reset(new ProxyServer(base, state)); state->proxy_server.reset(new ProxyServer(base, state));
} }
@@ -297,13 +324,20 @@ int main(int, char**) {
game_server.reset(new Server(base, state)); game_server.reset(new Server(base, state));
log(INFO, "Opening sockets"); log(INFO, "Opening sockets");
for (const auto& it : state->named_port_configuration) { for (const auto& it : state->name_to_port_config) {
if (it.second.behavior == ServerBehavior::PROXY_SERVER) { if (it.second->behavior == ServerBehavior::PROXY_SERVER) {
if (state->proxy_server.get()) { if (state->proxy_server.get()) {
state->proxy_server->listen(it.second.port, it.second.version); 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);
} else {
state->proxy_server->listen(it.second->port, it.second->version);
}
} }
} else { } else {
game_server->listen("", it.second.port, it.second.version, it.second.behavior); game_server->listen("", it.second->port, it.second->version, it.second->behavior);
} }
} }
+344 -179
View File
@@ -47,36 +47,49 @@ ProxyServer::ProxyServer(
shared_ptr<ServerState> state) shared_ptr<ServerState> state)
: save_files(false), : save_files(false),
base(base), base(base),
state(state) { } state(state),
next_unlicensed_session_id(0xFF00000000000001) { }
void ProxyServer::listen(uint16_t port, GameVersion version) { void ProxyServer::listen(uint16_t port, GameVersion version,
int fd = ::listen("", port, SOMAXCONN); const struct sockaddr_storage* default_destination) {
if (fd < 0) { shared_ptr<ListeningSocket> socket_obj(new ListeningSocket(
this, port, version, default_destination));
auto l = this->listeners.emplace(port, socket_obj).first->second;
log(INFO, "[ProxyServer] Listening on TCP port %hu (%s) on fd %d",
port, name_for_version(version), static_cast<int>(l->fd));
}
ProxyServer::ListeningSocket::ListeningSocket(
ProxyServer* server,
uint16_t port,
GameVersion version,
const struct sockaddr_storage* default_destination)
: server(server),
port(port),
fd(::listen("", port, SOMAXCONN)),
listener(nullptr, evconnlistener_free),
version(version) {
if (!this->fd.is_open()) {
throw runtime_error("cannot listen on port"); throw runtime_error("cannot listen on port");
} }
auto* listener = evconnlistener_new(this->base.get(), this->listener.reset(evconnlistener_new(
&ProxyServer::ListeningSocket::dispatch_on_listen_accept, this, this->server->base.get(),
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 0, fd); &ProxyServer::ListeningSocket::dispatch_on_listen_accept,
this,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
0,
this->fd));
if (!listener) { if (!listener) {
close(fd);
throw runtime_error("cannot create listener"); throw runtime_error("cannot create listener");
} }
evconnlistener_set_error_cb( evconnlistener_set_error_cb(
listener, &ProxyServer::ListeningSocket::dispatch_on_listen_error); this->listener.get(), &ProxyServer::ListeningSocket::dispatch_on_listen_error);
unique_ptr<struct evconnlistener, void(*)(struct evconnlistener*)> evlistener( if (default_destination) {
listener, evconnlistener_free); this->default_destination = *default_destination;
shared_ptr<ListeningSocket> socket_obj(new ListeningSocket({ } else {
.server = this, this->default_destination.ss_family = 0;
.fd = fd, }
.port = port,
.listener = move(evlistener),
.version = version,
}));
this->listeners.emplace(port, socket_obj);
log(INFO, "[ProxyServer] Listening on TCP port %hu (%s) on fd %d",
port, name_for_version(version), fd);
} }
void ProxyServer::ListeningSocket::dispatch_on_listen_accept( void ProxyServer::ListeningSocket::dispatch_on_listen_accept(
@@ -89,11 +102,12 @@ void ProxyServer::ListeningSocket::dispatch_on_listen_error(
reinterpret_cast<ListeningSocket*>(ctx)->on_listen_error(); reinterpret_cast<ListeningSocket*>(ctx)->on_listen_error();
} }
void ProxyServer::ListeningSocket::on_listen_accept(struct sockaddr*, int) { void ProxyServer::ListeningSocket::on_listen_accept(struct sockaddr*, int fd) {
log(INFO, "[ProxyServer] Client connected on fd %d", fd); log(INFO, "[ProxyServer] Client connected on fd %d", fd);
auto* bev = bufferevent_socket_new(this->server->base.get(), fd, auto* bev = bufferevent_socket_new(this->server->base.get(), fd,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
this->server->on_client_connect(bev, this->port, this->version); this->server->on_client_connect(bev, this->port, this->version,
(this->default_destination.ss_family == AF_INET) ? &this->default_destination : nullptr);
} }
void ProxyServer::ListeningSocket::on_listen_error() { void ProxyServer::ListeningSocket::on_listen_error() {
@@ -107,7 +121,8 @@ void ProxyServer::ListeningSocket::on_listen_error() {
void ProxyServer::connect_client(struct bufferevent* bev, uint16_t server_port) { void ProxyServer::connect_client(struct bufferevent* bev, uint16_t server_port) {
// Look up the listening socket for the given port, and use that game version // Look up the listening socket for the given port, and use that game version.
// We don't support default-destination proxying for virtual connections (yet)
GameVersion version; GameVersion version;
try { try {
version = this->listeners.at(server_port)->version; version = this->listeners.at(server_port)->version;
@@ -119,35 +134,63 @@ void ProxyServer::connect_client(struct bufferevent* bev, uint16_t server_port)
} }
log(INFO, "[ProxyServer] Client connected on virtual connection %p", bev); log(INFO, "[ProxyServer] Client connected on virtual connection %p", bev);
this->on_client_connect(bev, server_port, version); this->on_client_connect(bev, server_port, version, nullptr);
} }
void ProxyServer::on_client_connect( void ProxyServer::on_client_connect(
struct bufferevent* bev, uint16_t listen_port, GameVersion version) { struct bufferevent* bev,
auto emplace_ret = this->bev_to_unlinked_session.emplace(bev, new UnlinkedSession( uint16_t listen_port,
this, bev, listen_port, version)); GameVersion version,
if (!emplace_ret.second) { const struct sockaddr_storage* default_destination) {
throw logic_error("stale unlinked session exists"); // If a default destination exists for this client, create a linked session
} // immediately and connect to the remote server. This creates a direct session
auto session = emplace_ret.first->second; if (default_destination) {
uint64_t session_id = this->next_unlicensed_session_id++;
switch (version) { if (this->next_unlicensed_session_id == 0) {
case GameVersion::GC: { this->next_unlicensed_session_id = 0xFF00000000000001;
uint32_t server_key = random_object<uint32_t>(); }
uint32_t client_key = random_object<uint32_t>();
auto cmd = prepare_server_init_contents_dc_pc_gc( auto emplace_ret = this->id_to_session.emplace(session_id, new LinkedSession(
false, server_key, client_key); this, session_id, listen_port, version, *default_destination));
send_command(session->bev.get(), session->version, if (!emplace_ret.second) {
session->crypt_out.get(), 0x02, 0, &cmd, sizeof(cmd), throw logic_error("linked session already exists for unlicensed client");
"unlinked proxy client"); }
session->crypt_out.reset(new PSOGCEncryption(server_key)); auto session = emplace_ret.first->second;
session->crypt_in.reset(new PSOGCEncryption(client_key)); log(INFO, "[ProxyServer/%08" PRIX64 "] Opened session", session->id);
break; session->resume(bev);
// If no default destination exists, create an unlinked session - we'll have
// to get the destination from the client's config, which we'll get via a 9E
// command soon
} else {
auto emplace_ret = this->bev_to_unlinked_session.emplace(bev, new UnlinkedSession(
this, bev, listen_port, version));
if (!emplace_ret.second) {
throw logic_error("stale unlinked session exists");
}
auto session = emplace_ret.first->second;
switch (version) {
case GameVersion::PATCH:
throw logic_error("cannot create unlinked patch session");
case GameVersion::PC:
case GameVersion::GC: {
uint32_t server_key = random_object<uint32_t>();
uint32_t client_key = random_object<uint32_t>();
auto cmd = prepare_server_init_contents_dc_pc_gc(
false, server_key, client_key);
send_command(session->bev.get(), session->version,
session->crypt_out.get(), 0x02, 0, &cmd, sizeof(cmd),
"unlinked proxy client");
session->crypt_out.reset(new PSOGCEncryption(server_key));
session->crypt_in.reset(new PSOGCEncryption(client_key));
break;
}
default:
throw logic_error("unsupported game version on proxy server");
} }
default:
throw logic_error("unsupported game version on proxy server");
} }
} }
@@ -225,15 +268,14 @@ void ProxyServer::UnlinkedSession::on_client_input() {
// Look up the linked session for this license (if any) // Look up the linked session for this license (if any)
shared_ptr<LinkedSession> session; shared_ptr<LinkedSession> session;
try { try {
session = this->server->serial_number_to_session.at(license->serial_number); session = this->server->id_to_session.at(license->serial_number);
} catch (const out_of_range&) { } catch (const out_of_range&) {
// If there's no open session for this license, then there must be a valid // 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 // destination in the client config. If there is, open a new linked
// session and set its initial destination // session and set its initial destination
if (client_config.magic != CLIENT_CONFIG_MAGIC) { if (client_config.magic != CLIENT_CONFIG_MAGIC) {
log(ERROR, "[ProxyServer/%08" PRIX32 "] Client configuration is invalid; cannot open session", log(ERROR, "[ProxyServer] Client configuration is invalid; cannot open session");
license->serial_number);
} else { } else {
session.reset(new LinkedSession( session.reset(new LinkedSession(
this->server, this->server,
@@ -241,16 +283,14 @@ void ProxyServer::UnlinkedSession::on_client_input() {
this->version, this->version,
license, license,
client_config)); client_config));
this->server->serial_number_to_session.emplace( this->server->id_to_session.emplace(license->serial_number, session);
license->serial_number, session); log(INFO, "[ProxyServer/%08" PRIX64 "] Opened session", session->id);
log(INFO, "[ProxyServer/%08" PRIX32 "] Opened session",
license->serial_number);
} }
} }
if (session.get() && (session->version != this->version)) { if (session.get() && (session->version != this->version)) {
log(ERROR, "[ProxyServer/%08" PRIX32 "] Linked session has different game version", log(ERROR, "[ProxyServer/%08" PRIX64 "] Linked session has different game version",
session->license->serial_number); session->id);
} else { } else {
// Resume the linked session using the unlinked session // Resume the linked session using the unlinked session
try { try {
@@ -258,8 +298,8 @@ void ProxyServer::UnlinkedSession::on_client_input() {
this->crypt_in.reset(); this->crypt_in.reset();
this->crypt_out.reset(); this->crypt_out.reset();
} catch (const exception& e) { } catch (const exception& e) {
log(ERROR, "[ProxyServer/%08" PRIX32 "] Failed to resume linked session: %s", log(ERROR, "[ProxyServer/%08" PRIX64 "] Failed to resume linked session: %s",
session->license->serial_number, e.what()); session->id, e.what());
} }
} }
} }
@@ -286,28 +326,36 @@ void ProxyServer::UnlinkedSession::on_client_error(short events) {
ProxyServer::LinkedSession::LinkedSession( ProxyServer::LinkedSession::LinkedSession(
ProxyServer* server, ProxyServer* server,
uint64_t id,
uint16_t local_port, uint16_t local_port,
GameVersion version, GameVersion version)
std::shared_ptr<const License> license,
const ClientConfig& newserv_client_config)
: server(server), : server(server),
id(id),
timeout_event(event_new(this->server->base.get(), -1, EV_TIMEOUT, timeout_event(event_new(this->server->base.get(), -1, EV_TIMEOUT,
&LinkedSession::dispatch_on_timeout, this), event_free), &LinkedSession::dispatch_on_timeout, this), event_free),
license(license), license(nullptr),
client_bev(nullptr, flush_and_free_bufferevent), client_bev(nullptr, flush_and_free_bufferevent),
server_bev(nullptr, flush_and_free_bufferevent), server_bev(nullptr, flush_and_free_bufferevent),
local_port(local_port), local_port(local_port),
version(version), version(version),
sub_version(0), // This is set during resume() sub_version(0), // This is set during resume()
guild_card_number(0), guild_card_number(0),
newserv_client_config(newserv_client_config),
suppress_newserv_commands(true), suppress_newserv_commands(true),
enable_chat_filter(true), enable_chat_filter(true),
override_section_id(-1), override_section_id(-1),
override_lobby_event(-1), override_lobby_event(-1),
override_lobby_number(-1), override_lobby_number(-1),
lobby_players(12), lobby_players(12),
lobby_client_id(0) { lobby_client_id(0) { }
ProxyServer::LinkedSession::LinkedSession(
ProxyServer* server,
uint16_t local_port,
GameVersion version,
std::shared_ptr<const License> license,
const ClientConfig& newserv_client_config)
: LinkedSession(server, license->serial_number, local_port, version) {
this->newserv_client_config = newserv_client_config;
memset(&this->next_destination, 0, sizeof(this->next_destination)); memset(&this->next_destination, 0, sizeof(this->next_destination));
struct sockaddr_in* dest_sin = reinterpret_cast<struct sockaddr_in*>(&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_family = AF_INET;
@@ -315,6 +363,16 @@ ProxyServer::LinkedSession::LinkedSession(
dest_sin->sin_addr.s_addr = htonl(this->newserv_client_config.proxy_destination_address); dest_sin->sin_addr.s_addr = htonl(this->newserv_client_config.proxy_destination_address);
} }
ProxyServer::LinkedSession::LinkedSession(
ProxyServer* server,
uint64_t id,
uint16_t local_port,
GameVersion version,
const struct sockaddr_storage& destination)
: LinkedSession(server, id, local_port, version) {
this->next_destination = destination;
}
void ProxyServer::LinkedSession::resume( void ProxyServer::LinkedSession::resume(
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev, std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
std::shared_ptr<PSOEncryption> client_input_crypt, std::shared_ptr<PSOEncryption> client_input_crypt,
@@ -324,6 +382,9 @@ void ProxyServer::LinkedSession::resume(
if (this->client_bev.get()) { if (this->client_bev.get()) {
throw runtime_error("client connection is already open for this session"); throw runtime_error("client connection is already open for this session");
} }
if (this->next_destination.ss_family != AF_INET) {
throw logic_error("attempted to resume an unlicensed linked session wihout destination set");
}
this->client_bev = move(client_bev); this->client_bev = move(client_bev);
bufferevent_setcb(this->client_bev.get(), bufferevent_setcb(this->client_bev.get(),
@@ -337,9 +398,34 @@ void ProxyServer::LinkedSession::resume(
this->character_name = character_name; this->character_name = character_name;
this->server_input_crypt.reset(); this->server_input_crypt.reset();
this->server_output_crypt.reset(); this->server_output_crypt.reset();
this->saving_files.clear(); this->saving_files.clear();
this->connect();
}
void ProxyServer::LinkedSession::resume(struct bufferevent* client_bev) {
if (this->client_bev.get()) {
throw runtime_error("client connection is already open for this session");
}
this->client_bev.reset(client_bev);
bufferevent_setcb(this->client_bev.get(),
&ProxyServer::LinkedSession::dispatch_on_client_input, nullptr,
&ProxyServer::LinkedSession::dispatch_on_client_error, this);
bufferevent_enable(this->client_bev.get(), EV_READ | EV_WRITE);
this->client_input_crypt.reset();
this->client_output_crypt.reset();
this->server_input_crypt.reset();
this->server_output_crypt.reset();
this->sub_version = 0;
this->character_name.clear();
this->saving_files.clear();
this->connect();
}
void ProxyServer::LinkedSession::connect() {
// Connect to the remote server. The command handlers will do the login steps // Connect to the remote server. The command handlers will do the login steps
// and set up forwarding // and set up forwarding
this->server_bev.reset(bufferevent_socket_new(this->server->base.get(), -1, this->server_bev.reset(bufferevent_socket_new(this->server->base.get(), -1,
@@ -357,7 +443,7 @@ void ProxyServer::LinkedSession::resume(
local_sin->sin_addr.s_addr = dest_sin->sin_addr.s_addr; local_sin->sin_addr.s_addr = dest_sin->sin_addr.s_addr;
string netloc_str = render_sockaddr_storage(local_ss); string netloc_str = render_sockaddr_storage(local_ss);
log(INFO, "[ProxyServer/%08" PRIX32 "] Connecting to %s", this->license->serial_number, netloc_str.c_str()); log(INFO, "[ProxyServer/%08" PRIX64 "] Connecting to %s", this->id, netloc_str.c_str());
if (bufferevent_socket_connect(this->server_bev.get(), if (bufferevent_socket_connect(this->server_bev.get(),
reinterpret_cast<const sockaddr*>(local_sin), sizeof(*local_sin)) != 0) { reinterpret_cast<const sockaddr*>(local_sin), sizeof(*local_sin)) != 0) {
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR())); throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
@@ -412,9 +498,8 @@ void ProxyServer::LinkedSession::dispatch_on_timeout(
void ProxyServer::LinkedSession::on_timeout() { void ProxyServer::LinkedSession::on_timeout() {
log(INFO, "[ProxyServer/%08" PRIX32 "] Session timed out", log(INFO, "[ProxyServer/%08" PRIX64 "] Session timed out", this->id);
this->license->serial_number); this->server->delete_session(this->id);
this->server->delete_session(this->license->serial_number);
} }
@@ -423,13 +508,13 @@ void ProxyServer::LinkedSession::on_stream_error(
short events, bool is_server_stream) { short events, bool is_server_stream) {
if (events & BEV_EVENT_ERROR) { if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR(); int err = EVUTIL_SOCKET_ERROR();
log(WARNING, "[ProxyServer/%08" PRIX32 "] Error %d (%s) in %s stream", log(WARNING, "[ProxyServer/%08" PRIX64 "] Error %d (%s) in %s stream",
this->license->serial_number, err, evutil_socket_error_to_string(err), this->id, err, evutil_socket_error_to_string(err),
is_server_stream ? "server" : "client"); is_server_stream ? "server" : "client");
} }
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
log(INFO, "[ProxyServer/%08" PRIX32 "] %s has disconnected", log(INFO, "[ProxyServer/%08" PRIX64 "] %s has disconnected",
this->license->serial_number, is_server_stream ? "Server" : "Client"); this->id, is_server_stream ? "Server" : "Client");
this->disconnect(); this->disconnect();
} }
} }
@@ -454,39 +539,42 @@ void ProxyServer::LinkedSession::disconnect() {
static void check_implemented_subcommand( static void check_implemented_subcommand(
uint32_t serial_number, uint16_t command, const string& data) { uint64_t id, uint16_t command, const string& data) {
if (command == 0x60 || command == 0x6C || command == 0xC9 || if (command == 0x60 || command == 0x6C || command == 0xC9 ||
command == 0x62 || command == 0x6D || command == 0xCB) { command == 0x62 || command == 0x6D || command == 0xCB) {
if (data.size() < 4) { if (data.size() < 4) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Received broadcast/target command with no contents", serial_number); log(WARNING, "[ProxyServer/%08" PRIX64 "] Received broadcast/target command with no contents", id);
} else { } else {
if (!subcommand_is_implemented(data[0])) { if (!subcommand_is_implemented(data[0])) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Received subcommand %02hhX which is not implemented on the server", log(WARNING, "[ProxyServer/%08" PRIX64 "] Received subcommand %02hhX which is not implemented on the server",
serial_number, data[0]); id, data[0]);
} }
} }
} }
} }
void ProxyServer::LinkedSession::on_client_input() { void ProxyServer::LinkedSession::on_client_input() {
string name = string_printf("ProxySession:%08" PRIX32 ":client", this->license->serial_number); string name = string_printf("ProxySession:%08" PRIX64 ":client", this->id);
for_each_received_command(this->client_bev.get(), this->version, this->client_input_crypt.get(), for_each_received_command(this->client_bev.get(), this->version, this->client_input_crypt.get(),
[&](uint16_t command, uint32_t flag, string& data) { [&](uint16_t command, uint32_t flag, string& data) {
print_received_command(command, flag, data.data(), data.size(), print_received_command(command, flag, data.data(), data.size(),
this->version, name.c_str()); this->version, name.c_str());
check_implemented_subcommand(this->license->serial_number, command, data); check_implemented_subcommand(this->id, command, data);
bool should_forward = true; bool should_forward = true;
switch (command) { switch (command) {
case 0x06: case 0x06:
if (this->version != GameVersion::GC) {
break;
}
if (data.size() < 12) { if (data.size() < 12) {
break; break;
} }
// If this chat message looks like a newserv chat command, suppress it // If this chat message looks like a newserv chat command, suppress it
if (this->suppress_newserv_commands && if (this->suppress_newserv_commands &&
(data[8] == '$' || (data[8] == '\t' && data[9] != 'C' && data[10] == '$'))) { (data[8] == '$' || (data[8] == '\t' && data[9] != 'C' && data[10] == '$'))) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Chat message appears to be a server command; dropping it", log(WARNING, "[ProxyServer/%08" PRIX64 "] Chat message appears to be a server command; dropping it",
this->license->serial_number); this->id);
should_forward = false; should_forward = false;
} else if (this->enable_chat_filter) { } else if (this->enable_chat_filter) {
// Turn all $ into \t and all # into \n // Turn all $ into \t and all # into \n
@@ -496,6 +584,13 @@ void ProxyServer::LinkedSession::on_client_input() {
case 0xA0: // Change ship case 0xA0: // Change ship
case 0xA1: { // Change block case 0xA1: { // Change block
if ((this->version == GameVersion::PATCH) || (this->version == GameVersion::BB)) {
break;
}
if (!this->license) {
break;
}
// These will take you back to the newserv main menu instead of the // These will take you back to the newserv main menu instead of the
// proxied service's menu // proxied service's menu
@@ -531,7 +626,7 @@ void ProxyServer::LinkedSession::on_client_input() {
this->version)); this->version));
S_Reconnect_19 reconnect_cmd = { S_Reconnect_19 reconnect_cmd = {
0, this->server->state->named_port_configuration.at(port_name).port, 0}; 0, this->server->state->name_to_port_config.at(port_name)->port, 0};
// If the client is on a virtual connection, we can use any address // 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 // here and they should be able to connect back to the game server. If
@@ -566,8 +661,8 @@ void ProxyServer::LinkedSession::on_client_input() {
if (should_forward) { if (should_forward) {
if (!this->client_bev.get()) { if (!this->client_bev.get()) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] No server is present; dropping command", log(WARNING, "[ProxyServer/%08" PRIX64 "] No server is present; dropping command",
this->license->serial_number); this->id);
} else { } else {
// Note: we intentionally don't pass a name string here because we already // Note: we intentionally don't pass a name string here because we already
// printed the command above // printed the command above
@@ -580,69 +675,98 @@ void ProxyServer::LinkedSession::on_client_input() {
} }
void ProxyServer::LinkedSession::on_server_input() { void ProxyServer::LinkedSession::on_server_input() {
string name = string_printf("ProxySession:%08" PRIX32 ":server", this->license->serial_number); string name = string_printf("ProxySession:%08" PRIX64 ":server", this->id);
try { try {
for_each_received_command(this->server_bev.get(), this->version, this->server_input_crypt.get(), for_each_received_command(this->server_bev.get(), this->version, this->server_input_crypt.get(),
[&](uint16_t command, uint32_t flag, string& data) { [&](uint16_t command, uint32_t flag, string& data) {
print_received_command(command, flag, data.data(), data.size(), print_received_command(command, flag, data.data(), data.size(),
this->version, name.c_str()); this->version, name.c_str());
check_implemented_subcommand(this->license->serial_number, command, data); check_implemented_subcommand(this->id, command, data);
// In the case of server init commands, the client output crypt cannot
// be set until after we forwarwd the command to the client, hence this
// variable.
shared_ptr<PSOEncryption> new_client_output_crypt;
bool should_forward = true; bool should_forward = true;
switch (command) { switch (command) {
case 0x02: case 0x02:
case 0x17: { case 0x17: {
if (this->version == GameVersion::PATCH && command == 0x17) {
throw invalid_argument("patch server sent 17 server init");
}
if (this->version == GameVersion::BB) { if (this->version == GameVersion::BB) {
throw invalid_argument("console server init received on BB"); throw invalid_argument("console server init received on BB");
} }
// Most servers don't include after_message or have a shorter // Most servers don't include after_message or have a shorter
// after_message than newserv does, so don't require it // after_message than newserv does, so don't require it
if (data.size() < offsetof(S_ServerInit_DC_GC_02_17, after_message)) { if (data.size() < offsetof(S_ServerInit_DC_GC_02_17, after_message)) {
throw std::runtime_error("init encryption command is too small"); throw std::runtime_error("init encryption command is too small");
} }
const auto* cmd = reinterpret_cast<const S_ServerInit_DC_GC_02_17*>( const auto* cmd = reinterpret_cast<const S_ServerInit_DC_GC_02_17*>(
data.data()); data.data());
// This doesn't get forwarded to the client, so don't recreate the if (!this->license) {
// client's crypts if ((this->version == GameVersion::PC) || (this->version == GameVersion::PATCH)) {
if (this->version == GameVersion::PC) { this->server_input_crypt.reset(new PSOPCEncryption(cmd->server_key));
this->server_input_crypt.reset(new PSOPCEncryption(cmd->server_key)); this->server_output_crypt.reset(new PSOPCEncryption(cmd->client_key));
this->server_output_crypt.reset(new PSOPCEncryption(cmd->client_key)); this->client_input_crypt.reset(new PSOPCEncryption(cmd->client_key));
} else if (this->version == GameVersion::GC) { new_client_output_crypt.reset(new PSOPCEncryption(cmd->server_key));
this->server_input_crypt.reset(new PSOGCEncryption(cmd->server_key)); } else if (this->version == GameVersion::GC) {
this->server_output_crypt.reset(new PSOGCEncryption(cmd->client_key)); this->server_input_crypt.reset(new PSOGCEncryption(cmd->server_key));
} else { this->server_output_crypt.reset(new PSOGCEncryption(cmd->client_key));
throw invalid_argument("unsupported version"); this->client_input_crypt.reset(new PSOGCEncryption(cmd->client_key));
} new_client_output_crypt.reset(new PSOGCEncryption(cmd->server_key));
} else {
should_forward = false; throw invalid_argument("unsupported version");
}
// If this is a 17, respond with a DB; otherwise respond with a 9E.
// We don't let the client do this because it believes it already
// did (when it was in an unlinked session).
if (command == 0x17) {
C_VerifyLicense_GC_DB cmd;
cmd.serial_number = string_printf("%08" PRIX32 "",
this->license->serial_number);
cmd.access_key = this->license->access_key;
cmd.sub_version = this->sub_version;
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
cmd.password = this->license->gc_password;
send_command(this->server_bev.get(), this->version,
this->server_output_crypt.get(), 0xDB, 0, &cmd, sizeof(cmd),
name.c_str());
break; break;
} else {
// This doesn't get forwarded to the client, so don't recreate the
// client's crypts
if (this->version == GameVersion::PATCH) {
throw logic_error("patch session is indirect");
} else if (this->version == GameVersion::PC) {
this->server_input_crypt.reset(new PSOPCEncryption(cmd->server_key));
this->server_output_crypt.reset(new PSOPCEncryption(cmd->client_key));
} else if (this->version == GameVersion::GC) {
this->server_input_crypt.reset(new PSOGCEncryption(cmd->server_key));
this->server_output_crypt.reset(new PSOGCEncryption(cmd->client_key));
} else {
throw invalid_argument("unsupported version");
}
should_forward = false;
// If this is a 17, respond with a DB; otherwise respond with a 9E.
// We don't let the client do this because it believes it already
// did (when it was in an unlinked session).
if (command == 0x17) {
C_VerifyLicense_GC_DB cmd;
cmd.serial_number = string_printf("%08" PRIX32 "",
this->license->serial_number);
cmd.access_key = this->license->access_key;
cmd.sub_version = this->sub_version;
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
cmd.password = this->license->gc_password;
send_command(this->server_bev.get(), this->version,
this->server_output_crypt.get(), 0xDB, 0, &cmd, sizeof(cmd),
name.c_str());
break;
}
// Command 02 should be handled like 9A at this point (we should
// send a 9E in response)
[[fallthrough]];
} }
// Command 02 should be handled like 9A at this point (we should
// send a 9E in response)
[[fallthrough]];
} }
case 0x9A: { case 0x9A: {
if (!this->license) {
break;
}
should_forward = false; should_forward = false;
C_Login_PC_GC_9D_9E cmd; C_Login_PC_GC_9D_9E cmd;
@@ -678,6 +802,10 @@ void ProxyServer::LinkedSession::on_server_input() {
} }
case 0x04: { case 0x04: {
if (this->version != GameVersion::GC) {
break;
}
// Some servers send a short 04 command if they don't use all of the // Some servers send a short 04 command if they don't use all of the
// 0x20 bytes available. We should be prepared to handle that. // 0x20 bytes available. We should be prepared to handle that.
if (data.size() < offsetof(S_UpdateClientConfig_DC_PC_GC_04, cfg)) { if (data.size() < offsetof(S_UpdateClientConfig_DC_PC_GC_04, cfg)) {
@@ -688,8 +816,8 @@ void ProxyServer::LinkedSession::on_server_input() {
const auto* cmd = reinterpret_cast<const S_UpdateClientConfig_DC_PC_GC_04*>(data.data()); const auto* cmd = reinterpret_cast<const S_UpdateClientConfig_DC_PC_GC_04*>(data.data());
this->guild_card_number = cmd->guild_card_number; this->guild_card_number = cmd->guild_card_number;
log(INFO, "[ProxyServer/%08" PRIX32 "] Guild card number set to %" PRIX32, log(INFO, "[ProxyServer/%08" PRIX64 "] Guild card number set to %" PRIX32,
this->license->serial_number, this->guild_card_number); this->id, this->guild_card_number);
// It seems the client ignores the length of the 04 command, and // It seems the client ignores the length of the 04 command, and
// always copies 0x20 bytes to its config data. So if the server // always copies 0x20 bytes to its config data. So if the server
@@ -724,6 +852,10 @@ void ProxyServer::LinkedSession::on_server_input() {
} }
case 0x19: { case 0x19: {
if (this->version == GameVersion::PATCH) {
break;
}
if (data.size() < sizeof(S_Reconnect_19)) { if (data.size() < sizeof(S_Reconnect_19)) {
throw std::runtime_error("reconnect command is too small"); throw std::runtime_error("reconnect command is too small");
} }
@@ -737,8 +869,8 @@ void ProxyServer::LinkedSession::on_server_input() {
sin->sin_port = htons(args->port); sin->sin_port = htons(args->port);
if (!this->client_bev.get()) { if (!this->client_bev.get()) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Received reconnect command with no destination present", log(WARNING, "[ProxyServer/%08" PRIX64 "] Received reconnect command with no destination present",
this->license->serial_number); this->id);
} else { } else {
// If the client is on a virtual connection (fd < 0), only change // If the client is on a virtual connection (fd < 0), only change
@@ -768,6 +900,10 @@ void ProxyServer::LinkedSession::on_server_input() {
case 0x1A: case 0x1A:
case 0xD5: { case 0xD5: {
if (this->version != GameVersion::PATCH) {
break;
}
// If the client has the no-close-confirmation flag set in its // If the client has the no-close-confirmation flag set in its
// newserv client config, send a fake confirmation to the remote // newserv client config, send a fake confirmation to the remote
// server immediately. // server immediately.
@@ -781,6 +917,9 @@ void ProxyServer::LinkedSession::on_server_input() {
case 0x44: case 0x44:
case 0xA6: { case 0xA6: {
if (this->version != GameVersion::GC) {
break;
}
if (!this->server->save_files) { if (!this->server->save_files) {
break; break;
} }
@@ -788,8 +927,8 @@ void ProxyServer::LinkedSession::on_server_input() {
bool is_download_quest = (command == 0xA6); bool is_download_quest = (command == 0xA6);
if (data.size() < sizeof(S_OpenFile_PC_GC_44_A6)) { if (data.size() < sizeof(S_OpenFile_PC_GC_44_A6)) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Open file command is too small; skipping file", log(WARNING, "[ProxyServer/%08" PRIX64 "] Open file command is too small; skipping file",
this->license->serial_number); this->id);
break; break;
} }
const auto* cmd = reinterpret_cast<const S_OpenFile_PC_GC_44_A6*>(data.data()); const auto* cmd = reinterpret_cast<const S_OpenFile_PC_GC_44_A6*>(data.data());
@@ -808,20 +947,23 @@ void ProxyServer::LinkedSession::on_server_input() {
SavingFile sf(cmd->filename, output_filename, cmd->file_size); SavingFile sf(cmd->filename, output_filename, cmd->file_size);
this->saving_files.emplace(cmd->filename, move(sf)); this->saving_files.emplace(cmd->filename, move(sf));
log(INFO, "[ProxyServer/%08" PRIX32 "] Opened file %s", log(INFO, "[ProxyServer/%08" PRIX64 "] Opened file %s",
this->license->serial_number, output_filename.c_str()); this->id, output_filename.c_str());
break; break;
} }
case 0x13: case 0x13:
case 0xA7: { case 0xA7: {
if (this->version != GameVersion::GC) {
break;
}
if (!this->server->save_files) { if (!this->server->save_files) {
break; break;
} }
if (data.size() < sizeof(S_WriteFile_13_A7)) { if (data.size() < sizeof(S_WriteFile_13_A7)) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Write file command is too small", log(WARNING, "[ProxyServer/%08" PRIX64 "] Write file command is too small",
this->license->serial_number); this->id);
break; break;
} }
const auto* cmd = reinterpret_cast<const S_WriteFile_13_A7*>(data.data()); const auto* cmd = reinterpret_cast<const S_WriteFile_13_A7*>(data.data());
@@ -830,69 +972,76 @@ void ProxyServer::LinkedSession::on_server_input() {
try { try {
sf = &this->saving_files.at(cmd->filename); sf = &this->saving_files.at(cmd->filename);
} catch (const out_of_range&) { } catch (const out_of_range&) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Received data for non-open file %s", log(WARNING, "[ProxyServer/%08" PRIX64 "] Received data for non-open file %s",
this->license->serial_number, cmd->filename.c_str()); this->id, cmd->filename.c_str());
break; break;
} }
size_t bytes_to_write = cmd->data_size; size_t bytes_to_write = cmd->data_size;
if (bytes_to_write > 0x400) { if (bytes_to_write > 0x400) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Chunk data size is invalid; truncating to 0x400", log(WARNING, "[ProxyServer/%08" PRIX64 "] Chunk data size is invalid; truncating to 0x400",
this->license->serial_number); this->id);
bytes_to_write = 0x400; bytes_to_write = 0x400;
} }
log(INFO, "[ProxyServer/%08" PRIX32 "] Writing %zu bytes to %s", log(INFO, "[ProxyServer/%08" PRIX64 "] Writing %zu bytes to %s",
this->license->serial_number, bytes_to_write, this->id, bytes_to_write,
sf->output_filename.c_str()); sf->output_filename.c_str());
fwritex(sf->f.get(), cmd->data, bytes_to_write); fwritex(sf->f.get(), cmd->data, bytes_to_write);
if (bytes_to_write > sf->remaining_bytes) { if (bytes_to_write > sf->remaining_bytes) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Chunk size extends beyond original file size; file may be truncated", log(WARNING, "[ProxyServer/%08" PRIX64 "] Chunk size extends beyond original file size; file may be truncated",
this->license->serial_number); this->id);
sf->remaining_bytes = 0; sf->remaining_bytes = 0;
} else { } else {
sf->remaining_bytes -= bytes_to_write; sf->remaining_bytes -= bytes_to_write;
} }
if (sf->remaining_bytes == 0) { if (sf->remaining_bytes == 0) {
log(INFO, "[ProxyServer/%08" PRIX32 "] File %s is complete", log(INFO, "[ProxyServer/%08" PRIX64 "] File %s is complete",
this->license->serial_number, sf->output_filename.c_str()); this->id, sf->output_filename.c_str());
this->saving_files.erase(cmd->filename); this->saving_files.erase(cmd->filename);
} }
break; break;
} }
case 0xB8: { case 0xB8: {
if (this->version != GameVersion::GC) {
break;
}
if (!this->server->save_files) { if (!this->server->save_files) {
break; break;
} }
if (data.size() < 4) { if (data.size() < 4) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Card list data size is too small; skipping file", log(WARNING, "[ProxyServer/%08" PRIX64 "] Card list data size is too small; skipping file",
this->license->serial_number); this->id);
break; break;
} }
StringReader r(data); StringReader r(data);
size_t size = r.get_u32l(); size_t size = r.get_u32l();
if (r.remaining() < size) { if (r.remaining() < size) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Card list data size extends beyond end of command; skipping file", log(WARNING, "[ProxyServer/%08" PRIX64 "] Card list data size extends beyond end of command; skipping file",
this->license->serial_number); this->id);
break; break;
} }
string output_filename = string_printf("cardupdate.mnr.%" PRIu64, now()); string output_filename = string_printf("cardupdate.mnr.%" PRIu64, now());
save_file(output_filename, r.read(size)); save_file(output_filename, r.read(size));
log(INFO, "[ProxyServer/%08" PRIX32 "] Wrote %zu bytes to %s", log(INFO, "[ProxyServer/%08" PRIX64 "] Wrote %zu bytes to %s",
this->license->serial_number, size, output_filename.c_str()); this->id, size, output_filename.c_str());
break; break;
} }
case 0x67: // join lobby case 0x67: // join lobby
if (this->version != GameVersion::GC) {
break;
}
this->lobby_players.clear(); this->lobby_players.clear();
this->lobby_players.resize(12); this->lobby_players.resize(12);
log(WARNING, "[ProxyServer/%08" PRIX32 "] Cleared lobby players", log(WARNING, "[ProxyServer/%08" PRIX64 "] Cleared lobby players",
this->license->serial_number); this->id);
// This command can cause the client to no longer send D6 responses // This command can cause the client to no longer send D6 responses
// when 1A/D5 large message boxes are closed. newserv keeps track of // when 1A/D5 large message boxes are closed. newserv keeps track of
@@ -907,10 +1056,14 @@ void ProxyServer::LinkedSession::on_server_input() {
case 0x65: // other player joined game case 0x65: // other player joined game
case 0x68: { // other player joined lobby case 0x68: { // other player joined lobby
if (this->version != GameVersion::GC) {
break;
}
size_t expected_size = offsetof(S_JoinLobby_GC_65_67_68, entries) + sizeof(S_JoinLobby_GC_65_67_68::Entry) * flag; size_t expected_size = offsetof(S_JoinLobby_GC_65_67_68, entries) + sizeof(S_JoinLobby_GC_65_67_68::Entry) * flag;
if (data.size() < expected_size) { if (data.size() < expected_size) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Lobby join command is incorrect size (expected 0x%zX bytes, received 0x%zX bytes)", log(WARNING, "[ProxyServer/%08" PRIX64 "] Lobby join command is incorrect size (expected 0x%zX bytes, received 0x%zX bytes)",
this->license->serial_number, expected_size, data.size()); this->id, expected_size, data.size());
} else { } else {
auto* cmd = reinterpret_cast<S_JoinLobby_GC_65_67_68*>(data.data()); auto* cmd = reinterpret_cast<S_JoinLobby_GC_65_67_68*>(data.data());
@@ -919,13 +1072,13 @@ void ProxyServer::LinkedSession::on_server_input() {
for (size_t x = 0; x < flag; x++) { for (size_t x = 0; x < flag; x++) {
size_t index = cmd->entries[x].lobby_data.client_id; size_t index = cmd->entries[x].lobby_data.client_id;
if (index >= this->lobby_players.size()) { if (index >= this->lobby_players.size()) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Ignoring invalid player index %zu at position %zu", log(WARNING, "[ProxyServer/%08" PRIX64 "] Ignoring invalid player index %zu at position %zu",
this->license->serial_number, index, x); this->id, index, x);
} else { } else {
this->lobby_players[index].guild_card_number = cmd->entries[x].lobby_data.guild_card; this->lobby_players[index].guild_card_number = cmd->entries[x].lobby_data.guild_card;
this->lobby_players[index].name = cmd->entries[x].disp.name; this->lobby_players[index].name = cmd->entries[x].disp.name;
log(INFO, "[ProxyServer/%08" PRIX32 "] Added lobby player: (%zu) %" PRIu32 " %s", log(INFO, "[ProxyServer/%08" PRIX64 "] Added lobby player: (%zu) %" PRIu32 " %s",
this->license->serial_number, index, this->id, index,
this->lobby_players[index].guild_card_number, this->lobby_players[index].guild_card_number,
this->lobby_players[index].name.c_str()); this->lobby_players[index].name.c_str());
} }
@@ -942,17 +1095,21 @@ void ProxyServer::LinkedSession::on_server_input() {
} }
case 0x64: { // join game case 0x64: { // join game
if (this->version != GameVersion::GC) {
break;
}
// We don't need to clear lobby_players here because we always // We don't need to clear lobby_players here because we always
// overwrite all 4 entries in this case // overwrite all 4 entries in this case
this->lobby_players.resize(4); this->lobby_players.resize(4);
log(WARNING, "[ProxyServer/%08" PRIX32 "] Cleared lobby players", log(WARNING, "[ProxyServer/%08" PRIX64 "] Cleared lobby players",
this->license->serial_number); this->id);
const size_t expected_size = offsetof(S_JoinGame_GC_64, players_ep3); const size_t expected_size = offsetof(S_JoinGame_GC_64, players_ep3);
const size_t ep3_expected_size = sizeof(S_JoinGame_GC_64); const size_t ep3_expected_size = sizeof(S_JoinGame_GC_64);
if (data.size() < expected_size) { if (data.size() < expected_size) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Game join command is incorrect size (expected 0x%zX bytes, received 0x%zX bytes)", log(WARNING, "[ProxyServer/%08" PRIX64 "] Game join command is incorrect size (expected 0x%zX bytes, received 0x%zX bytes)",
this->license->serial_number, expected_size, data.size()); this->id, expected_size, data.size());
} else { } else {
auto* cmd = reinterpret_cast<S_JoinGame_GC_64*>(data.data()); auto* cmd = reinterpret_cast<S_JoinGame_GC_64*>(data.data());
@@ -965,8 +1122,8 @@ void ProxyServer::LinkedSession::on_server_input() {
} else { } else {
this->lobby_players[x].name.clear(); this->lobby_players[x].name.clear();
} }
log(INFO, "[ProxyServer/%08" PRIX32 "] Added lobby player: (%zu) %" PRIu32 " %s", log(INFO, "[ProxyServer/%08" PRIX64 "] Added lobby player: (%zu) %" PRIu32 " %s",
this->license->serial_number, x, this->id, x,
this->lobby_players[x].guild_card_number, this->lobby_players[x].guild_card_number,
this->lobby_players[x].name.c_str()); this->lobby_players[x].name.c_str());
} }
@@ -983,20 +1140,24 @@ void ProxyServer::LinkedSession::on_server_input() {
case 0x66: case 0x66:
case 0x69: { case 0x69: {
if (this->version != GameVersion::GC) {
break;
}
if (data.size() < sizeof(S_LeaveLobby_66_69)) { if (data.size() < sizeof(S_LeaveLobby_66_69)) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Lobby leave command is incorrect size", log(WARNING, "[ProxyServer/%08" PRIX64 "] Lobby leave command is incorrect size",
this->license->serial_number); this->id);
} else { } else {
const auto* cmd = reinterpret_cast<const S_LeaveLobby_66_69*>(data.data()); const auto* cmd = reinterpret_cast<const S_LeaveLobby_66_69*>(data.data());
size_t index = cmd->client_id; size_t index = cmd->client_id;
if (index >= this->lobby_players.size()) { if (index >= this->lobby_players.size()) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Lobby leave command references missing position", log(WARNING, "[ProxyServer/%08" PRIX64 "] Lobby leave command references missing position",
this->license->serial_number); this->id);
} else { } else {
this->lobby_players[index].guild_card_number = 0; this->lobby_players[index].guild_card_number = 0;
this->lobby_players[index].name.clear(); this->lobby_players[index].name.clear();
log(INFO, "[ProxyServer/%08" PRIX32 "] Removed lobby player (%zu)", log(INFO, "[ProxyServer/%08" PRIX64 "] Removed lobby player (%zu)",
this->license->serial_number, index); this->id, index);
} }
} }
break; break;
@@ -1005,8 +1166,8 @@ void ProxyServer::LinkedSession::on_server_input() {
if (should_forward) { if (should_forward) {
if (!this->client_bev.get()) { if (!this->client_bev.get()) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] No client is present; dropping command", log(WARNING, "[ProxyServer/%08" PRIX64 "] No client is present; dropping command",
this->license->serial_number); this->id);
} else { } else {
// Note: we intentionally don't pass name_str here because we already // Note: we intentionally don't pass name_str here because we already
// printed the command above // printed the command above
@@ -1015,11 +1176,15 @@ void ProxyServer::LinkedSession::on_server_input() {
data.data(), data.size()); data.data(), data.size());
} }
} }
if (new_client_output_crypt.get()) {
this->client_output_crypt = new_client_output_crypt;
}
}); });
} catch (const exception& e) { } catch (const exception& e) {
log(ERROR, "[ProxyServer/%08" PRIX32 "] Failed to process server command: %s", log(ERROR, "[ProxyServer/%08" PRIX64 "] Failed to process server command: %s",
this->license->serial_number, e.what()); this->id, e.what());
this->disconnect(); this->disconnect();
} }
} }
@@ -1035,8 +1200,8 @@ void ProxyServer::LinkedSession::send_to_end(
} }
const auto* header = reinterpret_cast<const PSOCommandHeader*>(data); const auto* header = reinterpret_cast<const PSOCommandHeader*>(data);
string name = string_printf("ProxySession:%08" PRIX32 ":shell:%s", string name = string_printf("ProxySession:%08" PRIX64 ":shell:%s",
this->license->serial_number, to_server ? "server" : "client"); this->id, to_server ? "server" : "client");
send_command( send_command(
to_server ? this->server_bev.get() : this->client_bev.get(), to_server ? this->server_bev.get() : this->client_bev.get(),
@@ -1054,17 +1219,17 @@ void ProxyServer::LinkedSession::send_to_end(const string& data, bool to_server)
} }
shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session() { shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session() {
if (this->serial_number_to_session.empty()) { if (this->id_to_session.empty()) {
throw runtime_error("no sessions exist"); throw runtime_error("no sessions exist");
} }
if (this->serial_number_to_session.size() > 1) { if (this->id_to_session.size() > 1) {
throw runtime_error("multiple sessions exist"); throw runtime_error("multiple sessions exist");
} }
return this->serial_number_to_session.begin()->second; return this->id_to_session.begin()->second;
} }
void ProxyServer::delete_session(uint32_t serial_number) { void ProxyServer::delete_session(uint64_t id) {
if (this->serial_number_to_session.erase(serial_number)) { if (this->id_to_session.erase(id)) {
log(WARNING, "[ProxyServer/%08" PRIX32 "] Closed session", serial_number); log(WARNING, "[ProxyServer/%08" PRIX64 "] Closed session", id);
} }
} }
+34 -5
View File
@@ -9,6 +9,7 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <memory> #include <memory>
#include <phosg/Filesystem.hh>
#include "PSOEncryption.hh" #include "PSOEncryption.hh"
#include "PSOProtocol.hh" #include "PSOProtocol.hh"
@@ -26,12 +27,14 @@ public:
std::shared_ptr<ServerState> state); std::shared_ptr<ServerState> state);
virtual ~ProxyServer() = default; virtual ~ProxyServer() = default;
void listen(uint16_t port, GameVersion version); void listen(uint16_t port, GameVersion version,
const struct sockaddr_storage* default_destination = nullptr);
void connect_client(struct bufferevent* bev, uint16_t server_port); void connect_client(struct bufferevent* bev, uint16_t server_port);
struct LinkedSession { struct LinkedSession {
ProxyServer* server; ProxyServer* server;
uint64_t id;
std::unique_ptr<struct event, void(*)(struct event*)> timeout_event; std::unique_ptr<struct event, void(*)(struct event*)> timeout_event;
@@ -80,12 +83,24 @@ public:
}; };
std::unordered_map<std::string, SavingFile> saving_files; std::unordered_map<std::string, SavingFile> saving_files;
// TODO: This first constructor should be private
LinkedSession(
ProxyServer* server,
uint64_t id,
uint16_t local_port,
GameVersion version);
LinkedSession( LinkedSession(
ProxyServer* server, ProxyServer* server,
uint16_t local_port, uint16_t local_port,
GameVersion version, GameVersion version,
std::shared_ptr<const License> license, std::shared_ptr<const License> license,
const ClientConfig& newserv_client_config); const ClientConfig& newserv_client_config);
LinkedSession(
ProxyServer* server,
uint64_t id,
uint16_t local_port,
GameVersion version,
const struct sockaddr_storage& next_destination);
void resume( void resume(
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev, std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
@@ -93,6 +108,8 @@ public:
std::shared_ptr<PSOEncryption> client_output_crypt, std::shared_ptr<PSOEncryption> client_output_crypt,
uint32_t sub_version, uint32_t sub_version,
const std::string& character_name); const std::string& character_name);
void resume(struct bufferevent* bev);
void connect();
static void dispatch_on_client_input(struct bufferevent* bev, void* ctx); static void dispatch_on_client_input(struct bufferevent* bev, void* ctx);
static void dispatch_on_client_error(struct bufferevent* bev, short events, static void dispatch_on_client_error(struct bufferevent* bev, short events,
@@ -115,7 +132,7 @@ public:
}; };
std::shared_ptr<LinkedSession> get_session(); std::shared_ptr<LinkedSession> get_session();
void delete_session(uint32_t serial_number); void delete_session(uint64_t id);
bool save_files; bool save_files;
@@ -123,10 +140,17 @@ private:
struct ListeningSocket { struct ListeningSocket {
ProxyServer* server; ProxyServer* server;
int fd;
uint16_t port; uint16_t port;
scoped_fd fd;
std::unique_ptr<struct evconnlistener, void(*)(struct evconnlistener*)> listener; std::unique_ptr<struct evconnlistener, void(*)(struct evconnlistener*)> listener;
GameVersion version; GameVersion version;
struct sockaddr_storage default_destination;
ListeningSocket(
ProxyServer* server,
uint16_t port,
GameVersion version,
const struct sockaddr_storage* default_destination);
static void dispatch_on_listen_accept(struct evconnlistener* listener, static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr *address, int socklen, void* ctx); evutil_socket_t fd, struct sockaddr *address, int socklen, void* ctx);
@@ -160,7 +184,12 @@ private:
std::shared_ptr<ServerState> state; std::shared_ptr<ServerState> state;
std::map<int, std::shared_ptr<ListeningSocket>> listeners; std::map<int, std::shared_ptr<ListeningSocket>> listeners;
std::unordered_map<struct bufferevent*, std::shared_ptr<UnlinkedSession>> bev_to_unlinked_session; std::unordered_map<struct bufferevent*, std::shared_ptr<UnlinkedSession>> bev_to_unlinked_session;
std::unordered_map<uint32_t, std::shared_ptr<LinkedSession>> serial_number_to_session; std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>> id_to_session;
uint64_t next_unlicensed_session_id;
void on_client_connect(struct bufferevent* bev, uint16_t port, GameVersion version); void on_client_connect(
struct bufferevent* bev,
uint16_t listen_port,
GameVersion version,
const struct sockaddr_storage* default_destination);
}; };
+20 -12
View File
@@ -104,8 +104,8 @@ vector<MenuItem> quest_download_menu({
void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c) { void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c) {
switch (c->server_behavior) { switch (c->server_behavior) {
case ServerBehavior::SPLIT_RECONNECT: { case ServerBehavior::SPLIT_RECONNECT: {
uint16_t pc_port = s->named_port_configuration.at("pc-login").port; uint16_t pc_port = s->name_to_port_config.at("pc-login")->port;
uint16_t gc_port = s->named_port_configuration.at("gc-jp10").port; uint16_t gc_port = s->name_to_port_config.at("gc-jp10")->port;
send_pc_gc_split_reconnect(c, s->connect_address_for_client(c), pc_port, gc_port); send_pc_gc_split_reconnect(c, s->connect_address_for_client(c), pc_port, gc_port);
c->should_disconnect = true; c->should_disconnect = true;
break; break;
@@ -383,7 +383,7 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
case ClientStateBB::INITIAL_LOGIN: case ClientStateBB::INITIAL_LOGIN:
// first login? send them to the other port // first login? send them to the other port
send_reconnect(c, s->connect_address_for_client(c), send_reconnect(c, s->connect_address_for_client(c),
s->named_port_configuration.at("bb-data1").port); s->name_to_port_config.at("bb-data1")->port);
break; break;
case ClientStateBB::DOWNLOAD_DATA: { case ClientStateBB::DOWNLOAD_DATA: {
@@ -409,7 +409,7 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
default: default:
send_reconnect(c, s->connect_address_for_client(c), send_reconnect(c, s->connect_address_for_client(c),
s->named_port_configuration.at("bb-login").port); s->name_to_port_config.at("bb-login")->port);
} }
} }
@@ -617,8 +617,9 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
send_ship_info(c, u"Return to the\nmain menu."); send_ship_info(c, u"Return to the\nmain menu.");
} else { } else {
try { try {
const auto& menu = s->proxy_destinations_menu_for_version(c->version);
// we use item_id + 1 here because "go back" is the first item // we use item_id + 1 here because "go back" is the first item
send_ship_info(c, s->proxy_destinations_menu.at(cmd.item_id + 1).description.c_str()); send_ship_info(c, menu.at(cmd.item_id + 1).description.c_str());
} catch (const out_of_range&) { } catch (const out_of_range&) {
send_ship_info(c, u"$C6No such information exists."); send_ship_info(c, u"$C6No such information exists.");
} }
@@ -661,7 +662,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version)); const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
send_reconnect(c, s->connect_address_for_client(c), send_reconnect(c, s->connect_address_for_client(c),
s->named_port_configuration.at(port_name).port); s->name_to_port_config.at(port_name)->port);
break; break;
} }
@@ -673,7 +674,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
case MAIN_MENU_PROXY_DESTINATIONS: case MAIN_MENU_PROXY_DESTINATIONS:
send_menu(c, u"Proxy server", PROXY_DESTINATIONS_MENU_ID, send_menu(c, u"Proxy server", PROXY_DESTINATIONS_MENU_ID,
s->proxy_destinations_menu, false); s->proxy_destinations_menu_for_version(c->version), false);
break; break;
case MAIN_MENU_DOWNLOAD_QUESTS: case MAIN_MENU_DOWNLOAD_QUESTS:
@@ -711,9 +712,9 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false); send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false);
} else { } else {
pair<string, uint16_t>* dest = nullptr; const pair<string, uint16_t>* dest = nullptr;
try { try {
dest = &s->proxy_destinations.at(cmd.item_id); dest = &s->proxy_destinations_for_version(c->version).at(cmd.item_id);
} catch (const out_of_range&) { } } catch (const out_of_range&) { }
if (!dest) { if (!dest) {
@@ -735,7 +736,7 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
"dc-proxy", "pc-proxy", "", "gc-proxy", "bb-proxy"}); "dc-proxy", "pc-proxy", "", "gc-proxy", "bb-proxy"});
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version)); const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
send_reconnect(c, s->connect_address_for_client(c), send_reconnect(c, s->connect_address_for_client(c),
s->named_port_configuration.at(port_name).port); s->name_to_port_config.at(port_name)->port);
} }
} }
break; break;
@@ -936,7 +937,7 @@ void process_change_ship(shared_ptr<ServerState> s, shared_ptr<Client> c,
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version)); const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
send_reconnect(c, s->connect_address_for_client(c), send_reconnect(c, s->connect_address_for_client(c),
s->named_port_configuration.at(port_name).port); s->name_to_port_config.at(port_name)->port);
} }
void process_change_block(shared_ptr<ServerState> s, shared_ptr<Client> c, void process_change_block(shared_ptr<ServerState> s, shared_ptr<Client> c,
@@ -1700,7 +1701,14 @@ void process_encryption_ok_patch(shared_ptr<ServerState>, shared_ptr<Client> c,
void process_login_patch(shared_ptr<ServerState> s, shared_ptr<Client> c, void process_login_patch(shared_ptr<ServerState> s, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { uint16_t, uint32_t, const string& data) {
const auto& cmd = check_size_t<C_Login_Patch_04>(data); const auto& cmd = check_size_t<C_Login_Patch_04>(data,
offsetof(C_Login_Patch_04, email), sizeof(C_Login_Patch_04));
if (data.size() == offsetof(C_Login_Patch_04, email)) {
c->flags |= Client::Flag::BB_PATCH;
} else if (data.size() != sizeof(C_Login_Patch_04)) {
throw runtime_error("unknown patch server login format");
}
u16string message = u"\ u16string message = u"\
$C7newserv patch server\n\ $C7newserv patch server\n\
+30 -6
View File
@@ -197,13 +197,37 @@ uint32_t ServerState::connect_address_for_client(std::shared_ptr<Client> c) {
const vector<MenuItem>& ServerState::proxy_destinations_menu_for_version(GameVersion version) {
if (version == GameVersion::PC) {
return this->proxy_destinations_menu_pc;
} else if (version == GameVersion::GC) {
return this->proxy_destinations_menu_gc;
}
throw out_of_range("no proxy destinations menu exists for this version");
}
const vector<pair<string, uint16_t>>& ServerState::proxy_destinations_for_version(GameVersion version) {
if (version == GameVersion::PC) {
return this->proxy_destinations_pc;
} else if (version == GameVersion::GC) {
return this->proxy_destinations_gc;
}
throw out_of_range("no proxy destinations menu exists for this version");
}
void ServerState::set_port_configuration( void ServerState::set_port_configuration(
const std::unordered_map<std::string, PortConfiguration>& named_port_configuration) { const vector<PortConfiguration>& port_configs) {
this->named_port_configuration = named_port_configuration; this->name_to_port_config.clear();
this->numbered_port_configuration.clear(); this->number_to_port_config.clear();
for (const auto& it : this->named_port_configuration) { for (const auto& pc : port_configs) {
if (!this->numbered_port_configuration.emplace(it.second.port, it.second).second) { shared_ptr<PortConfiguration> spc(new PortConfiguration(pc));
throw runtime_error("duplicate port in configuration"); if (!this->name_to_port_config.emplace(spc->name, spc).second) {
throw logic_error("duplicate name in port configuration");
}
if (!this->number_to_port_config.emplace(spc->port, spc).second) {
throw logic_error("duplicate number in port configuration");
} }
} }
} }
+12 -5
View File
@@ -22,6 +22,7 @@
class ProxyServer; class ProxyServer;
struct PortConfiguration { struct PortConfiguration {
std::string name;
uint16_t port; uint16_t port;
GameVersion version; GameVersion version;
ServerBehavior behavior; ServerBehavior behavior;
@@ -35,8 +36,8 @@ struct ServerState {
}; };
std::u16string name; std::u16string name;
std::unordered_map<std::string, PortConfiguration> named_port_configuration; std::unordered_map<std::string, std::shared_ptr<PortConfiguration>> name_to_port_config;
std::unordered_map<uint16_t, PortConfiguration> numbered_port_configuration; std::unordered_map<uint16_t, std::shared_ptr<PortConfiguration>> number_to_port_config;
std::string username; std::string username;
uint16_t dns_server_port; uint16_t dns_server_port;
std::vector<std::string> ip_stack_addresses; std::vector<std::string> ip_stack_addresses;
@@ -54,8 +55,11 @@ struct ServerState {
std::vector<MenuItem> main_menu; std::vector<MenuItem> main_menu;
std::shared_ptr<std::vector<MenuItem>> information_menu; std::shared_ptr<std::vector<MenuItem>> information_menu;
std::shared_ptr<std::vector<std::u16string>> information_contents; std::shared_ptr<std::vector<std::u16string>> information_contents;
std::vector<MenuItem> proxy_destinations_menu; std::vector<MenuItem> proxy_destinations_menu_pc;
std::vector<std::pair<std::string, uint16_t>> proxy_destinations; std::vector<MenuItem> proxy_destinations_menu_gc;
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::u16string welcome_message; std::u16string welcome_message;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby; std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
@@ -94,6 +98,9 @@ struct ServerState {
uint32_t connect_address_for_client(std::shared_ptr<Client> c); uint32_t connect_address_for_client(std::shared_ptr<Client> c);
const std::vector<MenuItem>& proxy_destinations_menu_for_version(GameVersion version);
const std::vector<std::pair<std::string, uint16_t>>& proxy_destinations_for_version(GameVersion version);
void set_port_configuration( void set_port_configuration(
const std::unordered_map<std::string, PortConfiguration>& named_port_configuration); const std::vector<PortConfiguration>& port_configs);
}; };
+3 -1
View File
@@ -1,8 +1,10 @@
#include "Version.hh" #include "Version.hh"
#include <strings.h>
#include <stdexcept> #include <stdexcept>
#include <strings.h> #include "Client.hh"
using namespace std; using namespace std;
+9 -4
View File
@@ -25,10 +25,15 @@
// strings to this list to listen for tapserver connections on a TCP port. // strings to this list to listen for tapserver connections on a TCP port.
"IPStackListen": [], "IPStackListen": [],
// Other servers to support proxying to. If this is empty, the proxy server is // Other servers to support proxying to. If either of these is empty, the
// disabled. Entries are like "name": "address:port"; the names are visible to // proxy server is disabled for that game version. Entries are like
// clients in the proxy server menu. // "name": "address:port"; the names are used in the proxy server menu.
"ProxyDestinations": {}, "ProxyDestinations-GC": {},
"ProxyDestinations-PC": {},
// Proxy destination for patch server clients. If this is given, the internal
// 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": "",
// By default, the interactive shell runs if stdin is a terminal, and doesn't // 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. // run if it's not. This option, if present, overrides that behavior.