allow returning to newserv from proxy server

This commit is contained in:
Martin Michelsen
2022-03-29 23:23:55 -07:00
parent 03dcc016d8
commit 2e839fe70a
5 changed files with 107 additions and 30 deletions
+1 -1
View File
@@ -134,7 +134,7 @@ void populate_state_from_config(shared_ptr<ServerState> s,
u"View server information", MenuItemFlag::REQUIRES_MESSAGE_BOXES);
if (!s->proxy_destinations.empty()) {
s->main_menu.emplace_back(MAIN_MENU_PROXY_DESTINATIONS, u"Proxy server",
u"Connect to another\nserver", MenuItemFlag::REQUIRES_MESSAGE_BOXES);
u"Connect to another\nserver", 0);
}
s->main_menu.emplace_back(MAIN_MENU_DOWNLOAD_QUESTS, u"Download quests",
u"Download quests", 0);
+96 -19
View File
@@ -202,7 +202,8 @@ void ProxyServer::UnlinkedSession::on_client_input() {
serial_number, cmd->access_key, nullptr);
sub_version = cmd->sub_version;
character_name = cmd->name;
memcpy(&client_config, &cmd->cfg, 0x20);
memcpy(&client_config, &cmd->cfg, offsetof(ClientConfig, unused_bb_only));
memset(client_config.unused_bb_only, 0xFF, sizeof(client_config.unused_bb_only));
} catch (const exception& e) {
log(ERROR, "[ProxyServer] Unlinked client has no valid license");
should_close_unlinked_session = true;
@@ -238,8 +239,7 @@ void ProxyServer::UnlinkedSession::on_client_input() {
this->local_port,
this->version,
license,
client_config.proxy_destination_address,
client_config.proxy_destination_port));
client_config));
this->server->serial_number_to_session.emplace(
license->serial_number, session);
log(INFO, "[ProxyServer/%08" PRIX32 "] Opened session",
@@ -288,8 +288,7 @@ ProxyServer::LinkedSession::LinkedSession(
uint16_t local_port,
GameVersion version,
std::shared_ptr<const License> license,
uint32_t dest_addr,
uint16_t dest_port)
const ClientConfig& newserv_client_config)
: server(server),
timeout_event(event_new(this->server->base.get(), -1, EV_TIMEOUT,
&LinkedSession::dispatch_on_timeout, this), event_free),
@@ -300,14 +299,15 @@ ProxyServer::LinkedSession::LinkedSession(
version(version),
sub_version(0), // This is set during resume()
guild_card_number(0),
newserv_client_config(newserv_client_config),
lobby_players(12),
lobby_client_id(0) {
memset(this->client_config_data, 0, 0x20);
memset(this->remote_client_config_data, 0, 0x20);
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(dest_port);
dest_sin->sin_addr.s_addr = htonl(dest_addr);
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);
}
void ProxyServer::LinkedSession::resume(
@@ -487,6 +487,66 @@ void ProxyServer::LinkedSession::on_client_input() {
add_color_inplace(data.data() + 8, data.size() - 8);
}
break;
case 0xA0: // Change ship
case 0xA1: { // Change block
// These will take you back to the newserv main menu instead of the
// proxied service's menu
// Restore the newserv client config, so the client gets its newserv
// guild card number back and the login server knows e.g. not to show
// the welcome message (if the appropriate flag is set)
struct {
uint32_t player_tag;
uint32_t serial_number;
ClientConfig config;
} __attribute__((packed)) update_client_config_cmd = {
0x00010000,
this->license->serial_number,
this->newserv_client_config,
};
send_command(this->client_bev.get(), this->version,
this->client_output_crypt.get(), 0x04, 0x00,
&update_client_config_cmd, sizeof(update_client_config_cmd),
name.c_str());
static const vector<string> version_to_port_name({
"dc-login", "pc-login", "bb-patch", "gc-us3", "bb-login"});
const auto& port_name = version_to_port_name.at(static_cast<size_t>(
this->version));
ReconnectCommand_19 reconnect_cmd = {
0, this->server->state->named_port_configuration.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).
int fd = bufferevent_getfd(this->client_bev.get());
if (fd < 0) {
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 {
struct sockaddr_storage sockname_ss;
socklen_t len = sizeof(sockname_ss);
getsockname(fd, reinterpret_cast<struct sockaddr*>(&sockname_ss), &len);
if (sockname_ss.ss_family != AF_INET) {
throw logic_error("existing connection is not ipv4");
}
struct sockaddr_in* sockname_sin = reinterpret_cast<struct sockaddr_in*>(
&sockname_ss);
reconnect_cmd.address.store_raw(sockname_sin->sin_addr.s_addr);
}
send_command(this->client_bev.get(), this->version,
this->client_output_crypt.get(), 0x19, 0x00, &reconnect_cmd,
sizeof(reconnect_cmd), name.c_str());
break;
}
}
if (should_forward) {
@@ -590,7 +650,7 @@ void ProxyServer::LinkedSession::on_server_input() {
this->license->serial_number);
memcpy(cmd.access_key2, this->license->access_key, 0x10);
strncpy(cmd.name, this->character_name.c_str(), sizeof(cmd.name) - 1);
memcpy(&cmd.cfg, this->client_config_data, 0x20);
memcpy(&cmd.cfg, this->remote_client_config_data, 0x20);
// If there's a guild card number, a shorter 9E is sent that ends
// right after the client config data
@@ -632,12 +692,12 @@ void ProxyServer::LinkedSession::on_server_input() {
// If there was previously a guild card number, assume we got the
// lobby server init text instead of the port map init text.
memcpy(
this->client_config_data,
this->remote_client_config_data,
had_guild_card_number
? "t Lobby Server. Copyright SEGA E"
: "t Port Map. Copyright SEGA Enter",
0x20);
memcpy(this->client_config_data, cmd->client_config, min<size_t>(data.size() - sizeof(Contents), 0x20));
memcpy(this->remote_client_config_data, cmd->client_config, min<size_t>(data.size() - sizeof(Contents), 0x20));
// If the guild card number was not set, pretend (to the server)
// that this is the first 04 command the client has received. The
@@ -656,17 +716,11 @@ void ProxyServer::LinkedSession::on_server_input() {
}
case 0x19: {
struct ReconnectCommand {
be_uint32_t address;
uint16_t port;
uint16_t unused;
};
if (data.size() < sizeof(ReconnectCommand)) {
if (data.size() < sizeof(ReconnectCommand_19)) {
throw std::runtime_error("reconnect command is too small");
}
auto* args = reinterpret_cast<ReconnectCommand*>(data.data());
auto* args = reinterpret_cast<ReconnectCommand_19*>(data.data());
memset(&this->next_destination, 0, sizeof(this->next_destination));
struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(
&this->next_destination);
@@ -704,6 +758,19 @@ void ProxyServer::LinkedSession::on_server_input() {
break;
}
case 0x1A:
case 0xD5: {
// 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 (this->newserv_client_config.flags & ClientFlag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION) {
send_command(this->server_bev.get(), this->version,
this->server_output_crypt.get(), 0xD6, 0x00, "", 0,
name.c_str());
}
break;
}
case 0x44:
case 0xA6: {
if (!this->server->save_quests) {
@@ -829,6 +896,16 @@ void ProxyServer::LinkedSession::on_server_input() {
this->lobby_players.resize(12);
log(WARNING, "[ProxyServer/%08" PRIX32 "] Cleared lobby players",
this->license->serial_number);
// This command can cause the client to no longer send D6 responses
// when 1A/D5 large message boxes are closed. newserv keeps track of
// this 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 (this->newserv_client_config.flags & ClientFlag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION_AFTER_LOBBY_JOIN) {
this->newserv_client_config.flags |= ClientFlag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION;
}
[[fallthrough]];
case 0x65: // other player joined game
+3 -3
View File
@@ -46,7 +46,8 @@ public:
std::string character_name;
uint32_t guild_card_number;
uint8_t client_config_data[0x20];
uint8_t remote_client_config_data[0x20];
ClientConfig newserv_client_config;
struct LobbyPlayer {
uint32_t guild_card_number;
@@ -79,8 +80,7 @@ public:
uint16_t local_port,
GameVersion version,
std::shared_ptr<const License> license,
uint32_t dest_addr,
uint16_t dest_port);
const ClientConfig& newserv_client_config);
void resume(
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
+1 -7
View File
@@ -274,13 +274,7 @@ void send_update_client_config(shared_ptr<Client> c) {
void send_reconnect(shared_ptr<Client> c, uint32_t address, uint16_t port) {
struct {
// The address is big-endian, for some reason. Probably it was defined as a
// uint8_t[4] in the original PSO source rather than a uint32_t
be_uint32_t address;
uint16_t port;
uint16_t unused;
} __attribute__((packed)) cmd = {address, port, 0};
ReconnectCommand_19 cmd = {address, port, 0};
send_command(c, 0x19, 0x00, cmd);
}
+6
View File
@@ -75,6 +75,12 @@ void send_server_init(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
bool initial_connection);
void send_update_client_config(std::shared_ptr<Client> c);
struct ReconnectCommand_19 {
be_uint32_t address;
uint16_t port;
uint16_t unused;
} __attribute__((packed));
void send_reconnect(std::shared_ptr<Client> c, uint32_t address, uint16_t port);
void send_pc_gc_split_reconnect(std::shared_ptr<Client> c, uint32_t address,
uint16_t pc_port, uint16_t gc_port);