improve bb proxy robustness
This commit is contained in:
@@ -913,11 +913,12 @@ struct C_Login_BB_93 {
|
||||
union ClientConfigFields {
|
||||
ClientConfigBB cfg;
|
||||
ptext<char, 0x28> version_string;
|
||||
le_uint32_t as_u32[10];
|
||||
};
|
||||
|
||||
ClientConfigFields old_clients_cfg;
|
||||
struct NewFormat {
|
||||
uint64_t hardware_info;
|
||||
le_uint32_t hardware_info[2];
|
||||
ClientConfigFields cfg;
|
||||
} new_clients;
|
||||
} var;
|
||||
@@ -1544,13 +1545,14 @@ struct C_GuildCardDataRequest_BB_03DC {
|
||||
le_uint32_t cont;
|
||||
};
|
||||
|
||||
// DD (S->C): Unknown (BB)
|
||||
// header.flag is used, but the command body is unused (no other arguments).
|
||||
// DD (S->C): Send quest state to joining player (BB)
|
||||
// header.flag is the client ID that the leader should send quest state to.
|
||||
// No other arguments
|
||||
|
||||
// DE (S->C): Unknown (BB)
|
||||
// DE (S->C): Rare monster configuration (BB)
|
||||
|
||||
struct S_Unknown_BB_DE {
|
||||
le_uint32_t unknown_a1[8];
|
||||
struct S_RareMonsterConfig_BB_DE {
|
||||
le_uint16_t data[16];
|
||||
};
|
||||
|
||||
// DF: Invalid command
|
||||
|
||||
+86
-25
@@ -17,6 +17,7 @@
|
||||
#include <iostream>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
@@ -226,34 +227,61 @@ static bool process_server_pc_gc_patch_02_17(shared_ptr<ServerState> s,
|
||||
}
|
||||
}
|
||||
|
||||
static bool process_server_bb_03(shared_ptr<ServerState>,
|
||||
static bool 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
|
||||
// Most servers don't include after_message or have a shorter after_message
|
||||
// than newserv does, so don't require it
|
||||
const auto& cmd = check_size_t<S_ServerInit_BB_03>(data,
|
||||
offsetof(S_ServerInit_BB_03, after_message), 0xFFFF);
|
||||
|
||||
if (!session.detector_crypt.get()) {
|
||||
throw runtime_error("BB linked session has no detector crypt");
|
||||
// If the session has a detector crypt, then it was resumed from an unlinked
|
||||
// session, during which we already sent an 03 command.
|
||||
if (session.detector_crypt.get()) {
|
||||
if (session.login_command_bb.empty()) {
|
||||
throw logic_error("linked BB session does not have a saved login command");
|
||||
}
|
||||
|
||||
// This isn't forwarded to the client, so only recreate the server's crypts.
|
||||
// Use the same crypt type as the client... the server has the luxury of
|
||||
// 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.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false));
|
||||
session.server_output_crypt.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.
|
||||
if (session.enable_remote_ip_crc_patch && (session.login_command_bb.size() >= 0x98)) {
|
||||
*reinterpret_cast<le_uint32_t*>(session.login_command_bb.data() + 0x94) =
|
||||
session.remote_ip_crc ^ (1309539928 + 1248334810);
|
||||
}
|
||||
session.send_to_end(true, 0x93, 0x00, session.login_command_bb);
|
||||
|
||||
return false;
|
||||
|
||||
// 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
|
||||
// we should forward this one.
|
||||
} 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);
|
||||
|
||||
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.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true));
|
||||
session.server_input_crypt.reset(new PSOBBMultiKeyImitatorEncryption(
|
||||
session.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false));
|
||||
session.server_output_crypt.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;
|
||||
}
|
||||
if (session.login_command_bb.empty()) {
|
||||
throw logic_error("linked BB session does not have a saved login command");
|
||||
}
|
||||
|
||||
// This isn't forwarded to the client, so only recreate the server's crypts.
|
||||
// Use the same crypt type as the client... the server has the luxury of 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.detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false));
|
||||
session.server_output_crypt.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.
|
||||
session.send_to_end(true, 0x93, 0x00, session.login_command_bb);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool process_server_dc_pc_gc_04(shared_ptr<ServerState>,
|
||||
@@ -386,6 +414,16 @@ static bool process_server_B2(shared_ptr<ServerState>,
|
||||
}
|
||||
}
|
||||
|
||||
static bool 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.bin.%" PRId64, now());
|
||||
save_file(output_filename, data);
|
||||
session.log(INFO, "Wrote player data to file %s", output_filename.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static bool process_server_C4(shared_ptr<ServerState>,
|
||||
ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string& data) {
|
||||
@@ -414,6 +452,25 @@ static bool process_server_gc_E4(shared_ptr<ServerState>,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool 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.
|
||||
// It looks like this command's existence is another anti-proxy measure, since
|
||||
// this command is 0x34 bytes in total, and the logic that adds padding bytes
|
||||
// when the command size isn't a multiple of 8 is only active when encryption
|
||||
// is enabled. Presumably some simpler proxies would get this wrong.
|
||||
// Editor's note: There's an unsavory message in this command's data field,
|
||||
// hence the hash here instead of a direct string comparison. I'd love to hear
|
||||
// the story behind why they put that string there.
|
||||
if ((data.size() == 0x2C) &&
|
||||
(fnv1a64(data.data(), data.size()) == 0x8AF8314316A27994)) {
|
||||
session.log(INFO, "Enabling remote IP CRC patch");
|
||||
session.enable_remote_ip_crc_patch = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool 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
|
||||
@@ -432,6 +489,10 @@ static bool process_server_game_19_patch_14(shared_ptr<ServerState>,
|
||||
data.resize(sizeof(S_Reconnect_19), '\0');
|
||||
}
|
||||
|
||||
if (session.enable_remote_ip_crc_patch) {
|
||||
session.remote_ip_crc = crc32(data.data(), 4);
|
||||
}
|
||||
|
||||
// This weird maximum size is here to properly handle the version-split
|
||||
// command that some servers (including newserv) use on port 9100
|
||||
auto& cmd = check_size_t<S_Reconnect_19>(data, sizeof(S_Reconnect_19), 0xB0);
|
||||
@@ -951,7 +1012,7 @@ static process_command_t gc_server_handlers[0x100] = {
|
||||
static process_command_t bb_server_handlers[0x100] = {
|
||||
/* 00 */ defh, defh, defh, process_server_bb_03, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 10 */ defh, defh, defh, process_server_13_A7, defh, defh, defh, defh, defh, process_server_game_19_patch_14, defh, defh, defh, defh, defh, defh,
|
||||
/* 20 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 20 */ defh, defh, process_server_bb_22, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 30 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 40 */ defh, process_server_41<S_GuildCardSearchResult_BB_41>, defh, defh, process_server_44_A6<S_OpenFile_BB_44_A6>, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* 50 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
@@ -963,7 +1024,7 @@ static process_command_t bb_server_handlers[0x100] = {
|
||||
/* B0 */ defh, defh, process_server_B2, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* C0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* D0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* E0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* E0 */ defh, defh, defh, defh, defh, defh, defh, process_server_E7, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
/* F0 */ defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh, defh,
|
||||
};
|
||||
static process_command_t patch_server_handlers[0x100] = {
|
||||
|
||||
+6
-2
@@ -32,7 +32,8 @@ using namespace std;
|
||||
|
||||
|
||||
|
||||
static const uint32_t SESSION_TIMEOUT_USECS = 10 * 60 * 1000000; // 10 minutes
|
||||
static const uint32_t LICENSED_SESSION_TIMEOUT_USECS = 5 * 60 * 1000000; // 5 minutes
|
||||
static const uint32_t UNLICENSED_SESSION_TIMEOUT_USECS = 10 * 1000000; // 10 seconds
|
||||
|
||||
|
||||
|
||||
@@ -430,6 +431,8 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
client_bev(nullptr, flush_and_free_bufferevent),
|
||||
server_bev(nullptr, flush_and_free_bufferevent),
|
||||
local_port(local_port),
|
||||
remote_ip_crc(0),
|
||||
enable_remote_ip_crc_patch(false),
|
||||
version(version),
|
||||
sub_version(0), // This is set during resume()
|
||||
remote_guild_card_number(0),
|
||||
@@ -653,7 +656,8 @@ void ProxyServer::LinkedSession::disconnect() {
|
||||
|
||||
// Set a timeout to delete the session entirely (in case the client doesn't
|
||||
// reconnect)
|
||||
struct timeval tv = usecs_to_timeval(SESSION_TIMEOUT_USECS);
|
||||
struct timeval tv = usecs_to_timeval(this->license.get()
|
||||
? LICENSED_SESSION_TIMEOUT_USECS : UNLICENSED_SESSION_TIMEOUT_USECS);
|
||||
event_add(this->timeout_event.get(), &tv);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ public:
|
||||
struct sockaddr_storage next_destination;
|
||||
|
||||
uint8_t prev_server_command_bytes[6];
|
||||
uint32_t remote_ip_crc;
|
||||
bool enable_remote_ip_crc_patch;
|
||||
|
||||
GameVersion version;
|
||||
uint32_t sub_version;
|
||||
|
||||
Reference in New Issue
Block a user