hide remote guild card number in licensed proxy sessions
This commit is contained in:
@@ -30,7 +30,6 @@ Current known issues / missing features:
|
|||||||
- The trade window isn't implemented yet.
|
- The trade window isn't implemented yet.
|
||||||
- PSO PC and PSOBB are essentially entirely untested. Only GC is fairly well-tested.
|
- PSO PC and PSOBB are essentially entirely untested. Only GC is fairly well-tested.
|
||||||
- Add all the chat commands that khyller used to have. (Most, but not all, currently exist in newserv.)
|
- Add all the chat commands that khyller used to have. (Most, but not all, currently exist in newserv.)
|
||||||
- Hide the guild card number assigned by the remote server when proxying, and instead rewrite all commands that include the client's guild card number.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|||||||
+47
-5
@@ -1,5 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <phosg/Strings.hh>
|
||||||
#include <phosg/Encoding.hh>
|
#include <phosg/Encoding.hh>
|
||||||
|
|
||||||
#include "PSOProtocol.hh"
|
#include "PSOProtocol.hh"
|
||||||
@@ -33,6 +36,45 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This function is used in a lot of places to check received command sizes and
|
||||||
|
// cast them to the appropriate type
|
||||||
|
template <typename T>
|
||||||
|
const T& check_size_t(
|
||||||
|
const std::string& data,
|
||||||
|
size_t min_size = sizeof(T),
|
||||||
|
size_t max_size = sizeof(T)) {
|
||||||
|
if (data.size() < min_size) {
|
||||||
|
throw std::runtime_error(string_printf(
|
||||||
|
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
|
||||||
|
min_size, data.size()));
|
||||||
|
}
|
||||||
|
if (data.size() > max_size) {
|
||||||
|
throw std::runtime_error(string_printf(
|
||||||
|
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
|
||||||
|
max_size, data.size()));
|
||||||
|
}
|
||||||
|
return *reinterpret_cast<const T*>(data.data());
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
T& check_size_t(
|
||||||
|
std::string& data,
|
||||||
|
size_t min_size = sizeof(T),
|
||||||
|
size_t max_size = sizeof(T)) {
|
||||||
|
if (data.size() < min_size) {
|
||||||
|
throw std::runtime_error(string_printf(
|
||||||
|
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
|
||||||
|
min_size, data.size()));
|
||||||
|
}
|
||||||
|
if (data.size() > max_size) {
|
||||||
|
throw std::runtime_error(string_printf(
|
||||||
|
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
|
||||||
|
max_size, data.size()));
|
||||||
|
}
|
||||||
|
return *reinterpret_cast<T*>(data.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 00: Invalid command
|
// 00: Invalid command
|
||||||
|
|
||||||
// 01 (S->C): Lobby message box
|
// 01 (S->C): Lobby message box
|
||||||
@@ -52,7 +94,7 @@ struct S_ServerInit_DC_PC_GC_02_17 {
|
|||||||
le_uint32_t server_key; // Key for data sent by server
|
le_uint32_t server_key; // Key for data sent by server
|
||||||
le_uint32_t client_key; // Key for data sent by client
|
le_uint32_t client_key; // Key for data sent by client
|
||||||
// This field is not part of SEGA's original implementation
|
// This field is not part of SEGA's original implementation
|
||||||
ptext<char, 0xBC> after_message;
|
ptext<char, 0xC0> after_message;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct S_ServerInit_Patch_02 {
|
struct S_ServerInit_Patch_02 {
|
||||||
@@ -71,7 +113,7 @@ struct S_ServerInit_BB_03 {
|
|||||||
parray<uint8_t, 0x30> server_key;
|
parray<uint8_t, 0x30> server_key;
|
||||||
parray<uint8_t, 0x30> client_key;
|
parray<uint8_t, 0x30> client_key;
|
||||||
// This field is not part of SEGA's original implementation
|
// This field is not part of SEGA's original implementation
|
||||||
ptext<char, 0xBC> after_message;
|
ptext<char, 0xC0> after_message;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 04 (S->C): Set guild card number and update client config ("security data")
|
// 04 (S->C): Set guild card number and update client config ("security data")
|
||||||
@@ -330,9 +372,9 @@ struct S_GuildCardSearchResult {
|
|||||||
ptext<char, 0x3C> unused;
|
ptext<char, 0x3C> unused;
|
||||||
ptext<CharT, 0x20> name;
|
ptext<CharT, 0x20> name;
|
||||||
};
|
};
|
||||||
struct S_GuildCardSearchResult_PC_40 : S_GuildCardSearchResult<PSOCommandHeaderPC, char16_t> { };
|
struct S_GuildCardSearchResult_PC_41 : S_GuildCardSearchResult<PSOCommandHeaderPC, char16_t> { };
|
||||||
struct S_GuildCardSearchResult_DC_GC_40 : S_GuildCardSearchResult<PSOCommandHeaderDCGC, char> { };
|
struct S_GuildCardSearchResult_DC_GC_41 : S_GuildCardSearchResult<PSOCommandHeaderDCGC, char> { };
|
||||||
struct S_GuildCardSearchResult_BB_40 : S_GuildCardSearchResult<PSOCommandHeaderBB, char16_t> { };
|
struct S_GuildCardSearchResult_BB_41 : S_GuildCardSearchResult<PSOCommandHeaderBB, char16_t> { };
|
||||||
|
|
||||||
// 41: Invalid command
|
// 41: Invalid command
|
||||||
// 42: Invalid command
|
// 42: Invalid command
|
||||||
|
|||||||
+338
-147
@@ -237,6 +237,7 @@ void ProxyServer::UnlinkedSession::on_client_input() {
|
|||||||
string character_name;
|
string character_name;
|
||||||
ClientConfig client_config;
|
ClientConfig client_config;
|
||||||
|
|
||||||
|
try {
|
||||||
for_each_received_command(this->bev.get(), this->version, this->crypt_in.get(),
|
for_each_received_command(this->bev.get(), this->version, this->crypt_in.get(),
|
||||||
[&](uint16_t command, uint32_t flag, const string& data) {
|
[&](uint16_t command, uint32_t flag, const string& data) {
|
||||||
print_received_command(command, flag, data.data(), data.size(),
|
print_received_command(command, flag, data.data(), data.size(),
|
||||||
@@ -246,55 +247,39 @@ void ProxyServer::UnlinkedSession::on_client_input() {
|
|||||||
// We should only get a 9D while the session is unlinked; if we get
|
// We should only get a 9D while the session is unlinked; if we get
|
||||||
// anything else, disconnect
|
// anything else, disconnect
|
||||||
if (command != 0x9D) {
|
if (command != 0x9D) {
|
||||||
log(ERROR, "[ProxyServer] Received unexpected command %02hX", command);
|
throw runtime_error("command is not 9D");
|
||||||
should_close_unlinked_session = true;
|
}
|
||||||
} else if (data.size() < sizeof(C_Login_GC_9E) - 0x64) {
|
const auto& cmd = check_size_t<C_Login_PC_9D>(data, sizeof(C_Login_PC_9D), sizeof(C_LoginWithUnusedSpace_PC_9D));
|
||||||
log(ERROR, "[ProxyServer] Login command is too small");
|
uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16);
|
||||||
should_close_unlinked_session = true;
|
|
||||||
} else {
|
|
||||||
const auto* cmd = reinterpret_cast<const C_Login_GC_9E*>(data.data());
|
|
||||||
uint32_t serial_number = strtoul(cmd->serial_number.c_str(), nullptr, 16);
|
|
||||||
try {
|
|
||||||
license = this->server->state->license_manager->verify_pc(
|
license = this->server->state->license_manager->verify_pc(
|
||||||
serial_number, cmd->access_key.c_str(), nullptr);
|
serial_number, cmd.access_key.c_str(), nullptr);
|
||||||
sub_version = cmd->sub_version;
|
sub_version = cmd.sub_version;
|
||||||
character_name = cmd->name;
|
character_name = cmd.name;
|
||||||
client_config = cmd->client_config.cfg;
|
|
||||||
} catch (const exception& e) {
|
|
||||||
log(ERROR, "[ProxyServer] Unlinked client has no valid license");
|
|
||||||
should_close_unlinked_session = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (this->version == GameVersion::GC) {
|
} else if (this->version == GameVersion::GC) {
|
||||||
// We should only get a 9E while the session is unlinked; if we get
|
// We should only get a 9E while the session is unlinked; if we get
|
||||||
// anything else, disconnect
|
// anything else, disconnect
|
||||||
if (command != 0x9E) {
|
if (command != 0x9E) {
|
||||||
log(ERROR, "[ProxyServer] Received unexpected command %02hX", command);
|
throw runtime_error("command is not 9E");
|
||||||
should_close_unlinked_session = true;
|
}
|
||||||
} else if (data.size() < sizeof(C_Login_GC_9E) - 0x64) {
|
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_Login_GC_9E), sizeof(C_LoginWithUnusedSpace_GC_9E));
|
||||||
log(ERROR, "[ProxyServer] Login command is too small");
|
uint32_t serial_number = strtoul(cmd.serial_number.c_str(), nullptr, 16);
|
||||||
should_close_unlinked_session = true;
|
|
||||||
} else {
|
|
||||||
const auto* cmd = reinterpret_cast<const C_Login_GC_9E*>(data.data());
|
|
||||||
uint32_t serial_number = strtoul(cmd->serial_number.c_str(), nullptr, 16);
|
|
||||||
try {
|
|
||||||
license = this->server->state->license_manager->verify_gc(
|
license = this->server->state->license_manager->verify_gc(
|
||||||
serial_number, cmd->access_key.c_str(), nullptr);
|
serial_number, cmd.access_key.c_str(), nullptr);
|
||||||
sub_version = cmd->sub_version;
|
sub_version = cmd.sub_version;
|
||||||
character_name = cmd->name;
|
character_name = cmd.name;
|
||||||
client_config = cmd->client_config.cfg;
|
client_config = cmd.client_config.cfg;
|
||||||
} catch (const exception& e) {
|
|
||||||
log(ERROR, "[ProxyServer] Unlinked client has no valid license");
|
|
||||||
should_close_unlinked_session = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw logic_error("unsupported unlinked session version");
|
throw logic_error("unsupported unlinked session version");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
} catch (const exception& e) {
|
||||||
|
log(ERROR, "[ProxyServer] Failed to process command from unlinked client: %s", e.what());
|
||||||
|
should_close_unlinked_session = true;
|
||||||
|
}
|
||||||
|
|
||||||
struct bufferevent* session_key = this->bev.get();
|
struct bufferevent* session_key = this->bev.get();
|
||||||
|
|
||||||
// If license is non-null, then the client has a password and can be connected
|
// If license is non-null, then the client has a password and can be connected
|
||||||
@@ -379,7 +364,7 @@ ProxyServer::LinkedSession::LinkedSession(
|
|||||||
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),
|
remote_guild_card_number(0),
|
||||||
suppress_newserv_commands(true),
|
suppress_newserv_commands(true),
|
||||||
enable_chat_filter(true),
|
enable_chat_filter(true),
|
||||||
enable_switch_assist(false),
|
enable_switch_assist(false),
|
||||||
@@ -594,6 +579,7 @@ static void check_implemented_subcommand(uint64_t id, const string& data) {
|
|||||||
|
|
||||||
void ProxyServer::LinkedSession::on_client_input() {
|
void ProxyServer::LinkedSession::on_client_input() {
|
||||||
string name = string_printf("ProxySession:%08" PRIX64 ":client", this->id);
|
string name = string_printf("ProxySession:%08" PRIX64 ":client", this->id);
|
||||||
|
try {
|
||||||
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(),
|
||||||
@@ -620,6 +606,44 @@ void ProxyServer::LinkedSession::on_client_input() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 0x40: {
|
||||||
|
if (this->license) {
|
||||||
|
auto& cmd = check_size_t<C_GuildCardSearch_40>(data);
|
||||||
|
if (cmd.searcher_guild_card_number == this->license->serial_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.searcher_guild_card_number = this->remote_guild_card_number;
|
||||||
|
}
|
||||||
|
if (cmd.target_guild_card_number == this->license->serial_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.target_guild_card_number = this->remote_guild_card_number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x81: {
|
||||||
|
if (this->license) {
|
||||||
|
// TODO: Handle this on PC as well (the format isn't in
|
||||||
|
// CommandFormats yet)
|
||||||
|
if (this->version == GameVersion::GC) {
|
||||||
|
auto& cmd = check_size_t<SC_SimpleMail_GC_81>(data);
|
||||||
|
if (cmd.from_guild_card_number == this->license->serial_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.from_guild_card_number = this->remote_guild_card_number;
|
||||||
|
}
|
||||||
|
if (cmd.to_guild_card_number == this->license->serial_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.to_guild_card_number = this->remote_guild_card_number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 0x60:
|
case 0x60:
|
||||||
case 0x62:
|
case 0x62:
|
||||||
case 0x6C:
|
case 0x6C:
|
||||||
@@ -627,7 +651,16 @@ void ProxyServer::LinkedSession::on_client_input() {
|
|||||||
case 0xC9:
|
case 0xC9:
|
||||||
case 0xCB:
|
case 0xCB:
|
||||||
check_implemented_subcommand(this->id, data);
|
check_implemented_subcommand(this->id, data);
|
||||||
if (!data.empty() && (data[0] == 0x05) && (data[data.size() - 1] == 0x01) && this->enable_switch_assist) {
|
|
||||||
|
if (this->license && !data.empty() && (data[0] == 0x06)) {
|
||||||
|
auto& cmd = check_size_t<S_SendGuildCard_GC>(data);
|
||||||
|
if (cmd.guild_card_number == this->license->serial_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.guild_card_number = this->remote_guild_card_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (!data.empty() && (data[0] == 0x05) && (data[data.size() - 1] == 0x01) && this->enable_switch_assist) {
|
||||||
if (!this->last_switch_enabled_subcommand.empty()) {
|
if (!this->last_switch_enabled_subcommand.empty()) {
|
||||||
log(WARNING, "[ProxyServer/%08" PRIX64 "] Switch assist: replaying previous enable subcommand",
|
log(WARNING, "[ProxyServer/%08" PRIX64 "] Switch assist: replaying previous enable subcommand",
|
||||||
this->id);
|
this->id);
|
||||||
@@ -730,6 +763,11 @@ void ProxyServer::LinkedSession::on_client_input() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (const exception& e) {
|
||||||
|
log(ERROR, "[ProxyServer/%08" PRIX64 "] Failed to process command from client: %s",
|
||||||
|
this->id, e.what());
|
||||||
|
this->disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxyServer::LinkedSession::on_server_input() {
|
void ProxyServer::LinkedSession::on_server_input() {
|
||||||
@@ -758,26 +796,24 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
|
|
||||||
// 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_PC_GC_02_17, after_message)) {
|
const auto& cmd = check_size_t<S_ServerInit_DC_PC_GC_02_17>(data,
|
||||||
throw std::runtime_error("init encryption command is too small");
|
offsetof(S_ServerInit_DC_PC_GC_02_17, after_message),
|
||||||
}
|
sizeof(S_ServerInit_DC_PC_GC_02_17));
|
||||||
const auto* cmd = reinterpret_cast<const S_ServerInit_DC_PC_GC_02_17*>(
|
|
||||||
data.data());
|
|
||||||
|
|
||||||
if (!this->license) {
|
if (!this->license) {
|
||||||
log(INFO, "[ProxyServer/%08" PRIX64 "] No license in linked session",
|
log(INFO, "[ProxyServer/%08" PRIX64 "] No license in linked session",
|
||||||
this->id);
|
this->id);
|
||||||
|
|
||||||
if ((this->version == GameVersion::PC) || (this->version == GameVersion::PATCH)) {
|
if ((this->version == GameVersion::PC) || (this->version == GameVersion::PATCH)) {
|
||||||
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));
|
this->client_input_crypt.reset(new PSOPCEncryption(cmd.client_key));
|
||||||
new_client_output_crypt.reset(new PSOPCEncryption(cmd->server_key));
|
new_client_output_crypt.reset(new PSOPCEncryption(cmd.server_key));
|
||||||
} else if (this->version == GameVersion::GC) {
|
} else if (this->version == GameVersion::GC) {
|
||||||
this->server_input_crypt.reset(new PSOGCEncryption(cmd->server_key));
|
this->server_input_crypt.reset(new PSOGCEncryption(cmd.server_key));
|
||||||
this->server_output_crypt.reset(new PSOGCEncryption(cmd->client_key));
|
this->server_output_crypt.reset(new PSOGCEncryption(cmd.client_key));
|
||||||
this->client_input_crypt.reset(new PSOGCEncryption(cmd->client_key));
|
this->client_input_crypt.reset(new PSOGCEncryption(cmd.client_key));
|
||||||
new_client_output_crypt.reset(new PSOGCEncryption(cmd->server_key));
|
new_client_output_crypt.reset(new PSOGCEncryption(cmd.server_key));
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("unsupported version");
|
throw invalid_argument("unsupported version");
|
||||||
}
|
}
|
||||||
@@ -792,11 +828,11 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
if (this->version == GameVersion::PATCH) {
|
if (this->version == GameVersion::PATCH) {
|
||||||
throw logic_error("patch session is indirect");
|
throw logic_error("patch session is indirect");
|
||||||
} else if (this->version == GameVersion::PC) {
|
} else 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));
|
||||||
} else if (this->version == GameVersion::GC) {
|
} else if (this->version == GameVersion::GC) {
|
||||||
this->server_input_crypt.reset(new PSOGCEncryption(cmd->server_key));
|
this->server_input_crypt.reset(new PSOGCEncryption(cmd.server_key));
|
||||||
this->server_output_crypt.reset(new PSOGCEncryption(cmd->client_key));
|
this->server_output_crypt.reset(new PSOGCEncryption(cmd.client_key));
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("unsupported version");
|
throw invalid_argument("unsupported version");
|
||||||
}
|
}
|
||||||
@@ -808,12 +844,12 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
// in an unlinked session).
|
// in an unlinked session).
|
||||||
if (this->version == GameVersion::PC) {
|
if (this->version == GameVersion::PC) {
|
||||||
C_Login_PC_9D cmd;
|
C_Login_PC_9D cmd;
|
||||||
if (this->guild_card_number == 0) {
|
if (this->remote_guild_card_number == 0) {
|
||||||
cmd.player_tag = 0xFFFF0000;
|
cmd.player_tag = 0xFFFF0000;
|
||||||
cmd.guild_card_number = 0xFFFFFFFF;
|
cmd.guild_card_number = 0xFFFFFFFF;
|
||||||
} else {
|
} else {
|
||||||
cmd.player_tag = 0x00010000;
|
cmd.player_tag = 0x00010000;
|
||||||
cmd.guild_card_number = this->guild_card_number;
|
cmd.guild_card_number = this->remote_guild_card_number;
|
||||||
}
|
}
|
||||||
cmd.unused = 0xFFFFFFFFFFFF0000;
|
cmd.unused = 0xFFFFFFFFFFFF0000;
|
||||||
cmd.sub_version = this->sub_version;
|
cmd.sub_version = this->sub_version;
|
||||||
@@ -869,12 +905,12 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
}
|
}
|
||||||
should_forward = false;
|
should_forward = false;
|
||||||
C_LoginWithUnusedSpace_GC_9E cmd;
|
C_LoginWithUnusedSpace_GC_9E cmd;
|
||||||
if (this->guild_card_number == 0) {
|
if (this->remote_guild_card_number == 0) {
|
||||||
cmd.player_tag = 0xFFFF0000;
|
cmd.player_tag = 0xFFFF0000;
|
||||||
cmd.guild_card_number = 0xFFFFFFFF;
|
cmd.guild_card_number = 0xFFFFFFFF;
|
||||||
} else {
|
} else {
|
||||||
cmd.player_tag = 0x00010000;
|
cmd.player_tag = 0x00010000;
|
||||||
cmd.guild_card_number = this->guild_card_number;
|
cmd.guild_card_number = this->remote_guild_card_number;
|
||||||
}
|
}
|
||||||
cmd.sub_version = this->sub_version;
|
cmd.sub_version = this->sub_version;
|
||||||
cmd.unused2.data()[1] = 1;
|
cmd.unused2.data()[1] = 1;
|
||||||
@@ -895,7 +931,7 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
0x9E,
|
0x9E,
|
||||||
0x01,
|
0x01,
|
||||||
&cmd,
|
&cmd,
|
||||||
sizeof(C_LoginWithUnusedSpace_GC_9E) - (this->guild_card_number ? sizeof(cmd.unused_space) : 0),
|
sizeof(C_LoginWithUnusedSpace_GC_9E) - (this->remote_guild_card_number ? sizeof(cmd.unused_space) : 0),
|
||||||
name.c_str());
|
name.c_str());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -903,17 +939,25 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
case 0x04: {
|
case 0x04: {
|
||||||
// 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)) {
|
auto& cmd = check_size_t<S_UpdateClientConfig_DC_PC_GC_04>(data,
|
||||||
throw std::runtime_error("set security data command is too small");
|
offsetof(S_UpdateClientConfig_DC_PC_GC_04, cfg),
|
||||||
|
sizeof(S_UpdateClientConfig_DC_PC_GC_04));
|
||||||
|
|
||||||
|
// If this is a licensed session, hide the guild card number
|
||||||
|
// assigned by the remote server so the client doesn't see it
|
||||||
|
// change. If this is an unlicensed session, then the client never
|
||||||
|
// received a guild card number from newserv anyway, so it's safe to
|
||||||
|
// let the client see the number from the remote server.
|
||||||
|
bool had_guild_card_number = (this->remote_guild_card_number != 0);
|
||||||
|
this->remote_guild_card_number = cmd.guild_card_number;
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Remote guild card number set to %" PRIX32,
|
||||||
|
this->id, this->remote_guild_card_number);
|
||||||
|
if (this->license) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.guild_card_number = this->license->serial_number;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool had_guild_card_number = (this->guild_card_number != 0);
|
|
||||||
|
|
||||||
const auto* cmd = reinterpret_cast<const S_UpdateClientConfig_DC_PC_GC_04*>(data.data());
|
|
||||||
this->guild_card_number = cmd->guild_card_number;
|
|
||||||
log(INFO, "[ProxyServer/%08" PRIX64 "] Guild card number set to %" PRIX32,
|
|
||||||
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
|
||||||
// sends a short 04 command, part of the previous command ends up in
|
// sends a short 04 command, part of the previous command ends up in
|
||||||
@@ -926,7 +970,7 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
? "t Lobby Server. Copyright SEGA E"
|
? "t Lobby Server. Copyright SEGA E"
|
||||||
: "t Port Map. Copyright SEGA Enter",
|
: "t Port Map. Copyright SEGA Enter",
|
||||||
this->remote_client_config_data.bytes());
|
this->remote_client_config_data.bytes());
|
||||||
memcpy(this->remote_client_config_data.data(), &cmd->cfg,
|
memcpy(this->remote_client_config_data.data(), &cmd.cfg,
|
||||||
min<size_t>(data.size() - sizeof(S_UpdateClientConfig_DC_PC_GC_04),
|
min<size_t>(data.size() - sizeof(S_UpdateClientConfig_DC_PC_GC_04),
|
||||||
this->remote_client_config_data.bytes()));
|
this->remote_client_config_data.bytes()));
|
||||||
|
|
||||||
@@ -946,22 +990,131 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 0x06: {
|
||||||
|
if (this->license) {
|
||||||
|
auto& cmd = check_size_t<SC_TextHeader_01_06_11_B0>(data,
|
||||||
|
sizeof(SC_TextHeader_01_06_11_B0),
|
||||||
|
0xFFFF);
|
||||||
|
if (cmd.guild_card_number == this->remote_guild_card_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.guild_card_number = this->license->serial_number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x41: {
|
||||||
|
if (this->license) {
|
||||||
|
if (this->version == GameVersion::PC) {
|
||||||
|
auto& cmd = check_size_t<S_GuildCardSearchResult_PC_41>(data);
|
||||||
|
if (cmd.searcher_guild_card_number == this->remote_guild_card_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.searcher_guild_card_number = this->license->serial_number;
|
||||||
|
}
|
||||||
|
if (cmd.result_guild_card_number == this->remote_guild_card_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.result_guild_card_number = this->license->serial_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (this->version == GameVersion::GC) {
|
||||||
|
auto& cmd = check_size_t<S_GuildCardSearchResult_DC_GC_41>(data);
|
||||||
|
if (cmd.searcher_guild_card_number == this->remote_guild_card_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.searcher_guild_card_number = this->license->serial_number;
|
||||||
|
}
|
||||||
|
if (cmd.result_guild_card_number == this->remote_guild_card_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.result_guild_card_number = this->license->serial_number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x81: {
|
||||||
|
if (this->license) {
|
||||||
|
// TODO: Handle this on PC as well (the format isn't in
|
||||||
|
// CommandFormats yet)
|
||||||
|
if (this->version == GameVersion::GC) {
|
||||||
|
auto& cmd = check_size_t<SC_SimpleMail_GC_81>(data);
|
||||||
|
if (cmd.from_guild_card_number == this->remote_guild_card_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.from_guild_card_number = this->license->serial_number;
|
||||||
|
}
|
||||||
|
if (cmd.to_guild_card_number == this->remote_guild_card_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.to_guild_card_number = this->license->serial_number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x88: {
|
||||||
|
if (this->license) {
|
||||||
|
size_t expected_size = sizeof(S_ArrowUpdateEntry_88) * flag;
|
||||||
|
auto* entries = &check_size_t<S_ArrowUpdateEntry_88>(data,
|
||||||
|
expected_size, expected_size);
|
||||||
|
for (size_t x = 0; x < flag; x++) {
|
||||||
|
if (entries[x].guild_card_number == this->remote_guild_card_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
entries[x].guild_card_number = this->license->serial_number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xC4: {
|
||||||
|
if (this->license) {
|
||||||
|
// TODO: Implement this for PC too
|
||||||
|
if (this->version == GameVersion::GC) {
|
||||||
|
size_t expected_size = sizeof(S_ChoiceSearchResultEntry_GC_C4) * flag;
|
||||||
|
auto* entries = &check_size_t<S_ChoiceSearchResultEntry_GC_C4>(data,
|
||||||
|
expected_size, expected_size);
|
||||||
|
for (size_t x = 0; x < flag; x++) {
|
||||||
|
if (entries[x].guild_card_number == this->remote_guild_card_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
entries[x].guild_card_number = this->license->serial_number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xE4: {
|
||||||
|
if (this->license && this->version == GameVersion::GC) {
|
||||||
|
auto& cmd = check_size_t<S_CardLobbyGame_GC_E4>(data);
|
||||||
|
for (size_t x = 0; x < 4; x++) {
|
||||||
|
if (cmd.entries[x].guild_card_number == this->remote_guild_card_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.entries[x].guild_card_number = this->license->serial_number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 0x19: {
|
case 0x19: {
|
||||||
if (this->version == GameVersion::PATCH) {
|
if (this->version == GameVersion::PATCH) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.size() < sizeof(S_Reconnect_19)) {
|
auto& cmd = check_size_t<S_Reconnect_19>(data);
|
||||||
throw std::runtime_error("reconnect command is too small");
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* args = reinterpret_cast<S_Reconnect_19*>(data.data());
|
|
||||||
memset(&this->next_destination, 0, sizeof(this->next_destination));
|
memset(&this->next_destination, 0, sizeof(this->next_destination));
|
||||||
struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(
|
struct sockaddr_in* sin = reinterpret_cast<struct sockaddr_in*>(
|
||||||
&this->next_destination);
|
&this->next_destination);
|
||||||
sin->sin_family = AF_INET;
|
sin->sin_family = AF_INET;
|
||||||
sin->sin_addr.s_addr = args->address.load_raw();
|
sin->sin_addr.s_addr = cmd.address.load_raw();
|
||||||
sin->sin_port = htons(args->port);
|
sin->sin_port = htons(cmd.port);
|
||||||
|
|
||||||
if (!this->client_bev.get()) {
|
if (!this->client_bev.get()) {
|
||||||
log(WARNING, "[ProxyServer/%08" PRIX64 "] Received reconnect command with no destination present",
|
log(WARNING, "[ProxyServer/%08" PRIX64 "] Received reconnect command with no destination present",
|
||||||
@@ -983,11 +1136,11 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
|
|
||||||
struct sockaddr_in* sockname_sin = reinterpret_cast<struct sockaddr_in*>(
|
struct sockaddr_in* sockname_sin = reinterpret_cast<struct sockaddr_in*>(
|
||||||
&sockname_ss);
|
&sockname_ss);
|
||||||
args->address.store_raw(sockname_sin->sin_addr.s_addr);
|
cmd.address.store_raw(sockname_sin->sin_addr.s_addr);
|
||||||
args->port = ntohs(sockname_sin->sin_port);
|
cmd.port = ntohs(sockname_sin->sin_port);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
args->port = this->local_port;
|
cmd.port = this->local_port;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1028,17 +1181,11 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto& cmd = check_size_t<S_OpenFile_PC_GC_44_A6>(data);
|
||||||
bool is_download_quest = (command == 0xA6);
|
bool is_download_quest = (command == 0xA6);
|
||||||
|
|
||||||
if (data.size() < sizeof(S_OpenFile_PC_GC_44_A6)) {
|
|
||||||
log(WARNING, "[ProxyServer/%08" PRIX64 "] Open file command is too small; skipping file",
|
|
||||||
this->id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const auto* cmd = reinterpret_cast<const S_OpenFile_PC_GC_44_A6*>(data.data());
|
|
||||||
|
|
||||||
string output_filename = string_printf("%s.%s.%" PRIu64,
|
string output_filename = string_printf("%s.%s.%" PRIu64,
|
||||||
cmd->filename.c_str(),
|
cmd.filename.c_str(),
|
||||||
is_download_quest ? "download" : "online", now());
|
is_download_quest ? "download" : "online", now());
|
||||||
for (size_t x = 0; x < output_filename.size(); x++) {
|
for (size_t x = 0; x < output_filename.size(); x++) {
|
||||||
if (output_filename[x] < 0x20 || output_filename[x] > 0x7E || output_filename[x] == '/') {
|
if (output_filename[x] < 0x20 || output_filename[x] > 0x7E || output_filename[x] == '/') {
|
||||||
@@ -1049,8 +1196,8 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
output_filename[0] = '_';
|
output_filename[0] = '_';
|
||||||
}
|
}
|
||||||
|
|
||||||
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" PRIX64 "] Opened file %s",
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Opened file %s",
|
||||||
this->id, output_filename.c_str());
|
this->id, output_filename.c_str());
|
||||||
break;
|
break;
|
||||||
@@ -1065,23 +1212,18 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.size() < sizeof(S_WriteFile_13_A7)) {
|
const auto& cmd = check_size_t<S_WriteFile_13_A7>(data);
|
||||||
log(WARNING, "[ProxyServer/%08" PRIX64 "] Write file command is too small",
|
|
||||||
this->id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const auto* cmd = reinterpret_cast<const S_WriteFile_13_A7*>(data.data());
|
|
||||||
|
|
||||||
SavingFile* sf = nullptr;
|
SavingFile* sf = nullptr;
|
||||||
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" PRIX64 "] Received data for non-open file %s",
|
log(WARNING, "[ProxyServer/%08" PRIX64 "] Received data for non-open file %s",
|
||||||
this->id, 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" PRIX64 "] Chunk data size is invalid; truncating to 0x400",
|
log(WARNING, "[ProxyServer/%08" PRIX64 "] Chunk data size is invalid; truncating to 0x400",
|
||||||
this->id);
|
this->id);
|
||||||
@@ -1091,7 +1233,7 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
log(INFO, "[ProxyServer/%08" PRIX64 "] Writing %zu bytes to %s",
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Writing %zu bytes to %s",
|
||||||
this->id, 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" PRIX64 "] 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->id);
|
this->id);
|
||||||
@@ -1103,7 +1245,7 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
if (sf->remaining_bytes == 0) {
|
if (sf->remaining_bytes == 0) {
|
||||||
log(INFO, "[ProxyServer/%08" PRIX64 "] File %s is complete",
|
log(INFO, "[ProxyServer/%08" PRIX64 "] File %s is complete",
|
||||||
this->id, 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;
|
||||||
}
|
}
|
||||||
@@ -1144,7 +1286,7 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
|
|
||||||
this->lobby_players.clear();
|
this->lobby_players.clear();
|
||||||
this->lobby_players.resize(12);
|
this->lobby_players.resize(12);
|
||||||
log(WARNING, "[ProxyServer/%08" PRIX64 "] Cleared lobby players",
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Cleared lobby players",
|
||||||
this->id);
|
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
|
||||||
@@ -1160,27 +1302,25 @@ 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) {
|
if (this->version == GameVersion::PC) {
|
||||||
break;
|
size_t expected_size = offsetof(S_JoinLobby_PC_65_67_68, entries) + sizeof(S_JoinLobby_GC_65_67_68::Entry) * flag;
|
||||||
}
|
auto& cmd = check_size_t<S_JoinLobby_PC_65_67_68>(data,
|
||||||
|
expected_size, expected_size);
|
||||||
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) {
|
|
||||||
log(WARNING, "[ProxyServer/%08" PRIX64 "] Lobby join command is incorrect size (expected 0x%zX bytes, received 0x%zX bytes)",
|
|
||||||
this->id, expected_size, data.size());
|
|
||||||
} else {
|
|
||||||
auto* cmd = reinterpret_cast<S_JoinLobby_GC_65_67_68*>(data.data());
|
|
||||||
|
|
||||||
this->lobby_client_id = cmd->client_id;
|
|
||||||
|
|
||||||
|
this->lobby_client_id = cmd.client_id;
|
||||||
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" PRIX64 "] Ignoring invalid player index %zu at position %zu",
|
log(WARNING, "[ProxyServer/%08" PRIX64 "] Ignoring invalid player index %zu at position %zu",
|
||||||
this->id, index, x);
|
this->id, index, x);
|
||||||
} else {
|
} else {
|
||||||
this->lobby_players[index].guild_card_number = cmd->entries[x].lobby_data.guild_card;
|
if (this->license && (cmd.entries[x].lobby_data.guild_card == this->remote_guild_card_number)) {
|
||||||
this->lobby_players[index].name = cmd->entries[x].disp.name;
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.entries[x].lobby_data.guild_card = this->license->serial_number;
|
||||||
|
}
|
||||||
|
this->lobby_players[index].guild_card_number = cmd.entries[x].lobby_data.guild_card;
|
||||||
|
this->lobby_players[index].name = cmd.entries[x].disp.name;
|
||||||
log(INFO, "[ProxyServer/%08" PRIX64 "] Added lobby player: (%zu) %" PRIu32 " %s",
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Added lobby player: (%zu) %" PRIu32 " %s",
|
||||||
this->id, index,
|
this->id, index,
|
||||||
this->lobby_players[index].guild_card_number,
|
this->lobby_players[index].guild_card_number,
|
||||||
@@ -1189,40 +1329,96 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this->override_lobby_event >= 0) {
|
if (this->override_lobby_event >= 0) {
|
||||||
cmd->event = this->override_lobby_event;
|
cmd.event = this->override_lobby_event;
|
||||||
}
|
}
|
||||||
if (this->override_lobby_number >= 0) {
|
if (this->override_lobby_number >= 0) {
|
||||||
cmd->lobby_number = this->override_lobby_number;
|
cmd.lobby_number = this->override_lobby_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (this->version == GameVersion::GC) {
|
||||||
|
size_t expected_size = offsetof(S_JoinLobby_GC_65_67_68, entries) + sizeof(S_JoinLobby_GC_65_67_68::Entry) * flag;
|
||||||
|
auto& cmd = check_size_t<S_JoinLobby_GC_65_67_68>(data,
|
||||||
|
expected_size, expected_size);
|
||||||
|
|
||||||
|
this->lobby_client_id = cmd.client_id;
|
||||||
|
for (size_t x = 0; x < flag; x++) {
|
||||||
|
size_t index = cmd.entries[x].lobby_data.client_id;
|
||||||
|
if (index >= this->lobby_players.size()) {
|
||||||
|
log(WARNING, "[ProxyServer/%08" PRIX64 "] Ignoring invalid player index %zu at position %zu",
|
||||||
|
this->id, index, x);
|
||||||
|
} else {
|
||||||
|
if (this->license && (cmd.entries[x].lobby_data.guild_card == this->remote_guild_card_number)) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.entries[x].lobby_data.guild_card = this->license->serial_number;
|
||||||
|
}
|
||||||
|
this->lobby_players[index].guild_card_number = cmd.entries[x].lobby_data.guild_card;
|
||||||
|
this->lobby_players[index].name = cmd.entries[x].disp.name;
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Added lobby player: (%zu) %" PRIu32 " %s",
|
||||||
|
this->id, index,
|
||||||
|
this->lobby_players[index].guild_card_number,
|
||||||
|
this->lobby_players[index].name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->override_lobby_event >= 0) {
|
||||||
|
cmd.event = this->override_lobby_event;
|
||||||
|
}
|
||||||
|
if (this->override_lobby_number >= 0) {
|
||||||
|
cmd.lobby_number = this->override_lobby_number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 for this command
|
||||||
this->lobby_players.resize(4);
|
this->lobby_players.resize(4);
|
||||||
log(WARNING, "[ProxyServer/%08" PRIX64 "] Cleared lobby players",
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Cleared lobby players",
|
||||||
this->id);
|
this->id);
|
||||||
|
|
||||||
const size_t expected_size = offsetof(S_JoinGame_GC_64, players_ep3);
|
if (this->version == GameVersion::PC) {
|
||||||
const size_t ep3_expected_size = sizeof(S_JoinGame_GC_64);
|
auto& cmd = check_size_t<S_JoinGame_PC_64>(data);
|
||||||
if (data.size() < expected_size) {
|
|
||||||
log(WARNING, "[ProxyServer/%08" PRIX64 "] Game join command is incorrect size (expected 0x%zX bytes, received 0x%zX bytes)",
|
|
||||||
this->id, expected_size, data.size());
|
|
||||||
} else {
|
|
||||||
auto* cmd = reinterpret_cast<S_JoinGame_GC_64*>(data.data());
|
|
||||||
|
|
||||||
this->lobby_client_id = cmd->client_id;
|
|
||||||
|
|
||||||
|
this->lobby_client_id = cmd.client_id;
|
||||||
for (size_t x = 0; x < flag; x++) {
|
for (size_t x = 0; x < flag; x++) {
|
||||||
this->lobby_players[x].guild_card_number = cmd->lobby_data[x].guild_card;
|
if (cmd.lobby_data[x].guild_card == this->remote_guild_card_number) {
|
||||||
if (data.size() >= ep3_expected_size) {
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
this->lobby_players[x].name = cmd->players_ep3[x].disp.name;
|
this->id);
|
||||||
|
cmd.lobby_data[x].guild_card = this->license->serial_number;
|
||||||
|
}
|
||||||
|
this->lobby_players[x].guild_card_number = cmd.lobby_data[x].guild_card;
|
||||||
|
this->lobby_players[x].name.clear();
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Added lobby player: (%zu) %" PRIu32,
|
||||||
|
this->id, x,
|
||||||
|
this->lobby_players[x].guild_card_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->override_section_id >= 0) {
|
||||||
|
cmd.section_id = this->override_section_id;
|
||||||
|
}
|
||||||
|
if (this->override_lobby_event >= 0) {
|
||||||
|
cmd.event = this->override_lobby_event;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (this->version == GameVersion::GC) {
|
||||||
|
const size_t expected_size = this->sub_version >= 0x40
|
||||||
|
? sizeof(S_JoinGame_GC_64)
|
||||||
|
: offsetof(S_JoinGame_GC_64, players_ep3);
|
||||||
|
auto& cmd = check_size_t<S_JoinGame_GC_64>(data,
|
||||||
|
expected_size, expected_size);
|
||||||
|
|
||||||
|
this->lobby_client_id = cmd.client_id;
|
||||||
|
for (size_t x = 0; x < flag; x++) {
|
||||||
|
if (cmd.lobby_data[x].guild_card == this->remote_guild_card_number) {
|
||||||
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Overriding remote guild card number",
|
||||||
|
this->id);
|
||||||
|
cmd.lobby_data[x].guild_card = this->license->serial_number;
|
||||||
|
}
|
||||||
|
this->lobby_players[x].guild_card_number = cmd.lobby_data[x].guild_card;
|
||||||
|
if (data.size() == sizeof(S_JoinGame_GC_64)) {
|
||||||
|
this->lobby_players[x].name = cmd.players_ep3[x].disp.name;
|
||||||
} else {
|
} else {
|
||||||
this->lobby_players[x].name.clear();
|
this->lobby_players[x].name.clear();
|
||||||
}
|
}
|
||||||
@@ -1233,10 +1429,10 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this->override_section_id >= 0) {
|
if (this->override_section_id >= 0) {
|
||||||
cmd->section_id = this->override_section_id;
|
cmd.section_id = this->override_section_id;
|
||||||
}
|
}
|
||||||
if (this->override_lobby_event >= 0) {
|
if (this->override_lobby_event >= 0) {
|
||||||
cmd->event = this->override_lobby_event;
|
cmd.event = this->override_lobby_event;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1248,12 +1444,8 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.size() < sizeof(S_LeaveLobby_66_69)) {
|
const auto& cmd = check_size_t<S_LeaveLobby_66_69>(data);
|
||||||
log(WARNING, "[ProxyServer/%08" PRIX64 "] Lobby leave command is incorrect size",
|
size_t index = cmd.client_id;
|
||||||
this->id);
|
|
||||||
} else {
|
|
||||||
const auto* cmd = reinterpret_cast<const S_LeaveLobby_66_69*>(data.data());
|
|
||||||
size_t index = cmd->client_id;
|
|
||||||
if (index >= this->lobby_players.size()) {
|
if (index >= this->lobby_players.size()) {
|
||||||
log(WARNING, "[ProxyServer/%08" PRIX64 "] Lobby leave command references missing position",
|
log(WARNING, "[ProxyServer/%08" PRIX64 "] Lobby leave command references missing position",
|
||||||
this->id);
|
this->id);
|
||||||
@@ -1263,7 +1455,6 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
log(INFO, "[ProxyServer/%08" PRIX64 "] Removed lobby player (%zu)",
|
log(INFO, "[ProxyServer/%08" PRIX64 "] Removed lobby player (%zu)",
|
||||||
this->id, index);
|
this->id, index);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1287,7 +1478,7 @@ void ProxyServer::LinkedSession::on_server_input() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
log(ERROR, "[ProxyServer/%08" PRIX64 "] Failed to process server command: %s",
|
log(ERROR, "[ProxyServer/%08" PRIX64 "] Failed to process command from server: %s",
|
||||||
this->id, e.what());
|
this->id, e.what());
|
||||||
this->disconnect();
|
this->disconnect();
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ public:
|
|||||||
uint32_t sub_version;
|
uint32_t sub_version;
|
||||||
std::string character_name;
|
std::string character_name;
|
||||||
|
|
||||||
uint32_t guild_card_number;
|
uint32_t remote_guild_card_number;
|
||||||
parray<uint8_t, 0x20> remote_client_config_data;
|
parray<uint8_t, 0x20> remote_client_config_data;
|
||||||
ClientConfig newserv_client_config;
|
ClientConfig newserv_client_config;
|
||||||
bool suppress_newserv_commands;
|
bool suppress_newserv_commands;
|
||||||
|
|||||||
@@ -24,26 +24,6 @@ using namespace std;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
const T& check_size_t(
|
|
||||||
const string& data,
|
|
||||||
size_t min_size = sizeof(T),
|
|
||||||
size_t max_size = sizeof(T)) {
|
|
||||||
if (data.size() < min_size) {
|
|
||||||
throw runtime_error(string_printf(
|
|
||||||
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
|
|
||||||
min_size, data.size()));
|
|
||||||
}
|
|
||||||
if (data.size() > max_size) {
|
|
||||||
throw runtime_error(string_printf(
|
|
||||||
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
|
|
||||||
max_size, data.size()));
|
|
||||||
}
|
|
||||||
return *reinterpret_cast<const T*>(data.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
enum ClientStateBB {
|
enum ClientStateBB {
|
||||||
// initial connection. server will redirect client to another port.
|
// initial connection. server will redirect client to another port.
|
||||||
INITIAL_LOGIN = 0x00,
|
INITIAL_LOGIN = 0x00,
|
||||||
|
|||||||
Reference in New Issue
Block a user