support chat commands on proxy server

This commit is contained in:
Martin Michelsen
2022-06-25 12:17:43 -07:00
parent fc078a5d51
commit ba1a25036b
21 changed files with 1297 additions and 1059 deletions
+216 -169
View File
@@ -26,6 +26,7 @@
#include <resource_file/Emulators/PPC32Emulator.hh>
#endif
#include "ChatCommands.hh"
#include "Compression.hh"
#include "PSOProtocol.hh"
#include "SendCommands.hh"
@@ -37,21 +38,13 @@ using namespace std;
static void forward_command(ProxyServer::LinkedSession& session, bool to_server,
uint16_t command, uint32_t flag, string& data) {
auto* bev = to_server ? session.server_bev.get() : session.client_bev.get();
if (!bev) {
uint16_t command, uint32_t flag, string& data, bool print_contents = true) {
auto& ch = to_server ? session.server_channel : session.client_channel;
if (!ch.connected()) {
session.log(WARNING, "No endpoint is present; dropping command");
} else {
// Note: we intentionally don't pass name_str here because we already
// printed the command before calling the handler
send_command(
bev,
session.version,
to_server ? session.server_output_crypt.get() : session.client_output_crypt.get(),
command,
flag,
data.data(),
data.size());
// TODO: Don't print the command here (usually)
ch.send(command, flag, data, print_contents);
}
}
@@ -85,7 +78,7 @@ static void send_text_message_to_client(
while (w.size() & 3) {
w.put_u8(0);
}
session.send_to_end(false, command, 0x00, w.str());
session.client_channel.send(command, 0x00, w.str());
}
@@ -95,26 +88,33 @@ static void send_text_message_to_client(
// function may have modified) is forwarded to the other end; if they return
// false; it is not.
static bool process_default(shared_ptr<ServerState>,
enum class HandlerResult {
FORWARD = 0,
SUPPRESS,
MODIFIED,
};
static HandlerResult process_default(shared_ptr<ServerState>,
ProxyServer::LinkedSession&, uint16_t, uint32_t, string&) {
return true;
return HandlerResult::FORWARD;
}
static bool process_server_97(shared_ptr<ServerState>,
static HandlerResult process_server_97(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) {
// Trap 97 commands and always send 97 01 04 00. (If flag is 0, the client
// triggers cheat protection and deletes a bunch of data.)
session.send_to_end(false, 0x97, 0x01);
// Trap 97 commands and always send 97 01 04 00. This protects the client from
// cheat detection - if the flag is 0, the client triggers cheat detection and
// deletes a bunch of data.
session.client_channel.send(0x97, 0x01);
// Also, update the newserv client config so we'll know not to show the
// programs menu if they return to newserv.
session.newserv_client_config.cfg.flags |= Client::Flag::SAVE_ENABLED;
return false;
return HandlerResult::SUPPRESS;
}
static bool process_server_gc_9A(shared_ptr<ServerState>,
static HandlerResult process_server_gc_9A(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) {
if (!session.license) {
return true;
return HandlerResult::FORWARD;
}
C_LoginWithUnusedSpace_GC_9E cmd;
@@ -138,13 +138,13 @@ static bool process_server_gc_9A(shared_ptr<ServerState>,
// If there's a guild card number, a shorter 9E is sent that ends
// right after the client config data
session.send_to_end(
true, 0x9E, 0x01, &cmd,
session.server_channel.send(
0x9E, 0x01, &cmd,
sizeof(C_LoginWithUnusedSpace_GC_9E) - (session.remote_guild_card_number ? sizeof(cmd.unused_space) : 0));
return false;
return HandlerResult::SUPPRESS;
}
static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
static HandlerResult process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) {
if (session.version == GameVersion::PATCH && command == 0x17) {
throw invalid_argument("patch server sent 17 server init");
@@ -163,29 +163,29 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
forward_command(session, false, command, flag, data);
if (session.version == GameVersion::GC) {
session.server_input_crypt.reset(new PSOGCEncryption(cmd.server_key));
session.server_output_crypt.reset(new PSOGCEncryption(cmd.client_key));
session.client_input_crypt.reset(new PSOGCEncryption(cmd.client_key));
session.client_output_crypt.reset(new PSOGCEncryption(cmd.server_key));
session.server_channel.crypt_in.reset(new PSOGCEncryption(cmd.server_key));
session.server_channel.crypt_out.reset(new PSOGCEncryption(cmd.client_key));
session.client_channel.crypt_in.reset(new PSOGCEncryption(cmd.client_key));
session.client_channel.crypt_out.reset(new PSOGCEncryption(cmd.server_key));
} else { // PC or patch server (they both use PC encryption)
session.server_input_crypt.reset(new PSOPCEncryption(cmd.server_key));
session.server_output_crypt.reset(new PSOPCEncryption(cmd.client_key));
session.client_input_crypt.reset(new PSOPCEncryption(cmd.client_key));
session.client_output_crypt.reset(new PSOPCEncryption(cmd.server_key));
session.server_channel.crypt_in.reset(new PSOPCEncryption(cmd.server_key));
session.server_channel.crypt_out.reset(new PSOPCEncryption(cmd.client_key));
session.client_channel.crypt_in.reset(new PSOPCEncryption(cmd.client_key));
session.client_channel.crypt_out.reset(new PSOPCEncryption(cmd.server_key));
}
return false;
return HandlerResult::SUPPRESS;
}
session.log(INFO, "Existing license in linked session");
// This isn't forwarded to the client, so don't recreate the client's crypts
if ((session.version == GameVersion::PATCH) || (session.version == GameVersion::PC)) {
session.server_input_crypt.reset(new PSOPCEncryption(cmd.server_key));
session.server_output_crypt.reset(new PSOPCEncryption(cmd.client_key));
session.server_channel.crypt_in.reset(new PSOPCEncryption(cmd.server_key));
session.server_channel.crypt_out.reset(new PSOPCEncryption(cmd.client_key));
} else if (session.version == GameVersion::GC) {
session.server_input_crypt.reset(new PSOGCEncryption(cmd.server_key));
session.server_output_crypt.reset(new PSOGCEncryption(cmd.client_key));
session.server_channel.crypt_in.reset(new PSOGCEncryption(cmd.server_key));
session.server_channel.crypt_out.reset(new PSOGCEncryption(cmd.client_key));
} else {
throw invalid_argument("unsupported version");
}
@@ -195,8 +195,8 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
// in the patch server case, during the current session due to a hidden
// redirect).
if (session.version == GameVersion::PATCH) {
session.send_to_end(true, 0x02, 0x00);
return false;
session.server_channel.send(0x02);
return HandlerResult::SUPPRESS;
} else if (session.version == GameVersion::PC) {
C_Login_PC_9D cmd;
@@ -216,8 +216,8 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
cmd.name = session.character_name;
session.send_to_end(true, 0x9D, 0x00, &cmd, sizeof(cmd));
return false;
session.server_channel.send(0x9D, 0x00, &cmd, sizeof(cmd));
return HandlerResult::SUPPRESS;
} else if (session.version == GameVersion::GC) {
if (command == 0x17) {
@@ -229,8 +229,8 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
cmd.serial_number2 = cmd.serial_number;
cmd.access_key2 = cmd.access_key;
cmd.password = session.license->gc_password;
session.send_to_end(true, 0xDB, 0x00, &cmd, sizeof(cmd));
return false;
session.server_channel.send(0xDB, 0x00, &cmd, sizeof(cmd));
return HandlerResult::SUPPRESS;
} else {
// For command 02, send the same as if we had received 9A from the server
@@ -242,7 +242,7 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
}
}
static bool process_server_bb_03(shared_ptr<ServerState> s,
static HandlerResult process_server_bb_03(shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
// Most servers don't include after_message or have a shorter after_message
// than newserv does, so don't require it
@@ -261,9 +261,9 @@ static bool process_server_bb_03(shared_ptr<ServerState> s,
// being able to try all the crypts it knows to detect what type the client
// uses, but the client can't do this since it sends the first encrypted
// data on the connection.
session.server_input_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.server_channel.crypt_in.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false));
session.server_output_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.server_channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false));
// Forward the login command we saved during the unlinked session.
@@ -271,9 +271,9 @@ static bool process_server_bb_03(shared_ptr<ServerState> s,
*reinterpret_cast<le_uint32_t*>(session.login_command_bb.data() + 0x94) =
session.remote_ip_crc ^ (1309539928UL + 1248334810UL);
}
session.send_to_end(true, 0x93, 0x00, session.login_command_bb);
session.server_channel.send(0x93, 0x00, session.login_command_bb);
return false;
return HandlerResult::SUPPRESS;
// If there's no detector crypt, then the session is new and was linked
// immediately at connect time, and an 03 was not yet sent to the client, so
@@ -281,25 +281,25 @@ static bool process_server_bb_03(shared_ptr<ServerState> s,
} else {
// Forward the command to the client before setting up the crypts, so the
// client receives the unencrypted data
session.send_to_end(false, 0x03, 0x00, data);
session.client_channel.send(0x03, 0x00, data);
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
session.detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption(
s->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
session.client_input_crypt = session.detector_crypt;
session.client_output_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.client_channel.crypt_in = session.detector_crypt;
session.client_channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true));
session.server_input_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.server_channel.crypt_in.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false));
session.server_output_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
session.server_channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
session.detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false));
// We already forwarded the command, so don't do so again
return false;
return HandlerResult::SUPPRESS;
}
}
static bool process_server_dc_pc_gc_04(shared_ptr<ServerState>,
static HandlerResult 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
// bytes available. We should be prepared to handle that.
@@ -346,56 +346,64 @@ static bool process_server_dc_pc_gc_04(shared_ptr<ServerState>,
// We don't actually have a client checksum, of course... hopefully just
// random data will do (probably no private servers check this at all)
le_uint64_t checksum = random_object<uint64_t>() & 0x0000FFFFFFFFFFFF;
session.send_to_end(true, 0x96, 0x00, &checksum, sizeof(checksum));
session.server_channel.send(0x96, 0x00, &checksum, sizeof(checksum));
}
return true;
return session.license ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
}
static bool process_server_dc_pc_gc_06(shared_ptr<ServerState>,
static HandlerResult process_server_dc_pc_gc_06(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
if (session.license) {
auto& cmd = check_size_t<SC_TextHeader_01_06_11_B0_EE>(data,
sizeof(SC_TextHeader_01_06_11_B0_EE), 0xFFFF);
if (cmd.guild_card_number == session.remote_guild_card_number) {
cmd.guild_card_number = session.license->serial_number;
return HandlerResult::MODIFIED;
}
}
return true;
return HandlerResult::FORWARD;
}
template <typename CmdT>
static bool process_server_41(shared_ptr<ServerState>,
static HandlerResult process_server_41(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
bool modified = false;
if (session.license) {
auto& cmd = check_size_t<CmdT>(data);
if (cmd.searcher_guild_card_number == session.remote_guild_card_number) {
cmd.searcher_guild_card_number = session.license->serial_number;
modified = true;
}
if (cmd.result_guild_card_number == session.remote_guild_card_number) {
cmd.result_guild_card_number = session.license->serial_number;
modified = true;
}
}
return true;
return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
}
template <typename CmdT>
static bool process_server_81(shared_ptr<ServerState>,
static HandlerResult process_server_81(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
bool modified = false;
if (session.license) {
auto& cmd = check_size_t<CmdT>(data);
if (cmd.from_guild_card_number == session.remote_guild_card_number) {
cmd.from_guild_card_number = session.license->serial_number;
modified = true;
}
if (cmd.to_guild_card_number == session.remote_guild_card_number) {
cmd.to_guild_card_number = session.license->serial_number;
modified = true;
}
}
return true;
return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
}
static bool process_server_88(shared_ptr<ServerState>,
static HandlerResult process_server_88(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) {
bool modified = false;
if (session.license) {
size_t expected_size = sizeof(S_ArrowUpdateEntry_88) * flag;
auto* entries = &check_size_t<S_ArrowUpdateEntry_88>(data,
@@ -403,13 +411,14 @@ static bool process_server_88(shared_ptr<ServerState>,
for (size_t x = 0; x < flag; x++) {
if (entries[x].guild_card_number == session.remote_guild_card_number) {
entries[x].guild_card_number = session.license->serial_number;
modified = true;
}
}
}
return true;
return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
}
static bool process_server_B2(shared_ptr<ServerState>,
static HandlerResult process_server_B2(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) {
if (session.save_files) {
string output_filename = string_printf("code.%" PRId64 ".bin", now());
@@ -474,26 +483,27 @@ static bool process_server_B2(shared_ptr<ServerState>,
C_ExecuteCodeResult_B3 cmd;
cmd.return_value = session.function_call_return_value;
cmd.checksum = 0;
session.send_to_end(true, 0xB3, flag, &cmd, sizeof(cmd));
return false;
session.server_channel.send(0xB3, flag, &cmd, sizeof(cmd));
return HandlerResult::SUPPRESS;
} else {
return true;
return HandlerResult::FORWARD;
}
}
static bool process_server_E7(shared_ptr<ServerState>,
static HandlerResult process_server_E7(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
if (session.save_files) {
string output_filename = string_printf("player.%" PRId64 ".bin", now());
save_file(output_filename, data);
session.log(INFO, "Wrote player data to file %s", output_filename.c_str());
}
return true;
return HandlerResult::FORWARD;
}
template <typename CmdT>
static bool process_server_C4(shared_ptr<ServerState>,
static HandlerResult process_server_C4(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) {
bool modified = false;
if (session.license) {
size_t expected_size = sizeof(CmdT) * flag;
// Some servers (e.g. Schtserv) send extra data on the end of this command;
@@ -502,24 +512,27 @@ static bool process_server_C4(shared_ptr<ServerState>,
for (size_t x = 0; x < flag; x++) {
if (entries[x].guild_card_number == session.remote_guild_card_number) {
entries[x].guild_card_number = session.license->serial_number;
modified = true;
}
}
}
return true;
return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
}
static bool process_server_gc_E4(shared_ptr<ServerState>,
static HandlerResult process_server_gc_E4(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
auto& cmd = check_size_t<S_CardLobbyGame_GC_E4>(data);
bool modified = false;
for (size_t x = 0; x < 4; x++) {
if (cmd.entries[x].guild_card_number == session.remote_guild_card_number) {
cmd.entries[x].guild_card_number = session.license->serial_number;
modified = true;
}
}
return true;
return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
}
static bool process_server_bb_22(shared_ptr<ServerState>,
static HandlerResult process_server_bb_22(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
// We use this command (which is sent before the init encryption command) to
// detect a particular server behavior that we'll have to work around later.
@@ -535,10 +548,10 @@ static bool process_server_bb_22(shared_ptr<ServerState>,
session.log(INFO, "Enabling remote IP CRC patch");
session.enable_remote_ip_crc_patch = true;
}
return true;
return HandlerResult::FORWARD;
}
static bool process_server_game_19_patch_14(shared_ptr<ServerState>,
static HandlerResult process_server_game_19_patch_14(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t command, uint32_t, string& data) {
// If the command is shorter than 6 bytes, use the previous server command to
// fill it in. This simulates a behavior used by some private servers where a
@@ -570,17 +583,17 @@ static bool process_server_game_19_patch_14(shared_ptr<ServerState>,
sin->sin_addr.s_addr = cmd.address.load_raw();
sin->sin_port = htons(cmd.port);
if (!session.client_bev.get()) {
if (!session.client_channel.connected()) {
session.log(WARNING, "Received reconnect command with no destination present");
return false;
return HandlerResult::SUPPRESS;
} else if (command == 0x14) {
// On the patch server, hide redirects from the client completely. The new
// destination server will presumably send a new 02 command to start
// encryption; it appears that PSOBB doesn't fail if this happens, and
// simply re-initializes its encryption appropriately.
session.server_input_crypt.reset();
session.server_output_crypt.reset();
session.server_channel.crypt_in.reset();
session.server_channel.crypt_out.reset();
struct sockaddr_in* dest_sin = reinterpret_cast<sockaddr_in*>(
&session.next_destination);
@@ -588,46 +601,41 @@ static bool process_server_game_19_patch_14(shared_ptr<ServerState>,
dest_sin->sin_addr.s_addr = cmd.address.load_raw();
dest_sin->sin_port = cmd.port;
session.connect();
return false;
return HandlerResult::SUPPRESS;
} else {
// If the client is on a virtual connection (fd < 0), only change
// the port (so we'll know which version to treat the next
// connection as). It's better to leave the address as-is so we
// can circumvent the Plus/Ep3 same-network-server check.
int fd = bufferevent_getfd(session.client_bev.get());
if (fd >= 0) {
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) {
if (session.client_channel.is_virtual_connection) {
cmd.port = session.local_port;
} else {
const struct sockaddr_in* sin = reinterpret_cast<const struct sockaddr_in*>(
&session.client_channel.local_addr);
if (sin->sin_family != AF_INET) {
throw logic_error("existing connection is not ipv4");
}
struct sockaddr_in* sockname_sin = reinterpret_cast<struct sockaddr_in*>(
&sockname_ss);
cmd.address.store_raw(sockname_sin->sin_addr.s_addr);
cmd.port = ntohs(sockname_sin->sin_port);
} else {
cmd.port = session.local_port;
cmd.address.store_raw(sin->sin_addr.s_addr);
cmd.port = ntohs(sin->sin_port);
}
return true;
return HandlerResult::MODIFIED;
}
}
static bool process_server_gc_1A_D5(shared_ptr<ServerState>,
static HandlerResult process_server_gc_1A_D5(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) {
// 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.cfg.flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION) {
session.send_to_end(true, 0xD6, 0x00, "", 0);
// If the client is a version that sends close confirmations and 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.version == GameVersion::GC) &&
(session.newserv_client_config.cfg.flags & Client::Flag::NO_MESSAGE_BOX_CLOSE_CONFIRMATION)) {
session.server_channel.send(0xD6);
}
return true;
return HandlerResult::FORWARD;
}
static bool process_server_60_62_6C_6D_C9_CB(shared_ptr<ServerState>,
static HandlerResult process_server_60_62_6C_6D_C9_CB(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
check_implemented_subcommand(session.id, data);
@@ -644,11 +652,11 @@ static bool process_server_60_62_6C_6D_C9_CB(shared_ptr<ServerState>,
}
}
return true;
return HandlerResult::FORWARD;
}
template <typename T>
static bool process_server_44_A6(shared_ptr<ServerState>,
static HandlerResult process_server_44_A6(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t command, uint32_t, string& data) {
if (session.save_files) {
const auto& cmd = check_size_t<S_OpenFile_PC_GC_44_A6>(data);
@@ -672,10 +680,10 @@ static bool process_server_44_A6(shared_ptr<ServerState>,
session.saving_files.emplace(cmd.filename, move(sf));
session.log(INFO, "Opened file %s", output_filename.c_str());
}
return true;
return HandlerResult::FORWARD;
}
static bool process_server_13_A7(shared_ptr<ServerState>,
static HandlerResult process_server_13_A7(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
if (session.save_files) {
const auto& cmd = check_size_t<S_WriteFile_13_A7>(data);
@@ -686,7 +694,7 @@ static bool process_server_13_A7(shared_ptr<ServerState>,
} catch (const out_of_range&) {
string filename = cmd.filename;
session.log(WARNING, "Received data for non-open file %s", filename.c_str());
return true;
return HandlerResult::FORWARD;
}
size_t bytes_to_write = cmd.data_size;
@@ -709,33 +717,33 @@ static bool process_server_13_A7(shared_ptr<ServerState>,
session.saving_files.erase(cmd.filename);
}
}
return true;
return HandlerResult::FORWARD;
}
static bool process_server_gc_B8(shared_ptr<ServerState>,
static HandlerResult process_server_gc_B8(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
if (session.save_files) {
if (data.size() < 4) {
session.log(WARNING, "Card list data size is too small; skipping file");
return true;
session.log(WARNING, "Card list data size is too small; not saving file");
return HandlerResult::FORWARD;
}
StringReader r(data);
size_t size = r.get_u32l();
if (r.remaining() < size) {
session.log(WARNING, "Card list data size extends beyond end of command; skipping file");
return true;
session.log(WARNING, "Card list data size extends beyond end of command; not saving file");
return HandlerResult::FORWARD;
}
string output_filename = string_printf("cardupdate.%" PRIu64 ".mnr", now());
save_file(output_filename, r.read(size));
session.log(INFO, "Wrote %zu bytes to %s", size, output_filename.c_str());
}
return true;
return HandlerResult::FORWARD;
}
template <typename CmdT>
static bool process_server_65_67_68(shared_ptr<ServerState>,
static HandlerResult process_server_65_67_68(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) {
if (command == 0x67) {
session.lobby_players.clear();
@@ -754,6 +762,7 @@ static bool process_server_65_67_68(shared_ptr<ServerState>,
size_t expected_size = offsetof(CmdT, entries) + sizeof(typename CmdT::Entry) * flag;
auto& cmd = check_size_t<CmdT>(data, expected_size, expected_size);
bool modified = false;
session.lobby_client_id = cmd.client_id;
for (size_t x = 0; x < flag; x++) {
@@ -763,6 +772,7 @@ static bool process_server_65_67_68(shared_ptr<ServerState>,
} else {
if (session.license && (cmd.entries[x].lobby_data.guild_card == session.remote_guild_card_number)) {
cmd.entries[x].lobby_data.guild_card = session.license->serial_number;
modified = true;
}
session.lobby_players[index].guild_card_number = cmd.entries[x].lobby_data.guild_card;
ptext<char, 0x10> name = cmd.entries[x].disp.name;
@@ -776,16 +786,18 @@ static bool process_server_65_67_68(shared_ptr<ServerState>,
if (session.override_lobby_event >= 0) {
cmd.event = session.override_lobby_event;
modified = true;
}
if (session.override_lobby_number >= 0) {
cmd.lobby_number = session.override_lobby_number;
modified = true;
}
return true;
return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
}
template <typename CmdT>
static bool process_server_64(shared_ptr<ServerState>,
static HandlerResult process_server_64(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) {
// We don't need to clear lobby_players here because we always
// overwrite all 4 entries for this command
@@ -796,11 +808,13 @@ static bool process_server_64(shared_ptr<ServerState>,
? sizeof(CmdT)
: offsetof(CmdT, players_ep3);
auto& cmd = check_size_t<CmdT>(data, expected_size, expected_size);
bool modified = false;
session.lobby_client_id = cmd.client_id;
for (size_t x = 0; x < flag; x++) {
if (cmd.lobby_data[x].guild_card == session.remote_guild_card_number) {
cmd.lobby_data[x].guild_card = session.license->serial_number;
modified = true;
}
session.lobby_players[x].guild_card_number = cmd.lobby_data[x].guild_card;
if (data.size() == sizeof(CmdT)) {
@@ -817,15 +831,17 @@ static bool process_server_64(shared_ptr<ServerState>,
if (session.override_section_id >= 0) {
cmd.section_id = session.override_section_id;
modified = true;
}
if (session.override_lobby_event >= 0) {
cmd.event = session.override_lobby_event;
modified = true;
}
return true;
return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
}
static bool process_server_66_69(shared_ptr<ServerState>,
static HandlerResult process_server_66_69(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
const auto& cmd = check_size_t<S_LeaveLobby_66_69_Ep3_E9>(data);
size_t index = cmd.client_id;
@@ -836,44 +852,71 @@ static bool process_server_66_69(shared_ptr<ServerState>,
session.lobby_players[index].name.clear();
session.log(INFO, "Removed lobby player (%zu)", index);
}
return true;
return HandlerResult::FORWARD;
}
static bool process_client_06(shared_ptr<ServerState>,
static HandlerResult process_client_06(shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
if (data.size() >= 12) {
// If this chat message looks like a newserv chat command, suppress it
if (session.suppress_newserv_commands &&
(data[8] == '$' || (data[8] == '\t' && data[9] != 'C' && data[10] == '$'))) {
session.log(WARNING, "Chat message appears to be a server command; dropping it");
return false;
u16string text;
if (session.version == GameVersion::PC || session.version == GameVersion::BB) {
const auto& cmd = check_size_t<C_Chat_06>(data, sizeof(C_Chat_06), 0xFFFF);
text = u16string(cmd.text.pcbb, (data.size() - sizeof(C_Chat_06)) / sizeof(char16_t));
} else {
const auto& cmd = check_size_t<C_Chat_06>(data, sizeof(C_Chat_06), 0xFFFF);
text = decode_sjis(cmd.text.dcgc, data.size() - sizeof(C_Chat_06));
}
strip_trailing_zeroes(text);
if (text.empty()) {
return HandlerResult::SUPPRESS;
}
bool is_command = (text[0] == '$' || (text[0] == '\t' && text[1] != 'C' && text[2] == '$'));
if (is_command) {
text = text.substr((text[0] == '$') ? 0 : 2);
if (text.size() >= 2 && text[1] == '$') {
send_chat_message(session.server_channel, text.substr(1));
return HandlerResult::SUPPRESS;
} else {
process_chat_command(s, session, text);
return HandlerResult::SUPPRESS;
}
} else if (session.enable_chat_filter) {
add_color_inplace(data.data() + 8, data.size() - 8);
// TODO: We should return MODIFIED here if the message was changed by
// the add_color_inplace call
return HandlerResult::FORWARD;
} else {
return HandlerResult::FORWARD;
}
} else {
return HandlerResult::FORWARD;
}
return true;
}
static bool process_client_40(shared_ptr<ServerState>,
static HandlerResult process_client_40(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
bool modified = false;
if (session.license) {
auto& cmd = check_size_t<C_GuildCardSearch_40>(data);
if (cmd.searcher_guild_card_number == session.license->serial_number) {
cmd.searcher_guild_card_number = session.remote_guild_card_number;
modified = true;
}
if (cmd.target_guild_card_number == session.license->serial_number) {
cmd.target_guild_card_number = session.remote_guild_card_number;
modified = true;
}
}
return true;
return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
}
template <typename CmdT>
static bool process_client_81(shared_ptr<ServerState>,
static HandlerResult process_client_81(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
auto& cmd = check_size_t<SC_SimpleMail_GC_81>(data);
if (session.license) {
@@ -886,11 +929,11 @@ static bool process_client_81(shared_ptr<ServerState>,
}
// GC clients send uninitialized memory here; don't forward it
cmd.text.clear_after(cmd.text.len());
return true;
return HandlerResult::MODIFIED;
}
template <typename SendGuildCardCmdT>
static bool process_client_60_62_6C_6D_C9_CB(shared_ptr<ServerState> s,
static HandlerResult process_client_60_62_6C_6D_C9_CB(shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) {
if (session.license && !data.empty()) {
if (data[0] == 0x06) {
@@ -912,7 +955,7 @@ static bool process_client_60_62_6C_6D_C9_CB(shared_ptr<ServerState> s,
sub2.byte[3] = (amount > 0xFF) ? 0xFF : amount;
amount -= sub2.byte[3];
}
session.send_to_end(false, 0x60, 0x00, subs.data(), subs.size() * sizeof(PSOSubcommand));
session.client_channel.send(0x60, 0x00, subs.data(), subs.size() * sizeof(PSOSubcommand));
}
} else if (data[0] == 0x48) {
if (session.infinite_tp) {
@@ -923,7 +966,7 @@ static bool process_client_60_62_6C_6D_C9_CB(shared_ptr<ServerState> s,
subs[1].word[0] = 0x0000;
subs[1].byte[2] = PlayerStatsChange::ADD_TP;
subs[1].byte[3] = 0xFF;
session.send_to_end(false, 0x60, 0x00, &subs[0], sizeof(subs));
session.client_channel.send(0x60, 0x00, &subs[0], sizeof(subs));
}
}
}
@@ -931,31 +974,31 @@ static bool process_client_60_62_6C_6D_C9_CB(shared_ptr<ServerState> s,
}
template <>
bool process_client_60_62_6C_6D_C9_CB<void>(shared_ptr<ServerState>,
HandlerResult process_client_60_62_6C_6D_C9_CB<void>(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) {
check_implemented_subcommand(session.id, data);
if (!data.empty() && (data[0] == 0x05) && session.enable_switch_assist) {
if (!data.empty() && (data[0] == 0x05) && session.switch_assist) {
auto& cmd = check_size_t<G_SwitchStateChanged_6x05>(data);
if (cmd.enabled && cmd.switch_id != 0xFFFF) {
if (session.last_switch_enabled_command.subcommand == 0x05) {
session.log(INFO, "Switch assist: replaying previous enable command");
session.send_to_end(true, 0x60, 0x00, &session.last_switch_enabled_command,
session.server_channel.send(0x60, 0x00, &session.last_switch_enabled_command,
sizeof(session.last_switch_enabled_command));
session.send_to_end(false, 0x60, 0x00, &session.last_switch_enabled_command,
session.client_channel.send(0x60, 0x00, &session.last_switch_enabled_command,
sizeof(session.last_switch_enabled_command));
}
session.last_switch_enabled_command = cmd;
}
}
return true;
return HandlerResult::FORWARD;
}
static bool process_client_dc_pc_gc_A0_A1(shared_ptr<ServerState> s,
static HandlerResult process_client_dc_pc_gc_A0_A1(shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) {
if (!session.license) {
return true;
return HandlerResult::FORWARD;
}
// For licensed sessions, send them back to newserv's main menu instead of
@@ -969,7 +1012,7 @@ static bool process_client_dc_pc_gc_A0_A1(shared_ptr<ServerState> s,
uint8_t leaving_id = x;
uint8_t leader_id = session.lobby_client_id;
S_LeaveLobby_66_69_Ep3_E9 cmd = {leaving_id, leader_id, 0};
session.send_to_end(false, 0x69, leaving_id, &cmd, sizeof(cmd));
session.client_channel.send(0x69, leaving_id, &cmd, sizeof(cmd));
}
string encoded_name = encode_sjis(s->name);
@@ -981,7 +1024,7 @@ static bool process_client_dc_pc_gc_A0_A1(shared_ptr<ServerState> s,
update_client_config_cmd.player_tag = 0x00010000;
update_client_config_cmd.guild_card_number = session.license->serial_number;
update_client_config_cmd.cfg = session.newserv_client_config.cfg;
session.send_to_end(false, 0x04, 0x00, &update_client_config_cmd, sizeof(update_client_config_cmd));
session.client_channel.send(0x04, 0x00, &update_client_config_cmd, sizeof(update_client_config_cmd));
static const vector<string> version_to_port_name({
"dc-login", "pc-login", "bb-patch", "gc-us3", "bb-login"});
@@ -995,34 +1038,29 @@ static bool process_client_dc_pc_gc_A0_A1(shared_ptr<ServerState> s,
// 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(session.client_bev.get());
if (fd < 0) {
if (session.client_channel.is_virtual_connection) {
struct sockaddr_in* dest_sin = reinterpret_cast<struct sockaddr_in*>(&session.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) {
const struct sockaddr_in* sin = reinterpret_cast<const struct sockaddr_in*>(
&session.client_channel.local_addr);
if (sin->sin_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);
reconnect_cmd.address.store_raw(sin->sin_addr.s_addr);
}
session.send_to_end(false, 0x19, 0x00, &reconnect_cmd, sizeof(reconnect_cmd));
session.client_channel.send(0x19, 0x00, &reconnect_cmd, sizeof(reconnect_cmd));
return false;
return HandlerResult::SUPPRESS;
}
typedef bool (*process_command_t)(
typedef HandlerResult (*process_command_t)(
shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session,
uint16_t command,
@@ -1242,9 +1280,18 @@ void process_proxy_command(
string& data) {
auto fn = get_handler(session.version, from_server, command);
try {
bool should_forward = fn(s, session, command, flag, data);
if (should_forward) {
auto res = fn(s, session, command, flag, data);
if (res == HandlerResult::FORWARD) {
forward_command(session, !from_server, command, flag, data, false);
} else if (res == HandlerResult::MODIFIED) {
session.log(INFO, "The preceding command from the %s was modified in transit",
from_server ? "server" : "client");
forward_command(session, !from_server, command, flag, data);
} else if (res == HandlerResult::SUPPRESS) {
session.log(INFO, "The preceding command from the %s was not forwarded",
from_server ? "server" : "client");
} else {
throw logic_error("invalid handler result");
}
} catch (const exception& e) {
session.log(ERROR, "Failed to process command: %s", e.what());