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
+393
View File
@@ -0,0 +1,393 @@
#include "Channel.hh"
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <phosg/Network.hh>
#include <phosg/Time.hh>
#include "Version.hh"
using namespace std;
extern bool use_terminal_colors;
static void flush_and_free_bufferevent(struct bufferevent* bev) {
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
Channel::Channel(
GameVersion version,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const string& name,
TerminalFormat terminal_send_color,
TerminalFormat terminal_recv_color)
: bev(nullptr, flush_and_free_bufferevent),
version(version),
name(name),
terminal_send_color(terminal_send_color),
terminal_recv_color(terminal_recv_color),
on_command_received(on_command_received),
on_error(on_error),
context_obj(context_obj) {
}
Channel::Channel(
struct bufferevent* bev,
GameVersion version,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const string& name,
TerminalFormat terminal_send_color,
TerminalFormat terminal_recv_color)
: bev(nullptr, flush_and_free_bufferevent),
version(version),
name(name),
terminal_send_color(terminal_send_color),
terminal_recv_color(terminal_recv_color),
on_command_received(on_command_received),
on_error(on_error),
context_obj(context_obj) {
this->set_bufferevent(bev);
}
void Channel::replace_with(
Channel&& other,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name) {
this->set_bufferevent(other.bev.release());
this->local_addr = other.local_addr;
this->remote_addr = other.remote_addr;
this->is_virtual_connection = other.is_virtual_connection;
this->version = other.version;
this->crypt_in = other.crypt_in;
this->crypt_out = other.crypt_out;
this->name = name;
this->terminal_send_color = other.terminal_send_color;
this->terminal_recv_color = other.terminal_recv_color;
this->on_command_received = on_command_received;
this->on_error = on_error;
this->context_obj = context_obj;
other.disconnect(); // Clears crypts, addrs, etc.
}
void Channel::set_bufferevent(struct bufferevent* bev) {
this->bev.reset(bev);
if (this->bev.get()) {
int fd = bufferevent_getfd(this->bev.get());
if (fd < 0) {
this->is_virtual_connection = true;
memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
} else {
this->is_virtual_connection = false;
get_socket_addresses(fd, &this->local_addr, &this->remote_addr);
}
bufferevent_setcb(this->bev.get(),
&Channel::dispatch_on_input, nullptr,
&Channel::dispatch_on_error, this);
bufferevent_enable(this->bev.get(), EV_READ | EV_WRITE);
} else {
this->is_virtual_connection = false;
memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
}
}
void Channel::disconnect() {
if (this->bev.get()) {
// If the output buffer is not empty, move the bufferevent into the draining
// pool instead of disconnecting it, to make sure all the data gets sent.
struct evbuffer* out_buffer = bufferevent_get_output(this->bev.get());
if (evbuffer_get_length(out_buffer) == 0) {
this->bev.reset(); // Destructor flushes and frees the bufferevent
} else {
// The callbacks will free it when all the data is sent or the client
// disconnects
auto on_output = +[](struct bufferevent* bev, void*) -> void {
flush_and_free_bufferevent(bev);
};
auto on_error = +[](struct bufferevent* bev, short events, void*) -> void {
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
log(WARNING, "Disconnecting channel caused error %d (%s)", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
};
struct bufferevent* bev = this->bev.release();
bufferevent_setcb(bev, nullptr, on_output, on_error, bev);
bufferevent_disable(bev, EV_READ);
}
}
memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
this->is_virtual_connection = false;
this->crypt_in.reset();
this->crypt_out.reset();
}
Channel::Message Channel::recv(bool print_contents) {
struct evbuffer* buf = bufferevent_get_input(this->bev.get());
size_t header_size = (this->version == GameVersion::BB) ? 8 : 4;
PSOCommandHeader header;
if (evbuffer_copyout(buf, &header, header_size)
< static_cast<ssize_t>(header_size)) {
throw out_of_range("no command available");
}
if (this->crypt_in.get()) {
this->crypt_in->decrypt(&header, header_size, false);
}
size_t command_logical_size = header.size(version);
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this
// is not reflected in the size field. This logic does not occur if encryption
// is not yet enabled.
size_t command_physical_size = (this->crypt_in.get() && (version == GameVersion::BB))
? ((command_logical_size + 7) & ~7) : command_logical_size;
if (evbuffer_get_length(buf) < command_physical_size) {
throw out_of_range("no command available");
}
// If we get here, then there is a full command in the buffer. Some encryption
// algorithms' advancement depends on the decrypted data, so we have to
// actually decrypt the header again (with advance=true) to keep them in a
// consistent state.
string header_data(header_size, '\0');
if (evbuffer_remove(buf, header_data.data(), header_data.size())
< static_cast<ssize_t>(header_data.size())) {
throw logic_error("enough bytes available, but could not remove them");
}
if (this->crypt_in.get()) {
this->crypt_in->decrypt(header_data.data(), header_data.size());
}
string command_data(command_physical_size - header_size, '\0');
if (evbuffer_remove(buf, command_data.data(), command_data.size())
< static_cast<ssize_t>(command_data.size())) {
throw logic_error("enough bytes available, but could not remove them");
}
if (this->crypt_in.get()) {
this->crypt_in->decrypt(command_data.data(), command_data.size());
}
command_data.resize(command_logical_size - header_size);
if (print_contents && (this->terminal_recv_color != TerminalFormat::END)) {
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, this->terminal_recv_color, TerminalFormat::BOLD, TerminalFormat::END);
}
string name_token;
if (!this->name.empty()) {
name_token = " from " + this->name;
}
log(INFO, "Received%s (version=%s command=%04hX flag=%08X)",
name_token.c_str(),
name_for_version(this->version),
header.command(this->version),
header.flag(this->version));
vector<struct iovec> iovs;
iovs.emplace_back(iovec{.iov_base = header_data.data(), .iov_len = header_data.size()});
iovs.emplace_back(iovec{.iov_base = command_data.data(), .iov_len = command_data.size()});
print_data(stderr, iovs, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR);
if (use_terminal_colors && this->terminal_recv_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
}
}
return {
.command = header.command(this->version),
.flag = header.flag(this->version),
.data = move(command_data),
};
}
void Channel::send(uint16_t cmd, uint32_t flag, const void* data, size_t size,
bool print_contents) {
if (!this->connected()) {
log(WARNING, "Attempted to send command on closed channel; dropping data");
}
string send_data;
size_t logical_size;
size_t send_data_size = 0;
switch (this->version) {
case GameVersion::GC:
case GameVersion::DC: {
PSOCommandHeaderDCGC header;
if (this->crypt_out.get()) {
send_data_size = (sizeof(header) + size + 3) & ~3;
} else {
send_data_size = (sizeof(header) + size);
}
logical_size = send_data_size;
header.command = cmd;
header.flag = flag;
header.size = send_data_size;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break;
}
case GameVersion::PC:
case GameVersion::PATCH: {
PSOCommandHeaderPC header;
if (this->crypt_out.get()) {
send_data_size = (sizeof(header) + size + 3) & ~3;
} else {
send_data_size = (sizeof(header) + size);
}
logical_size = send_data_size;
header.size = send_data_size;
header.command = cmd;
header.flag = flag;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break;
}
case GameVersion::BB: {
// BB has an annoying behavior here: command lengths must be multiples of
// 4, but the actual data length must be a multiple of 8. If the size
// field is not divisible by 8, 4 extra bytes are sent anyway. This
// behavior only applies when encryption is enabled - any commands sent
// before encryption is enabled have no size restrictions (except they
// must include a full header and must fit in the client's receive
// buffer), and no implicit extra bytes are sent.
PSOCommandHeaderBB header;
if (this->crypt_out.get()) {
send_data_size = (sizeof(header) + size + 7) & ~7;
} else {
send_data_size = (sizeof(header) + size);
}
logical_size = (sizeof(header) + size + 3) & ~3;
header.size = logical_size;
header.command = cmd;
header.flag = flag;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
break;
}
default:
throw logic_error("unimplemented game version in send_command");
}
// All versions of PSO I've seen (PC, GC, BB) have a receive buffer 0x7C00
// bytes in size
if (send_data_size > 0x7C00) {
throw runtime_error("outbound command too large");
}
if (send_data.size() < send_data_size) {
send_data.append(reinterpret_cast<const char*>(data), size);
send_data.resize(send_data_size, '\0');
}
if (print_contents && (this->terminal_send_color != TerminalFormat::END)) {
string name_token;
if (!this->name.empty()) {
name_token = " to " + this->name;
}
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::FG_YELLOW, TerminalFormat::BOLD, TerminalFormat::END);
}
log(INFO, "Sending%s (version=%s command=%04hX flag=%08X)",
name_token.c_str(), name_for_version(version), cmd, flag);
print_data(stderr, send_data.data(), logical_size, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR);
if (use_terminal_colors && this->terminal_send_color != TerminalFormat::NORMAL) {
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
}
}
if (this->crypt_out.get()) {
this->crypt_out->encrypt(send_data.data(), send_data.size());
}
struct evbuffer* buf = bufferevent_get_output(this->bev.get());
evbuffer_add(buf, send_data.data(), send_data.size());
}
void Channel::send(uint16_t cmd, uint32_t flag, const string& data, bool print_contents) {
this->send(cmd, flag, data.data(), data.size(), print_contents);
}
void Channel::send(const void* data, size_t size, bool print_contents) {
size_t header_size = (this->version == GameVersion::BB) ? 8 : 4;
const auto* header = reinterpret_cast<const PSOCommandHeader*>(data);
this->send(
header->command(this->version),
header->flag(this->version),
reinterpret_cast<const uint8_t*>(data) + header_size,
size - header_size,
print_contents);
}
void Channel::send(const string& data, bool print_contents) {
return this->send(data.data(), data.size(), print_contents);
}
void Channel::dispatch_on_input(struct bufferevent*, void* ctx) {
Channel* ch = reinterpret_cast<Channel*>(ctx);
// The client can be disconnected during on_command_received, so we have to
// make sure ch->bev is valid every time before calling recv()
while (ch->bev.get()) {
Message msg;
try {
msg = ch->recv();
} catch (const out_of_range&) {
break;
} catch (const exception& e) {
log(WARNING, "Error receiving on channel: %s", e.what());
ch->on_error(*ch, BEV_EVENT_ERROR);
break;
}
if (ch->on_command_received) {
ch->on_command_received(*ch, msg.command, msg.flag, msg.data);
}
}
}
void Channel::dispatch_on_error(struct bufferevent*, short events, void* ctx) {
Channel* ch = reinterpret_cast<Channel*>(ctx);
if (ch->on_error) {
ch->on_error(*ch, events);
} else {
ch->disconnect();
}
}
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include <netinet/in.h>
#include <memory>
#include <string>
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "Version.hh"
struct Channel {
std::unique_ptr<struct bufferevent, void (*)(struct bufferevent*)> bev;
struct sockaddr_storage local_addr;
struct sockaddr_storage remote_addr;
bool is_virtual_connection;
GameVersion version;
std::shared_ptr<PSOEncryption> crypt_in;
std::shared_ptr<PSOEncryption> crypt_out;
std::string name;
TerminalFormat terminal_send_color;
TerminalFormat terminal_recv_color;
struct Message {
uint16_t command;
uint32_t flag;
std::string data;
};
typedef void (*on_command_received_t)(Channel&, uint16_t, uint32_t, std::string&);
typedef void (*on_error_t)(Channel&, short);
on_command_received_t on_command_received;
on_error_t on_error;
void* context_obj;
Channel(
GameVersion version,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name = "",
TerminalFormat terminal_send_color = TerminalFormat::END,
TerminalFormat terminal_recv_color = TerminalFormat::END);
Channel(
struct bufferevent* bev,
GameVersion version,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name = "",
TerminalFormat terminal_send_color = TerminalFormat::END,
TerminalFormat terminal_recv_color = TerminalFormat::END);
Channel(const Channel& other) = delete;
Channel(Channel&& other) = delete;
Channel& operator=(const Channel& other) = delete;
Channel& operator=(Channel&& other) = delete;
void replace_with(
Channel&& other,
on_command_received_t on_command_received,
on_error_t on_error,
void* context_obj,
const std::string& name = "");
void set_bufferevent(struct bufferevent* bev);
inline bool connected() const {
return this->bev.get() != nullptr;
}
void disconnect();
// Receives a message. Throws std::out_of_range if no messages are available.
Message recv(bool print_contents = true);
// Sends a message with an automatically-constructed header.
void send(uint16_t cmd, uint32_t flag = 0, const void* data = nullptr, size_t size = 0, bool print_contents = true);
void send(uint16_t cmd, uint32_t flag, const std::string& data, bool print_contents = true);
// Sends a message with a pre-existing header (as the first few bytes in the
// data)
void send(const void* data = nullptr, size_t size = 0, bool print_contents = true);
void send(const std::string& data, bool print_contents = true);
private:
static void dispatch_on_input(struct bufferevent*, void* ctx);
static void dispatch_on_error(struct bufferevent*, short events, void* ctx);
};
+229 -78
View File
@@ -9,6 +9,7 @@
#include <phosg/Time.hh>
#include "Server.hh"
#include "ProxyServer.hh"
#include "Lobby.hh"
#include "Client.hh"
#include "SendCommands.hh"
@@ -91,7 +92,7 @@ static void check_is_leader(shared_ptr<Lobby> l, shared_ptr<Client> c) {
////////////////////////////////////////////////////////////////////////////////
// Message commands
static void command_lobby_info(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void server_command_lobby_info(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string&) {
// no preconditions - everyone can use this command
@@ -120,20 +121,61 @@ static void command_lobby_info(shared_ptr<ServerState>, shared_ptr<Lobby> l,
}
}
static void command_ax(shared_ptr<ServerState>, shared_ptr<Lobby>,
static void proxy_command_lobby_info(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, const std::u16string&) {
vector<const char*> cheats_tokens;
if (session.switch_assist) {
cheats_tokens.emplace_back("SWA");
}
if (session.infinite_hp) {
cheats_tokens.emplace_back("HP");
}
if (session.infinite_tp) {
cheats_tokens.emplace_back("TP");
}
string cheats_str = cheats_tokens.empty() ? "none" : join(cheats_tokens, ",");
vector<const char*> behaviors_tokens;
if (session.save_files) {
behaviors_tokens.emplace_back("SF");
}
if (session.function_call_return_value >= 0) {
behaviors_tokens.emplace_back("BFC");
}
string behaviors_str = behaviors_tokens.empty() ? "none" : join(behaviors_tokens, ",");
string section_id_override = "none";
if (session.override_section_id >= 0) {
section_id_override = name_for_section_id(session.override_section_id);
}
send_text_message_printf(session.client_channel,
"$C7GC: $C6%" PRIu32 "\n"
"$C7Client ID: $C6%zu\n"
"$C7Cheats: $C6%s\n"
"$C7Flags: $C6%s\n"
"$C7SecID override: $C6%s\n",
session.remote_guild_card_number,
session.lobby_client_id,
cheats_str.c_str(),
behaviors_str.c_str(),
section_id_override.c_str());
}
static void server_command_ax(shared_ptr<ServerState>, shared_ptr<Lobby>,
shared_ptr<Client> c, const std::u16string& args) {
check_privileges(c, Privilege::ANNOUNCE);
string message = encode_sjis(args);
log(INFO, "[Client message from %010u] %s\n", c->license->serial_number, message.c_str());
}
static void command_announce(shared_ptr<ServerState> s, shared_ptr<Lobby>,
static void server_command_announce(shared_ptr<ServerState> s, shared_ptr<Lobby>,
shared_ptr<Client> c, const std::u16string& args) {
check_privileges(c, Privilege::ANNOUNCE);
send_text_message(s, args);
}
static void command_arrow(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void server_command_arrow(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
// no preconditions
c->lobby_arrow_color = stoull(encode_sjis(args), nullptr, 0);
@@ -142,7 +184,12 @@ static void command_arrow(shared_ptr<ServerState>, shared_ptr<Lobby> l,
}
}
static void command_dbgid(shared_ptr<ServerState>, shared_ptr<Lobby>,
static void proxy_command_arrow(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, const std::u16string& args) {
session.server_channel.send(0x89, stoull(encode_sjis(args), nullptr, 0));
}
static void server_command_dbgid(shared_ptr<ServerState>, shared_ptr<Lobby>,
shared_ptr<Client> c, const std::u16string&) {
c->prefer_high_lobby_client_id = !c->prefer_high_lobby_client_id;
}
@@ -150,7 +197,7 @@ static void command_dbgid(shared_ptr<ServerState>, shared_ptr<Lobby>,
////////////////////////////////////////////////////////////////////////////////
// Lobby commands
static void command_cheat(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void server_command_cheat(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string&) {
check_is_game(l, true);
check_is_leader(l, c);
@@ -174,7 +221,7 @@ static void command_cheat(shared_ptr<ServerState>, shared_ptr<Lobby> l,
}
}
static void command_lobby_event(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void server_command_lobby_event(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_is_game(l, false);
check_privileges(c, Privilege::CHANGE_EVENT);
@@ -189,7 +236,24 @@ static void command_lobby_event(shared_ptr<ServerState>, shared_ptr<Lobby> l,
send_change_event(l, l->event);
}
static void command_lobby_event_all(shared_ptr<ServerState> s, shared_ptr<Lobby>,
static void proxy_command_lobby_event(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, const std::u16string& args) {
if (args.empty()) {
session.override_lobby_event = -1;
} else {
uint8_t new_event = event_for_name(args);
if (new_event == 0xFF) {
send_text_message(session.client_channel, u"$C6No such lobby event.");
} else {
session.override_lobby_event = new_event;
if (session.version == GameVersion::GC || session.version == GameVersion::BB) {
session.client_channel.send(0xDA, session.override_lobby_event);
}
}
}
}
static void server_command_lobby_event_all(shared_ptr<ServerState> s, shared_ptr<Lobby>,
shared_ptr<Client> c, const std::u16string& args) {
check_privileges(c, Privilege::CHANGE_EVENT);
@@ -209,7 +273,7 @@ static void command_lobby_event_all(shared_ptr<ServerState> s, shared_ptr<Lobby>
}
}
static void command_lobby_type(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void server_command_lobby_type(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_is_game(l, false);
check_privileges(c, Privilege::CHANGE_EVENT);
@@ -235,20 +299,41 @@ static void command_lobby_type(shared_ptr<ServerState>, shared_ptr<Lobby> l,
////////////////////////////////////////////////////////////////////////////////
// Game commands
static void command_secid(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void server_command_secid(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_is_game(l, false);
if (!args[0]) {
c->override_section_id = -1;
send_text_message(l, u"$C6Override section ID\nremoved");
send_text_message(c, u"$C6Override section ID\nremoved");
} else {
c->override_section_id = section_id_for_name(args);
send_text_message(l, u"$C6Override section ID\nset");
uint8_t new_secid = section_id_for_name(args);
if (new_secid == 0xFF) {
send_text_message(c, u"$C6Invalid section ID");
} else {
c->override_section_id = new_secid;
send_text_message(c, u"$C6Override section ID\nset");
}
}
}
static void command_password(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void proxy_command_secid(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, const std::u16string& args) {
if (!args[0]) {
session.override_section_id = -1;
send_text_message(session.client_channel, u"$C6Override section ID\nremoved");
} else {
uint8_t new_secid = section_id_for_name(args);
if (new_secid == 0xFF) {
send_text_message(session.client_channel, u"$C6Invalid section ID");
} else {
session.override_section_id = new_secid;
send_text_message(session.client_channel, u"$C6Override section ID\nset");
}
}
}
static void server_command_password(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_is_game(l, true);
check_is_leader(l, c);
@@ -265,7 +350,7 @@ static void command_password(shared_ptr<ServerState>, shared_ptr<Lobby> l,
}
}
static void command_min_level(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void server_command_min_level(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_is_game(l, true);
check_is_leader(l, c);
@@ -276,7 +361,7 @@ static void command_min_level(shared_ptr<ServerState>, shared_ptr<Lobby> l,
l->min_level + 1);
}
static void command_max_level(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void server_command_max_level(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_is_game(l, true);
check_is_leader(l, c);
@@ -296,7 +381,7 @@ static void command_max_level(shared_ptr<ServerState>, shared_ptr<Lobby> l,
////////////////////////////////////////////////////////////////////////////////
// Character commands
static void command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
static void server_command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_is_game(l, false);
check_version(c, GameVersion::BB);
@@ -383,14 +468,15 @@ static void command_edit(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
// TODO: implement this
// TODO: make sure the bank name is filesystem-safe
/* static void command_change_bank(shared_ptr<ServerState>, shared_ptr<Lobby>,
/* static void server_command_change_bank(shared_ptr<ServerState>, shared_ptr<Lobby>,
shared_ptr<Client> c, const std::u16string&) {
check_version(c, GameVersion::BB);
TODO
} */
static void command_convert_char_to_bb(shared_ptr<ServerState> s,
// TODO: This can be implemented on the proxy server too.
static void server_command_convert_char_to_bb(shared_ptr<ServerState> s,
shared_ptr<Lobby> l, shared_ptr<Client> c, const std::u16string& args) {
check_is_game(l, false);
check_not_version(c, GameVersion::BB);
@@ -438,7 +524,7 @@ static string name_for_client(shared_ptr<Client> c) {
return "Player";
}
static void command_silence(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
static void server_command_silence(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_privileges(c, Privilege::SILENCE_USER);
@@ -460,7 +546,7 @@ static void command_silence(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
target->can_chat ? "un" : "");
}
static void command_kick(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
static void server_command_kick(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_privileges(c, Privilege::KICK_USER);
@@ -482,7 +568,7 @@ static void command_kick(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
send_text_message_printf(l, "$C6%s kicked off", target_name.c_str());
}
static void command_ban(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
static void server_command_ban(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_privileges(c, Privilege::BAN_USER);
@@ -536,7 +622,7 @@ static void command_ban(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
////////////////////////////////////////////////////////////////////////////////
// Cheat commands
static void command_warp(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void server_command_warp(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_is_game(l, true);
check_cheats_enabled(l);
@@ -565,7 +651,13 @@ static void command_warp(shared_ptr<ServerState>, shared_ptr<Lobby> l,
send_warp(c, area);
}
static void command_next(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void proxy_command_warp(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, const std::u16string& args) {
uint32_t area = stoul(encode_sjis(args), nullptr, 0);
send_warp(session.client_channel, session.lobby_client_id, area);
}
static void server_command_next(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string&) {
check_is_game(l, true);
check_cheats_enabled(l);
@@ -584,7 +676,7 @@ static void command_next(shared_ptr<ServerState>, shared_ptr<Lobby> l,
send_warp(c, new_area);
}
static void command_what(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void server_command_what(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string&) {
check_is_game(l, true);
if (!l->episode || (l->episode > 3)) {
@@ -618,7 +710,7 @@ static void command_what(shared_ptr<ServerState>, shared_ptr<Lobby> l,
}
}
static void command_song(shared_ptr<ServerState>, shared_ptr<Lobby>,
static void server_command_song(shared_ptr<ServerState>, shared_ptr<Lobby>,
shared_ptr<Client> c, const std::u16string& args) {
check_is_ep3(c, true);
@@ -626,7 +718,7 @@ static void command_song(shared_ptr<ServerState>, shared_ptr<Lobby>,
send_ep3_change_music(c, song);
}
static void command_infinite_hp(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void server_command_infinite_hp(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string&) {
check_is_game(l, true);
check_cheats_enabled(l);
@@ -635,7 +727,14 @@ static void command_infinite_hp(shared_ptr<ServerState>, shared_ptr<Lobby> l,
send_text_message_printf(c, "$C6Infinite HP %s", c->infinite_hp ? "enabled" : "disabled");
}
static void command_infinite_tp(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void proxy_command_infinite_hp(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, const std::u16string&) {
session.infinite_hp = !session.infinite_hp;
send_text_message_printf(session.client_channel, "$C6Infinite HP %s",
session.infinite_hp ? "enabled" : "disabled");
}
static void server_command_infinite_tp(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string&) {
check_is_game(l, true);
check_cheats_enabled(l);
@@ -644,7 +743,14 @@ static void command_infinite_tp(shared_ptr<ServerState>, shared_ptr<Lobby> l,
send_text_message_printf(c, "$C6Infinite TP %s", c->infinite_tp ? "enabled" : "disabled");
}
static void command_switch_assist(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void proxy_command_infinite_tp(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, const std::u16string&) {
session.infinite_tp = !session.infinite_tp;
send_text_message_printf(session.client_channel, "$C6Infinite TP %s",
session.infinite_tp ? "enabled" : "disabled");
}
static void server_command_switch_assist(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string&) {
check_is_game(l, true);
check_cheats_enabled(l);
@@ -653,7 +759,13 @@ static void command_switch_assist(shared_ptr<ServerState>, shared_ptr<Lobby> l,
send_text_message_printf(c, "$C6Switch assist %s", c->switch_assist ? "enabled" : "disabled");
}
static void command_item(shared_ptr<ServerState>, shared_ptr<Lobby> l,
static void proxy_command_switch_assist(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, const std::u16string&) {
session.switch_assist = !session.switch_assist;
send_text_message_printf(session.client_channel, "$C6Switch assist %s", session.switch_assist ? "enabled" : "disabled");
}
static void server_command_item(shared_ptr<ServerState>, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args) {
check_is_game(l, true);
check_cheats_enabled(l);
@@ -683,74 +795,113 @@ static void command_item(shared_ptr<ServerState>, shared_ptr<Lobby> l,
////////////////////////////////////////////////////////////////////////////////
typedef void (*handler_t)(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
typedef void (*server_handler_t)(shared_ptr<ServerState> s, shared_ptr<Lobby> l,
shared_ptr<Client> c, const std::u16string& args);
typedef void (*proxy_handler_t)(shared_ptr<ServerState>,
ProxyServer::LinkedSession& session, const std::u16string& args);
struct ChatCommandDefinition {
handler_t handler;
server_handler_t server_handler;
proxy_handler_t proxy_handler;
u16string usage;
};
static const unordered_map<u16string, ChatCommandDefinition> chat_commands({
// TODO: implement command_help and actually use the usage strings here
{u"$allevent" , {command_lobby_event_all , u"Usage:\nallevent <name/ID>"}},
{u"$ann" , {command_announce , u"Usage:\nann <message>"}},
{u"$arrow" , {command_arrow , u"Usage:\narrow <color>"}},
{u"$ax" , {command_ax , u"Usage:\nax <message>"}},
{u"$ban" , {command_ban , u"Usage:\nban <name-or-number>"}},
{u"$bbchar" , {command_convert_char_to_bb, u"Usage:\nbbchar <user> <pass> <1-4>"}},
// {u"$bank", {command_bank , u"Usage:\nbank <bank name>"}},
{u"$cheat" , {command_cheat , u"Usage:\ncheat"}},
{u"$dbgid" , {command_dbgid , u"Usage:\ndngid"}},
{u"$edit" , {command_edit , u"Usage:\nedit <stat> <value>"}},
{u"$event" , {command_lobby_event , u"Usage:\nevent <name>"}},
{u"$infhp" , {command_infinite_hp , u"Usage:\ninfhp"}},
{u"$inftp" , {command_infinite_tp , u"Usage:\ninftp"}},
{u"$item" , {command_item , u"Usage:\nitem <item-code>"}},
{u"$kick" , {command_kick , u"Usage:\nkick <name-or-number>"}},
{u"$li" , {command_lobby_info , u"Usage:\nli"}},
{u"$maxlevel" , {command_max_level , u"Usage:\nmax_level <level>"}},
{u"$minlevel" , {command_min_level , u"Usage:\nmin_level <level>"}},
{u"$next" , {command_next , u"Usage:\nnext"}},
{u"$password" , {command_password , u"Usage:\nlock [password]\nomit password to\nunlock game"}},
{u"$secid" , {command_secid , u"Usage:\nsecid [section ID]\nomit section ID to\nrevert to normal"}},
{u"$silence" , {command_silence , u"Usage:\nsilence <name-or-number>"}},
{u"$song" , {command_song , u"Usage:\nsong <song-number>"}},
{u"$swa" , {command_switch_assist , u"Usage:\nswa"}},
{u"$type" , {command_lobby_type , u"Usage:\ntype <name>"}},
{u"$warp" , {command_warp , u"Usage:\nwarp <area-number>"}},
{u"$what" , {command_what , u"Usage:\nwhat"}},
{u"$allevent" , {server_command_lobby_event_all , nullptr , u"Usage:\nallevent <name/ID>"}},
{u"$ann" , {server_command_announce , nullptr , u"Usage:\nann <message>"}},
{u"$arrow" , {server_command_arrow , proxy_command_arrow , u"Usage:\narrow <color>"}},
{u"$ax" , {server_command_ax , nullptr , u"Usage:\nax <message>"}},
{u"$ban" , {server_command_ban , nullptr , u"Usage:\nban <name-or-number>"}},
// TODO: implement this on proxy server
{u"$bbchar" , {server_command_convert_char_to_bb, nullptr , u"Usage:\nbbchar <user> <pass> <1-4>"}},
{u"$cheat" , {server_command_cheat , nullptr , u"Usage:\ncheat"}},
{u"$dbgid" , {server_command_dbgid , nullptr , u"Usage:\ndbgid"}},
{u"$edit" , {server_command_edit , nullptr , u"Usage:\nedit <stat> <value>"}},
{u"$event" , {server_command_lobby_event , proxy_command_lobby_event , u"Usage:\nevent <name>"}},
{u"$infhp" , {server_command_infinite_hp , proxy_command_infinite_hp , u"Usage:\ninfhp"}},
{u"$inftp" , {server_command_infinite_tp , proxy_command_infinite_tp , u"Usage:\ninftp"}},
{u"$item" , {server_command_item , nullptr , u"Usage:\nitem <item-code>"}},
{u"$kick" , {server_command_kick , nullptr , u"Usage:\nkick <name-or-number>"}},
{u"$li" , {server_command_lobby_info , proxy_command_lobby_info , u"Usage:\nli"}},
{u"$maxlevel" , {server_command_max_level , nullptr , u"Usage:\nmax_level <level>"}},
{u"$minlevel" , {server_command_min_level , nullptr , u"Usage:\nmin_level <level>"}},
// TODO: implement this on proxy server
{u"$next" , {server_command_next , nullptr , u"Usage:\nnext"}},
{u"$password" , {server_command_password , nullptr , u"Usage:\nlock [password]\nomit password to\nunlock game"}},
{u"$secid" , {server_command_secid , proxy_command_secid , u"Usage:\nsecid [section ID]\nomit section ID to\nrevert to normal"}},
{u"$silence" , {server_command_silence , nullptr , u"Usage:\nsilence <name-or-number>"}},
// TODO: implement this on proxy server
{u"$song" , {server_command_song , nullptr , u"Usage:\nsong <song-number>"}},
{u"$swa" , {server_command_switch_assist , proxy_command_switch_assist, u"Usage:\nswa"}},
{u"$type" , {server_command_lobby_type , nullptr , u"Usage:\ntype <name>"}},
{u"$warp" , {server_command_warp , proxy_command_warp , u"Usage:\nwarp <area-number>"}},
{u"$what" , {server_command_what , nullptr , u"Usage:\nwhat"}},
});
struct SplitCommand {
u16string name;
u16string args;
SplitCommand(const u16string& text) {
size_t space_pos = text.find(u' ');
if (space_pos != string::npos) {
this->name = text.substr(0, space_pos);
this->args = text.substr(space_pos + 1);
} else {
this->name = text;
}
}
};
// This function is called every time any player sends a chat beginning with a
// dollar sign. It is this function's responsibility to see if the chat is a
// command, and to execute the command and block the chat if it is.
void process_chat_command(std::shared_ptr<ServerState> s, std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c, const std::u16string& text) {
u16string command_name;
u16string text_str(text);
size_t space_pos = text_str.find(u' ');
if (space_pos != string::npos) {
command_name = text_str.substr(0, space_pos);
text_str = text_str.substr(space_pos + 1);
} else {
command_name = text_str;
text_str.clear();
}
SplitCommand cmd(text);
const ChatCommandDefinition* def = nullptr;
try {
def = &chat_commands.at(command_name);
def = &chat_commands.at(cmd.name);
} catch (const out_of_range&) {
send_text_message(c, u"$C6Unknown command.");
send_text_message(c, u"$C6Unknown command");
return;
}
try {
def->handler(s, l, c, text_str.c_str());
} catch (const precondition_failed& e) {
send_text_message(c, e.what());
} catch (const exception& e) {
send_text_message_printf(c, "$C6Failed:\n%s", e.what());
if (!def->server_handler) {
send_text_message(c, u"$C6Command not available\non game server");
} else {
try {
def->server_handler(s, l, c, cmd.args);
} catch (const precondition_failed& e) {
send_text_message(c, e.what());
} catch (const exception& e) {
send_text_message_printf(c, "$C6Failed:\n%s", e.what());
}
}
}
void process_chat_command(std::shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, const std::u16string& text) {
SplitCommand cmd(text);
const ChatCommandDefinition* def = nullptr;
try {
def = &chat_commands.at(cmd.name);
} catch (const out_of_range&) {
send_text_message(session.client_channel, u"$C6Unknown command");
return;
}
if (!def->proxy_handler) {
send_text_message(session.client_channel, u"$C6Command not available\non proxy server");
} else {
try {
def->proxy_handler(s, session, cmd.args);
} catch (const precondition_failed& e) {
send_text_message(session.client_channel, e.what());
} catch (const exception& e) {
send_text_message_printf(session.client_channel, "$C6Failed:\n%s", e.what());
}
}
}
+3
View File
@@ -8,6 +8,9 @@
#include "ServerState.hh"
#include "Lobby.hh"
#include "Client.hh"
#include "ProxyServer.hh"
void process_chat_command(std::shared_ptr<ServerState> s, std::shared_ptr<Lobby> l,
std::shared_ptr<Client> c, const std::u16string& text);
void process_chat_command(std::shared_ptr<ServerState> s,
ProxyServer::LinkedSession& session, const std::u16string& text);
+2 -13
View File
@@ -24,16 +24,14 @@ Client::Client(
GameVersion version,
ServerBehavior server_behavior)
: version(version),
bb_game_state(0),
flags(flags_for_version(this->version, 0)),
bev(bev),
channel(bev, this->version, nullptr, nullptr, this, "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN),
server_behavior(server_behavior),
should_disconnect(false),
should_send_to_lobby_server(false),
proxy_destination_address(0),
proxy_destination_port(0),
play_time_begin(now()),
last_recv_time(this->play_time_begin),
last_send_time(0),
x(0.0f),
z(0.0f),
area(0),
@@ -50,15 +48,6 @@ Client::Client(
pending_bb_save_player_index(0),
dol_base_addr(0) {
this->last_switch_enabled_command.subcommand = 0;
int fd = bufferevent_getfd(this->bev);
if (fd < 0) {
this->is_virtual_connection = true;
memset(&this->local_addr, 0, sizeof(this->local_addr));
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
} else {
this->is_virtual_connection = false;
get_socket_addresses(fd, &this->local_addr, &this->remote_addr);
}
memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr));
}
+3 -15
View File
@@ -4,6 +4,7 @@
#include <memory>
#include "Channel.hh"
#include "CommandFormats.hh"
#include "FunctionCompiler.hh"
#include "License.hh"
@@ -66,27 +67,15 @@ struct Client {
uint8_t bb_game_state;
uint16_t flags;
// Encryption
std::shared_ptr<PSOEncryption> crypt_in;
std::shared_ptr<PSOEncryption> crypt_out;
// Network
struct sockaddr_storage local_addr;
struct sockaddr_storage remote_addr;
struct bufferevent* bev;
Channel channel;
struct sockaddr_storage next_connection_addr;
ServerBehavior server_behavior;
bool is_virtual_connection;
bool should_disconnect;
bool should_send_to_lobby_server;
uint32_t proxy_destination_address;
uint16_t proxy_destination_port;
// Timing & menus
uint64_t play_time_begin; // time of connection (used for incrementing play time on BB)
uint64_t last_recv_time; // time of last data received
uint64_t last_send_time; // time of last data sent
// Lobby/positioning
float x;
float z;
@@ -112,8 +101,7 @@ struct Client {
uint32_t dol_base_addr;
std::shared_ptr<DOLFileIndex::DOLFile> loading_dol_file;
Client(struct bufferevent* bev, GameVersion version,
ServerBehavior server_behavior);
Client(struct bufferevent* bev, GameVersion version, ServerBehavior server_behavior);
void set_license(std::shared_ptr<const License> l);
-93
View File
@@ -131,99 +131,6 @@ void PSOCommandHeader::set_flag(GameVersion version, uint32_t flag) {
void for_each_received_command(
struct bufferevent* bev,
GameVersion version,
PSOEncryption* crypt,
function<void(uint16_t, uint16_t, string&)> fn) {
struct evbuffer* buf = bufferevent_get_input(bev);
size_t header_size = (version == GameVersion::BB) ? 8 : 4;
for (;;) {
PSOCommandHeader header;
if (evbuffer_copyout(buf, &header, header_size)
< static_cast<ssize_t>(header_size)) {
break;
}
if (crypt) {
crypt->decrypt(&header, header_size, false);
}
size_t command_logical_size = header.size(version);
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this
// is not reflected in the size field. This logic does not occur if
// encryption is not yet enabled.
size_t command_physical_size = (crypt && (version == GameVersion::BB))
? ((command_logical_size + header_size - 1) & ~(header_size - 1))
: command_logical_size;
if (evbuffer_get_length(buf) < command_physical_size) {
break;
}
// If we get here, then there is a full command in the buffer. Some
// encryption algorithms' advancement depends on the decrypted data, so we
// have to actually decrypt the header again (with advance=true) to keep
// them in a consistent state.
string header_data(header_size, '\0');
if (evbuffer_remove(buf, header_data.data(), header_data.size())
< static_cast<ssize_t>(header_data.size())) {
throw logic_error("enough bytes available, but could not remove them");
}
string command_data(command_physical_size - header_size, '\0');
if (evbuffer_remove(buf, command_data.data(), command_data.size())
< static_cast<ssize_t>(command_data.size())) {
throw logic_error("enough bytes available, but could not remove them");
}
if (crypt) {
crypt->decrypt(header_data.data(), header_data.size());
crypt->decrypt(command_data.data(), command_data.size());
}
command_data.resize(command_logical_size - header_size);
fn(header.command(version), header.flag(version), command_data);
}
}
void print_received_command(
uint16_t command,
uint32_t flag,
const void* data,
size_t size,
GameVersion version,
const char* name,
TerminalFormat color) {
if (use_terminal_colors) {
print_color_escape(stderr, color, TerminalFormat::BOLD, TerminalFormat::END);
}
string name_token;
if (name && name[0]) {
name_token = string(" from ") + name;
}
log(INFO, "Received%s (version=%s command=%04hX flag=%08X)",
name_token.c_str(), name_for_version(version), command, flag);
PSOCommandHeader header;
size_t header_size = header.header_size(version);
header.set_command(version, command);
header.set_flag(version, flag);
header.set_size(version, size + header_size);
vector<struct iovec> iovs;
iovs.emplace_back(iovec{.iov_base = &header, .iov_len = header_size});
iovs.emplace_back(iovec{.iov_base = const_cast<void*>(data), .iov_len = size});
print_data(stderr, iovs, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR);
if (use_terminal_colors) {
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
}
}
void check_size_v(size_t size, size_t min_size, size_t max_size) {
if (size < min_size) {
throw std::runtime_error(string_printf(
-15
View File
@@ -52,21 +52,6 @@ union PSOSubcommand {
le_uint32_t dword;
} __attribute__((packed));
void for_each_received_command(
struct bufferevent* bev,
GameVersion version,
PSOEncryption* crypt,
std::function<void(uint16_t, uint16_t, std::string&)> fn);
void print_received_command(
uint16_t command,
uint32_t flag,
const void* data,
size_t size,
GameVersion version,
const char* name = nullptr,
TerminalFormat color = TerminalFormat::FG_GREEN);
// This function is used in a lot of places to check received command sizes and
// cast them to the appropriate type
template <typename T>
+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());
+176 -293
View File
@@ -29,6 +29,7 @@
#include "ProxyCommands.hh"
using namespace std;
using namespace std::placeholders;
@@ -37,13 +38,6 @@ static const uint32_t UNLICENSED_SESSION_TIMEOUT_USECS = 10 * 1000000; // 10 sec
static void flush_and_free_bufferevent(struct bufferevent* bev) {
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
ProxyServer::ProxyServer(
shared_ptr<struct event_base> base,
shared_ptr<ServerState> state)
@@ -134,7 +128,8 @@ void ProxyServer::connect_client(struct bufferevent* bev, uint16_t server_port)
} catch (const out_of_range&) {
this->log(INFO, "Virtual connection received on unregistered port %hu; closing it",
server_port);
flush_and_free_bufferevent(bev);
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
return;
}
@@ -166,7 +161,9 @@ void ProxyServer::on_client_connect(
}
auto session = emplace_ret.first->second;
session->log(INFO, "Opened linked session");
session->resume(bev);
Channel ch(bev, version, nullptr, nullptr, session.get(), "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN);
session->resume(move(ch));
// If no default destination exists, or the client is not a patch client,
// create an unlinked session - we'll have to get the destination from the
@@ -195,22 +192,15 @@ void ProxyServer::on_client_connect(
uint32_t client_key = random_object<uint32_t>();
auto cmd = prepare_server_init_contents_dc_pc_gc(
false, server_key, client_key);
send_command(
session->bev.get(),
session->version,
session->crypt_out.get(),
0x02,
0,
&cmd,
sizeof(cmd),
"unlinked proxy client");
bufferevent_flush(session->bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
session->channel.send(0x02, 0x00, &cmd, sizeof(cmd));
// TODO: Is this actually needed?
// bufferevent_flush(session->channel.bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
if (version == GameVersion::PC) {
session->crypt_out.reset(new PSOPCEncryption(server_key));
session->crypt_in.reset(new PSOPCEncryption(client_key));
session->channel.crypt_out.reset(new PSOPCEncryption(server_key));
session->channel.crypt_in.reset(new PSOPCEncryption(client_key));
} else {
session->crypt_out.reset(new PSOGCEncryption(server_key));
session->crypt_in.reset(new PSOGCEncryption(client_key));
session->channel.crypt_out.reset(new PSOGCEncryption(server_key));
session->channel.crypt_in.reset(new PSOGCEncryption(client_key));
}
break;
}
@@ -220,22 +210,14 @@ void ProxyServer::on_client_connect(
random_data(server_key.data(), server_key.bytes());
random_data(client_key.data(), client_key.bytes());
auto cmd = prepare_server_init_contents_bb(server_key, client_key);
send_command(
session->bev.get(),
session->version,
session->crypt_out.get(),
0x03,
0,
&cmd,
sizeof(cmd),
"unlinked proxy client");
bufferevent_flush(session->bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
session->channel.send(0x03, 0x00, &cmd, sizeof(cmd));
// TODO: Is this actually needed?
// bufferevent_flush(session->bev.get(), EV_READ | EV_WRITE, BEV_FLUSH);
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
session->detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption(
this->state->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
session->crypt_in = session->detector_crypt;
session->crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
session->channel.crypt_in = session->detector_crypt;
session->channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
session->detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true));
break;
}
@@ -251,27 +233,23 @@ ProxyServer::UnlinkedSession::UnlinkedSession(
ProxyServer* server, struct bufferevent* bev, uint16_t local_port, GameVersion version)
: server(server),
log(string_printf("[ProxyServer:UnlinkedSession:%p] ", bev)),
bev(bev, flush_and_free_bufferevent),
channel(
bev,
version,
ProxyServer::UnlinkedSession::on_input,
ProxyServer::UnlinkedSession::on_error,
this,
string_printf("UnlinkedSession:%p", bev),
TerminalFormat::FG_YELLOW,
TerminalFormat::FG_GREEN),
local_port(local_port),
version(version) {
memset(&this->next_destination, 0, sizeof(this->next_destination));
bufferevent_setcb(this->bev.get(),
&UnlinkedSession::dispatch_on_client_input, nullptr,
&UnlinkedSession::dispatch_on_client_error, this);
bufferevent_enable(this->bev.get(), EV_READ | EV_WRITE);
}
void ProxyServer::UnlinkedSession::dispatch_on_client_input(
struct bufferevent*, void* ctx) {
reinterpret_cast<UnlinkedSession*>(ctx)->on_client_input();
}
void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint32_t, std::string& data) {
auto* session = reinterpret_cast<UnlinkedSession*>(ch.context_obj);
void ProxyServer::UnlinkedSession::dispatch_on_client_error(
struct bufferevent*, short events, void* ctx) {
reinterpret_cast<UnlinkedSession*>(ctx)->on_client_error(events);
}
void ProxyServer::UnlinkedSession::on_client_input() {
bool should_close_unlinked_session = false;
shared_ptr<const License> license;
uint32_t sub_version = 0;
@@ -280,58 +258,54 @@ void ProxyServer::UnlinkedSession::on_client_input() {
string login_command_bb;
try {
for_each_received_command(this->bev.get(), this->version, this->crypt_in.get(),
[&](uint16_t command, uint32_t flag, const string& data) {
print_received_command(command, flag, data.data(), data.size(),
this->version, "unlinked proxy client");
if (session->version == GameVersion::PC) {
// We should only get a 9D while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x9D) {
throw runtime_error("command is not 9D");
}
const auto& cmd = check_size_t<C_Login_PC_9D>(
data, sizeof(C_Login_PC_9D), sizeof(C_LoginWithUnusedSpace_PC_9D));
license = session->server->state->license_manager->verify_pc(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
sub_version = cmd.sub_version;
character_name = cmd.name;
if (this->version == GameVersion::PC) {
// We should only get a 9D while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x9D) {
throw runtime_error("command is not 9D");
}
const auto& cmd = check_size_t<C_Login_PC_9D>(data, sizeof(C_Login_PC_9D), sizeof(C_LoginWithUnusedSpace_PC_9D));
license = this->server->state->license_manager->verify_pc(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
sub_version = cmd.sub_version;
character_name = cmd.name;
} else if (session->version == GameVersion::GC) {
// We should only get a 9E while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x9E) {
throw runtime_error("command is not 9E");
}
const auto& cmd = check_size_t<C_Login_GC_9E>(
data, sizeof(C_Login_GC_9E), sizeof(C_LoginWithUnusedSpace_GC_9E));
license = session->server->state->license_manager->verify_gc(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
sub_version = cmd.sub_version;
character_name = cmd.name;
client_config.cfg = cmd.client_config.cfg;
} else if (this->version == GameVersion::GC) {
// We should only get a 9E while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x9E) {
throw runtime_error("command is not 9E");
}
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_Login_GC_9E), sizeof(C_LoginWithUnusedSpace_GC_9E));
license = this->server->state->license_manager->verify_gc(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
sub_version = cmd.sub_version;
character_name = cmd.name;
client_config.cfg = cmd.client_config.cfg;
} else if (session->version == GameVersion::BB) {
// We should only get a 93 while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x93) {
throw runtime_error("command is not 93");
}
const auto& cmd = check_size_t<C_Login_BB_93>(data);
license = session->server->state->license_manager->verify_bb(
cmd.username, cmd.password);
login_command_bb = move(data);
} else if (this->version == GameVersion::BB) {
// We should only get a 93 while the session is unlinked; if we get
// anything else, disconnect
if (command != 0x93) {
throw runtime_error("command is not 93");
}
const auto& cmd = check_size_t<C_Login_BB_93>(data);
license = this->server->state->license_manager->verify_bb(
cmd.username, cmd.password);
login_command_bb = data;
} else {
throw logic_error("unsupported unlinked session version");
}
});
} else {
throw logic_error("unsupported unlinked session version");
}
} catch (const exception& e) {
this->log(ERROR, "Failed to process command from unlinked client: %s", e.what());
session->log(ERROR, "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 = ch.bev.get();
// If license is non-null, then the client has a password and can be connected
// to the remote lobby server.
@@ -341,75 +315,80 @@ void ProxyServer::UnlinkedSession::on_client_input() {
should_close_unlinked_session = true;
// Look up the linked session for this license (if any)
shared_ptr<LinkedSession> session;
shared_ptr<LinkedSession> linked_session;
try {
session = this->server->id_to_session.at(license->serial_number);
session->log(INFO, "Resuming linked session from unlinked session");
linked_session = session->server->id_to_session.at(license->serial_number);
linked_session->log(INFO, "Resuming linked session from unlinked session");
} catch (const out_of_range&) {
// If there's no open session for this license, then there must be a valid
// destination somewhere - either in the client config or in the unlinked
// session
if (client_config.cfg.magic == CLIENT_CONFIG_MAGIC) {
session.reset(new LinkedSession(
this->server,
this->local_port,
this->version,
linked_session.reset(new LinkedSession(
session->server,
session->local_port,
session->version,
license,
client_config));
session->log(INFO, "Opened licensed session for unlinked session based on client config");
} else if (this->next_destination.ss_family == AF_INET) {
session.reset(new LinkedSession(
this->server,
this->local_port,
this->version,
linked_session->log(INFO, "Opened licensed session for unlinked session based on client config");
} else if (session->next_destination.ss_family == AF_INET) {
linked_session.reset(new LinkedSession(
session->server,
session->local_port,
session->version,
license,
this->next_destination));
session->log(INFO, "Opened licensed session for unlinked session based on unlinked default destination");
session->next_destination));
linked_session->log(INFO, "Opened licensed session for unlinked session based on unlinked default destination");
} else {
this->log(ERROR, "Cannot open linked session: no valid destination in client config or unlinked session");
session->log(ERROR, "Cannot open linked session: no valid destination in client config or unlinked session");
}
}
if (session.get()) {
this->server->id_to_session.emplace(license->serial_number, session);
if (session->version != this->version) {
session->log(ERROR, "Linked session has different game version");
if (linked_session.get()) {
session->server->id_to_session.emplace(license->serial_number, linked_session);
if (linked_session->version != session->version) {
linked_session->log(ERROR, "Linked session has different game version");
} else {
// Resume the linked session using the unlinked session
try {
if (this->version == GameVersion::BB) {
session->resume(move(this->bev), this->crypt_in, this->crypt_out,
this->detector_crypt, move(login_command_bb));
if (session->version == GameVersion::BB) {
linked_session->resume(
move(session->channel),
session->detector_crypt,
move(login_command_bb));
} else {
session->resume(move(this->bev), this->crypt_in, this->crypt_out,
this->detector_crypt, sub_version, character_name);
linked_session->resume(
move(session->channel),
session->detector_crypt,
sub_version,
character_name);
}
this->crypt_in.reset();
this->crypt_out.reset();
} catch (const exception& e) {
session->log(ERROR, "Failed to resume linked session: %s", e.what());
linked_session->log(ERROR, "Failed to resume linked session: %s", e.what());
}
}
}
}
if (should_close_unlinked_session) {
this->log(INFO, "Closing session");
this->server->bev_to_unlinked_session.erase(session_key);
session->log(INFO, "Closing session");
session->server->bev_to_unlinked_session.erase(session_key);
// At this point, (*this) is destroyed! We must be careful not to touch it.
}
}
void ProxyServer::UnlinkedSession::on_client_error(short events) {
void ProxyServer::UnlinkedSession::on_error(Channel& ch, short events) {
auto* session = reinterpret_cast<UnlinkedSession*>(ch.context_obj);
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
this->log(WARNING, "Error %d (%s) in unlinked client stream", err,
session->log(WARNING, "Error %d (%s) in unlinked client stream", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
this->log(WARNING, "Unlinked client has disconnected");
this->server->bev_to_unlinked_session.erase(this->bev.get());
session->log(WARNING, "Unlinked client has disconnected");
session->server->bev_to_unlinked_session.erase(session->channel.bev.get());
}
}
@@ -422,23 +401,34 @@ ProxyServer::LinkedSession::LinkedSession(
GameVersion version)
: server(server),
id(id),
client_name(string_printf("LinkedSession:%08" PRIX64 ":client", this->id)),
server_name(string_printf("LinkedSession:%08" PRIX64 ":server", this->id)),
log(string_printf("[ProxyServer:LinkedSession:%08" PRIX64 "] ", this->id)),
timeout_event(event_new(this->server->base.get(), -1, EV_TIMEOUT,
&LinkedSession::dispatch_on_timeout, this), event_free),
license(nullptr),
client_bev(nullptr, flush_and_free_bufferevent),
server_bev(nullptr, flush_and_free_bufferevent),
client_channel(
version,
nullptr,
nullptr,
this,
string_printf("LinkedSession:%08" PRIX64 ":client", this->id),
TerminalFormat::FG_YELLOW,
TerminalFormat::FG_GREEN),
server_channel(
version,
nullptr,
nullptr,
this,
string_printf("LinkedSession:%08" PRIX64 ":server", this->id),
TerminalFormat::FG_YELLOW,
TerminalFormat::FG_RED),
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),
suppress_newserv_commands(true),
enable_chat_filter(true),
enable_switch_assist(false),
switch_assist(false),
infinite_hp(false),
infinite_tp(false),
save_files(false),
@@ -490,60 +480,48 @@ ProxyServer::LinkedSession::LinkedSession(
}
void ProxyServer::LinkedSession::resume(
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
shared_ptr<PSOEncryption> client_input_crypt,
shared_ptr<PSOEncryption> client_output_crypt,
Channel&& client_channel,
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
uint32_t sub_version,
const string& character_name) {
this->sub_version = sub_version;
this->character_name = character_name;
this->resume_inner(move(client_bev), client_input_crypt, client_output_crypt,
detector_crypt);
this->resume_inner(move(client_channel), detector_crypt);
}
void ProxyServer::LinkedSession::resume(
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
shared_ptr<PSOEncryption> client_input_crypt,
shared_ptr<PSOEncryption> client_output_crypt,
Channel&& client_channel,
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
string&& login_command_bb) {
this->login_command_bb = move(login_command_bb);
this->resume_inner(move(client_bev), client_input_crypt, client_output_crypt,
detector_crypt);
this->resume_inner(move(client_channel), detector_crypt);
}
void ProxyServer::LinkedSession::resume(struct bufferevent* client_bev) {
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> bev_unique(
client_bev, flush_and_free_bufferevent);
void ProxyServer::LinkedSession::resume(Channel&& client_channel) {
this->sub_version = 0;
this->character_name.clear();
this->resume_inner(move(bev_unique), nullptr, nullptr, nullptr);
this->resume_inner(move(client_channel), nullptr);
}
void ProxyServer::LinkedSession::resume_inner(
unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
shared_ptr<PSOEncryption> client_input_crypt,
shared_ptr<PSOEncryption> client_output_crypt,
Channel&& client_channel,
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt) {
if (this->client_bev.get()) {
if (this->client_channel.connected()) {
throw runtime_error("client connection is already open for this session");
}
if (this->next_destination.ss_family != AF_INET) {
throw logic_error("attempted to resume an unlicensed linked session without destination set");
}
this->client_bev = move(client_bev);
bufferevent_setcb(this->client_bev.get(),
&ProxyServer::LinkedSession::dispatch_on_client_input, nullptr,
&ProxyServer::LinkedSession::dispatch_on_client_error, this);
bufferevent_enable(this->client_bev.get(), EV_READ | EV_WRITE);
this->client_channel.replace_with(
move(client_channel),
ProxyServer::LinkedSession::on_input,
ProxyServer::LinkedSession::on_error,
this,
string_printf("LinkedSession:%08" PRIX64 ":client", this->id));
this->detector_crypt = detector_crypt;
this->client_input_crypt = client_input_crypt;
this->client_output_crypt = client_output_crypt;
this->server_input_crypt.reset();
this->server_output_crypt.reset();
this->server_channel.disconnect();
this->saving_files.clear();
this->connect();
@@ -552,9 +530,6 @@ void ProxyServer::LinkedSession::resume_inner(
void ProxyServer::LinkedSession::connect() {
// Connect to the remote server. The command handlers will do the login steps
// and set up forwarding
this->server_bev.reset(bufferevent_socket_new(this->server->base.get(), -1,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS));
struct sockaddr_storage local_ss;
struct sockaddr_in* local_sin = reinterpret_cast<struct sockaddr_in*>(&local_ss);
memset(local_sin, 0, sizeof(*local_sin));
@@ -568,14 +543,17 @@ void ProxyServer::LinkedSession::connect() {
string netloc_str = render_sockaddr_storage(local_ss);
this->log(INFO, "Connecting to %s", netloc_str.c_str());
if (bufferevent_socket_connect(this->server_bev.get(),
this->server_channel.set_bufferevent(bufferevent_socket_new(
this->server->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS));
if (bufferevent_socket_connect(this->server_channel.bev.get(),
reinterpret_cast<const sockaddr*>(local_sin), sizeof(*local_sin)) != 0) {
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
}
bufferevent_setcb(this->server_bev.get(),
&ProxyServer::LinkedSession::dispatch_on_server_input, nullptr,
&ProxyServer::LinkedSession::dispatch_on_server_error, this);
bufferevent_enable(this->server_bev.get(), EV_READ | EV_WRITE);
this->server_channel.on_command_received = ProxyServer::LinkedSession::on_input;
this->server_channel.on_error = ProxyServer::LinkedSession::on_error;
this->server_channel.context_obj = this;
// Cancel the session delete timeout
event_del(this->timeout_event.get());
@@ -594,26 +572,6 @@ ProxyServer::LinkedSession::SavingFile::SavingFile(
void ProxyServer::LinkedSession::dispatch_on_client_input(
struct bufferevent*, void* ctx) {
reinterpret_cast<LinkedSession*>(ctx)->on_client_input();
}
void ProxyServer::LinkedSession::dispatch_on_client_error(
struct bufferevent*, short events, void* ctx) {
reinterpret_cast<LinkedSession*>(ctx)->on_stream_error(events, false);
}
void ProxyServer::LinkedSession::dispatch_on_server_input(
struct bufferevent*, void* ctx) {
reinterpret_cast<LinkedSession*>(ctx)->on_server_input();
}
void ProxyServer::LinkedSession::dispatch_on_server_error(
struct bufferevent*, short events, void* ctx) {
reinterpret_cast<LinkedSession*>(ctx)->on_stream_error(events, true);
}
void ProxyServer::LinkedSession::dispatch_on_timeout(
evutil_socket_t, short, void* ctx) {
reinterpret_cast<LinkedSession*>(ctx)->on_timeout();
@@ -628,31 +586,27 @@ void ProxyServer::LinkedSession::on_timeout() {
void ProxyServer::LinkedSession::on_stream_error(
short events, bool is_server_stream) {
void ProxyServer::LinkedSession::on_error(Channel& ch, short events) {
auto* session = reinterpret_cast<LinkedSession*>(ch.context_obj);
bool is_server_stream = (&ch == &session->server_channel);
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
this->log(WARNING, "Error %d (%s) in %s stream",
session->log(WARNING, "Error %d (%s) in %s stream",
err, evutil_socket_error_to_string(err),
is_server_stream ? "server" : "client");
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
this->log(INFO, "%s has disconnected",
session->log(INFO, "%s has disconnected",
is_server_stream ? "Server" : "Client");
this->disconnect();
session->disconnect();
}
}
void ProxyServer::LinkedSession::disconnect() {
// Forward the disconnection to the other end
this->server_bev.reset();
this->client_bev.reset();
// Disable encryption for the next connection
this->server_input_crypt.reset();
this->server_output_crypt.reset();
this->client_input_crypt.reset();
this->client_output_crypt.reset();
this->client_channel.disconnect();
this->server_channel.disconnect();
// Set a timeout to delete the session entirely (in case the client doesn't
// reconnect)
@@ -662,105 +616,34 @@ void ProxyServer::LinkedSession::disconnect() {
}
bool ProxyServer::LinkedSession::is_connected() const {
return (this->server_bev.get() && this->client_bev.get());
return (this->server_channel.connected() && this->client_channel.connected());
}
void ProxyServer::LinkedSession::on_client_input() {
void ProxyServer::LinkedSession::on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
auto* session = reinterpret_cast<LinkedSession*>(ch.context_obj);
bool is_server_stream = (&ch == &session->server_channel);
try {
for_each_received_command(this->client_bev.get(), this->version, this->client_input_crypt.get(),
[&](uint16_t command, uint32_t flag, string& data) {
print_received_command(command, flag, data.data(), data.size(),
this->version, this->client_name.c_str());
process_proxy_command(
this->server->state,
*this,
false, // from_server
command,
flag,
data);
});
if (is_server_stream) {
size_t bytes_to_save = min<size_t>(data.size(), sizeof(session->prev_server_command_bytes));
memcpy(session->prev_server_command_bytes, data.data(), bytes_to_save);
}
process_proxy_command(
session->server->state,
*session,
is_server_stream,
command,
flag,
data);
} catch (const exception& e) {
this->log(ERROR, "Failed to process command from client: %s", e.what());
this->disconnect();
session->log(ERROR, "Failed to process command from %s: %s",
is_server_stream ? "server" : "client", e.what());
session->disconnect();
}
}
void ProxyServer::LinkedSession::on_server_input() {
try {
for_each_received_command(this->server_bev.get(), this->version, this->server_input_crypt.get(),
[&](uint16_t command, uint32_t flag, string& data) {
print_received_command(command, flag, data.data(), data.size(),
this->version, this->server_name.c_str(), TerminalFormat::FG_RED);
size_t bytes_to_save = min<size_t>(data.size(), sizeof(this->prev_server_command_bytes));
memcpy(this->prev_server_command_bytes, data.data(), bytes_to_save);
process_proxy_command(
this->server->state,
*this,
true, // from_server
command,
flag,
data);
});
} catch (const exception& e) {
this->log(ERROR, "Failed to process command from server: %s", e.what());
this->disconnect();
}
}
void ProxyServer::LinkedSession::send_to_end(
bool to_server,
uint16_t command,
uint32_t flag,
const void* data,
size_t size) {
if (size & 3) {
throw runtime_error("command size is not a multiple of 4");
}
string name = string_printf("LinkedSession:%08" PRIX64 ":synthetic:%s",
this->id, to_server ? "server" : "client");
auto* bev = to_server ? this->server_bev.get() : this->client_bev.get();
if (!bev) {
throw runtime_error("session endpoint is not connected");
}
send_command(
bev,
this->version,
to_server ? this->server_output_crypt.get() : this->client_output_crypt.get(),
command,
flag,
data,
size,
name.c_str());
}
void ProxyServer::LinkedSession::send_to_end(
bool to_server, uint16_t command, uint32_t flag, const string& data) {
this->send_to_end(to_server, command, flag, data.data(), data.size());
}
void ProxyServer::LinkedSession::send_to_end_with_header(
bool to_server, const void* data, size_t size) {
size_t header_size = PSOCommandHeader::header_size(this->version);
if (size < header_size) {
throw runtime_error("command is too small for header");
}
const auto* header = reinterpret_cast<const PSOCommandHeader*>(data);
this->send_to_end(
to_server,
header->command(this->version),
header->flag(this->version),
reinterpret_cast<const uint8_t*>(data) + header_size,
size - header_size);
}
void ProxyServer::LinkedSession::send_to_end_with_header(
bool to_server, const string& data) {
this->send_to_end_with_header(to_server, data.data(), data.size());
}
shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session() {
if (this->id_to_session.empty()) {
throw runtime_error("no sessions exist");
+12 -45
View File
@@ -35,16 +35,14 @@ public:
struct LinkedSession {
ProxyServer* server;
uint64_t id;
std::string client_name;
std::string server_name;
PrefixedLogger log;
std::unique_ptr<struct event, void(*)(struct event*)> timeout_event;
std::shared_ptr<const License> license;
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> client_bev;
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> server_bev;
Channel client_channel;
Channel server_channel;
uint16_t local_port;
struct sockaddr_storage next_destination;
@@ -60,9 +58,8 @@ public:
uint32_t remote_guild_card_number;
parray<uint8_t, 0x20> remote_client_config_data;
ClientConfigBB newserv_client_config;
bool suppress_newserv_commands;
bool enable_chat_filter;
bool enable_switch_assist;
bool switch_assist;
bool infinite_hp;
bool infinite_tp;
bool save_files;
@@ -80,10 +77,6 @@ public:
std::vector<LobbyPlayer> lobby_players;
size_t lobby_client_id;
std::shared_ptr<PSOEncryption> client_input_crypt;
std::shared_ptr<PSOEncryption> client_output_crypt;
std::shared_ptr<PSOEncryption> server_input_crypt;
std::shared_ptr<PSOEncryption> server_output_crypt;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
struct SavingFile {
@@ -125,46 +118,25 @@ public:
const struct sockaddr_storage& next_destination);
void resume(
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
std::shared_ptr<PSOEncryption> client_input_crypt,
std::shared_ptr<PSOEncryption> client_output_crypt,
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
uint32_t sub_version,
const std::string& character_name);
void resume(
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
std::shared_ptr<PSOEncryption> client_input_crypt,
std::shared_ptr<PSOEncryption> client_output_crypt,
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
std::string&& login_command_bb);
void resume(struct bufferevent* client_bev);
void resume(Channel&& client_channel);
void resume_inner(
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>&& client_bev,
std::shared_ptr<PSOEncryption> client_input_crypt,
std::shared_ptr<PSOEncryption> client_output_crypt,
Channel&& client_channel,
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt);
void connect();
static void dispatch_on_client_input(struct bufferevent* bev, void* ctx);
static void dispatch_on_client_error(struct bufferevent* bev, short events,
void* ctx);
static void dispatch_on_server_input(struct bufferevent* bev, void* ctx);
static void dispatch_on_server_error(struct bufferevent* bev, short events,
void* ctx);
static void dispatch_on_timeout(evutil_socket_t fd, short what, void* ctx);
void on_client_input();
void on_server_input();
void on_stream_error(short events, bool is_server_stream);
static void on_input(Channel& ch, uint16_t, uint32_t, std::string& msg);
static void on_error(Channel& ch, short events);
void on_timeout();
void send_to_end(bool to_server, uint16_t command, uint32_t flag,
const void* data = nullptr, size_t size = 0);
void send_to_end(bool to_server, uint16_t command, uint32_t flag,
const std::string& data);
void send_to_end_with_header(
bool to_server, const void* data, size_t size);
void send_to_end_with_header(bool to_server, const std::string& data);
void disconnect();
bool is_connected() const;
@@ -208,24 +180,19 @@ private:
ProxyServer* server;
PrefixedLogger log;
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> bev;
Channel channel;
uint16_t local_port;
GameVersion version;
struct sockaddr_storage next_destination;
std::shared_ptr<PSOEncryption> crypt_out;
std::shared_ptr<PSOEncryption> crypt_in;
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
UnlinkedSession(ProxyServer* server, struct bufferevent* bev, uint16_t port, GameVersion version);
void receive_and_process_commands();
static void dispatch_on_client_input(struct bufferevent* bev, void* ctx);
static void dispatch_on_client_error(struct bufferevent* bev, short events,
void* ctx);
void on_client_input();
void on_client_error(short events);
static void on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
static void on_error(Channel& ch, short events);
};
PrefixedLogger log;
+22 -21
View File
@@ -1334,6 +1334,11 @@ void process_player_data(shared_ptr<ServerState> s, shared_ptr<Client> c,
throw logic_error("player data command not implemented for version");
}
auto player = c->game_data.player(false);
if (player) {
c->channel.name = remove_language_marker(encode_sjis(player->disp.name));
}
if (command == 0x61 && !c->pending_bb_save_username.empty()) {
string prev_bb_username = c->game_data.bb_username;
size_t prev_bb_player_index = c->game_data.bb_player_index;
@@ -1390,37 +1395,35 @@ void process_game_command(shared_ptr<ServerState> s, shared_ptr<Client> c,
void process_chat_generic(shared_ptr<ServerState> s, shared_ptr<Client> c,
const u16string& text) { // 06
if (!c->can_chat) {
return;
}
u16string processed_text = remove_language_marker(text);
if (processed_text.empty()) {
return;
}
auto l = s->find_lobby(c->lobby_id);
if (!l) {
return;
}
if (processed_text[0] == L'$') {
auto l = s->find_lobby(c->lobby_id);
if (l) {
if (processed_text[1] == L'$') {
processed_text = processed_text.substr(1);
} else {
process_chat_command(s, l, c, processed_text);
}
} else {
if (!c->can_chat) {
return;
}
}
auto l = s->find_lobby(c->lobby_id);
if (!l) {
return;
}
if (!c->can_chat) {
return;
}
for (size_t x = 0; x < l->max_clients; x++) {
if (!l->clients[x]) {
continue;
}
send_chat_message(l->clients[x], c->license->serial_number,
c->game_data.player()->disp.name.data(), processed_text.c_str());
for (size_t x = 0; x < l->max_clients; x++) {
if (!l->clients[x]) {
continue;
}
send_chat_message(l->clients[x], c->license->serial_number,
c->game_data.player()->disp.name.data(), processed_text.c_str());
}
}
@@ -2451,8 +2454,6 @@ void process_command(shared_ptr<ServerState> s, shared_ptr<Client> c,
if (player) {
encoded_name = remove_language_marker(encode_sjis(player->disp.name));
}
print_received_command(command, flag, data.data(), data.size(), c->version,
encoded_name.c_str());
auto fn = handlers[static_cast<size_t>(c->version)][command & 0xFF];
if (fn) {
+60 -142
View File
@@ -25,119 +25,9 @@ extern FileContentsCache file_cache;
void send_command(
struct bufferevent* bev,
GameVersion version,
PSOEncryption* crypt,
uint16_t command,
uint32_t flag,
const void* data,
size_t size,
const char* name_str) {
string send_data;
size_t logical_size;
switch (version) {
case GameVersion::GC:
case GameVersion::DC: {
PSOCommandHeaderDCGC header;
header.command = command;
header.flag = flag;
header.size = (sizeof(header) + size + 3) & ~3;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
if (size) {
send_data.append(reinterpret_cast<const char*>(data), size);
send_data.resize(header.size, '\0');
}
logical_size = header.size;
break;
}
case GameVersion::PC:
case GameVersion::PATCH: {
PSOCommandHeaderPC header;
header.size = (sizeof(header) + size + 3) & ~3;
header.command = command;
header.flag = flag;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
if (size) {
send_data.append(reinterpret_cast<const char*>(data), size);
send_data.resize(header.size, '\0');
}
logical_size = header.size;
break;
}
case GameVersion::BB: {
// BB has an annoying behavior here: command lengths must be multiples of
// 4, but the actual data length must be a multiple of 8. If the size
// field is not divisible by 8, 4 extra bytes are sent anyway. This
// behavior only applies when encryption is enabled - any commands sent
// before encryption is enabled have no size restrictions (except they
// must include a full header and must fit in the client's receive
// buffer), and no implicit extra bytes are sent.
PSOCommandHeaderBB header;
header.size = (sizeof(header) + size + 3) & ~3;
header.command = command;
header.flag = flag;
send_data.append(reinterpret_cast<const char*>(&header), sizeof(header));
if (size) {
send_data.append(reinterpret_cast<const char*>(data), size);
if (crypt) {
send_data.resize((send_data.size() + 7) & ~7, '\0');
} else {
send_data.resize(header.size, '\0');
}
}
logical_size = header.size;
break;
}
default:
throw logic_error("unimplemented game version in send_command");
}
// Most client versions I've seen have a receive buffer 0x7C00 bytes in size
if (send_data.size() > 0x7C00) {
throw runtime_error("outbound command too large");
}
if (name_str) {
string name_token;
if (name_str[0]) {
name_token = string(" to ") + name_str;
}
if (use_terminal_colors) {
print_color_escape(stderr, TerminalFormat::FG_YELLOW, TerminalFormat::BOLD, TerminalFormat::END);
}
log(INFO, "Sending%s (version=%s command=%04hX flag=%08X)",
name_token.c_str(), name_for_version(version), command, flag);
print_data(stderr, send_data.data(), logical_size, 0, nullptr, PrintDataFlags::PRINT_ASCII | PrintDataFlags::DISABLE_COLOR);
if (use_terminal_colors) {
print_color_escape(stderr, TerminalFormat::NORMAL, TerminalFormat::END);
}
}
if (crypt) {
crypt->encrypt(send_data.data(), send_data.size());
}
struct evbuffer* buf = bufferevent_get_output(bev);
evbuffer_add(buf, send_data.data(), send_data.size());
}
void send_command(shared_ptr<Client> c, uint16_t command, uint32_t flag,
const void* data, size_t size) {
if (!c->bev) {
return;
}
string encoded_name;
auto player = c->game_data.player(false);
if (player) {
encoded_name = remove_language_marker(encode_sjis(player->disp.name));
}
send_command(c->bev, c->version, c->crypt_out.get(), command, flag, data,
size, encoded_name.c_str());
c->channel.send(command, flag, data, size);
}
void send_command_excluding_client(shared_ptr<Lobby> l, shared_ptr<Client> c,
@@ -236,12 +126,12 @@ void send_server_init_dc_pc_gc(shared_ptr<Client> c,
switch (c->version) {
case GameVersion::DC:
case GameVersion::PC:
c->crypt_out.reset(new PSOPCEncryption(server_key));
c->crypt_in.reset(new PSOPCEncryption(client_key));
c->channel.crypt_out.reset(new PSOPCEncryption(server_key));
c->channel.crypt_in.reset(new PSOPCEncryption(client_key));
break;
case GameVersion::GC:
c->crypt_out.reset(new PSOGCEncryption(server_key));
c->crypt_in.reset(new PSOGCEncryption(client_key));
c->channel.crypt_out.reset(new PSOGCEncryption(server_key));
c->channel.crypt_in.reset(new PSOGCEncryption(client_key));
break;
default:
throw invalid_argument("incorrect client version");
@@ -270,8 +160,8 @@ void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c) {
static const string expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt(new PSOBBMultiKeyDetectorEncryption(
s->bb_private_keys, expected_first_data, cmd.client_key.data(), sizeof(cmd.client_key)));
c->crypt_in = detector_crypt;
c->crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
c->channel.crypt_in = detector_crypt;
c->channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), true));
}
@@ -285,8 +175,8 @@ void send_server_init_patch(shared_ptr<Client> c) {
cmd.client_key = client_key;
send_command_t(c, 0x02, 0x00, cmd);
c->crypt_out.reset(new PSOPCEncryption(server_key));
c->crypt_in.reset(new PSOPCEncryption(client_key));
c->channel.crypt_out.reset(new PSOPCEncryption(server_key));
c->channel.crypt_in.reset(new PSOPCEncryption(client_key));
}
void send_server_init(shared_ptr<ServerState> s, shared_ptr<Client> c,
@@ -526,57 +416,70 @@ void send_enter_directory_patch(shared_ptr<Client> c, const string& dir) {
////////////////////////////////////////////////////////////////////////////////
// message functions
void send_text(shared_ptr<Client> c, StringWriter& w, uint16_t command,
const u16string& text) {
if ((c->version == GameVersion::DC) || (c->version == GameVersion::GC)) {
void send_text(Channel& ch, StringWriter& w, uint16_t command,
const u16string& text, bool should_add_color) {
if ((ch.version == GameVersion::DC) || (ch.version == GameVersion::GC)) {
string data = encode_sjis(text);
add_color(w, data.c_str(), data.size());
if (should_add_color) {
add_color(w, data.c_str(), data.size());
} else {
w.write(data);
}
w.put_u8(0);
} else {
add_color(w, text.c_str(), text.size());
if (should_add_color) {
add_color(w, text.c_str(), text.size());
} else {
w.write(text.data(), text.size() * sizeof(char16_t));
}
w.put_u16(0);
}
while (w.str().size() & 3) {
w.put_u8(0);
}
send_command(c, command, 0x00, w.str());
ch.send(command, 0x00, w.str());
}
void send_header_text(shared_ptr<Client> c, uint16_t command,
uint32_t guild_card_number, const u16string& text) {
void send_text(Channel& ch, uint16_t command, const u16string& text, bool should_add_color) {
StringWriter w;
send_text(ch, w, command, text, should_add_color);
}
void send_header_text(Channel& ch, uint16_t command,
uint32_t guild_card_number, const u16string& text, bool should_add_color) {
StringWriter w;
w.put(SC_TextHeader_01_06_11_B0_EE({0, guild_card_number}));
send_text(c, w, command, text);
}
void send_text(shared_ptr<Client> c, uint16_t command,
const u16string& text) {
StringWriter w;
send_text(c, w, command, text);
send_text(ch, w, command, text, should_add_color);
}
void send_message_box(shared_ptr<Client> c, const u16string& text) {
uint16_t command = (c->version == GameVersion::PATCH) ? 0x13 : 0x1A;
send_text(c, command, text);
send_text(c->channel, command, text, true);
}
void send_lobby_name(shared_ptr<Client> c, const u16string& text) {
send_text(c, 0x8A, text);
send_text(c->channel, 0x8A, text, false);
}
void send_quest_info(shared_ptr<Client> c, const u16string& text,
bool is_download_quest) {
send_text(c, is_download_quest ? 0xA5 : 0xA3, text);
send_text(c->channel, is_download_quest ? 0xA5 : 0xA3, text, true);
}
void send_lobby_message_box(shared_ptr<Client> c, const u16string& text) {
send_header_text(c, 0x01, 0, text);
send_header_text(c->channel, 0x01, 0, text, true);
}
void send_ship_info(shared_ptr<Client> c, const u16string& text) {
send_header_text(c, 0x11, 0, text);
send_header_text(c->channel, 0x11, 0, text, true);
}
void send_text_message(Channel& ch, const std::u16string& text) {
send_header_text(ch, 0xB0, 0, text, true);
}
void send_text_message(shared_ptr<Client> c, const u16string& text) {
send_header_text(c, 0xB0, 0, text);
send_header_text(c->channel, 0xB0, 0, text, true);
}
void send_text_message(shared_ptr<Lobby> l, const u16string& text) {
@@ -595,6 +498,10 @@ void send_text_message(shared_ptr<ServerState> s, const u16string& text) {
}
}
void send_chat_message(Channel& ch, const u16string& text) {
send_header_text(ch, 0x06, 0, text, false);
}
void send_chat_message(shared_ptr<Client> c, uint32_t from_guild_card_number,
const u16string& from_name, const u16string& text) {
u16string data;
@@ -604,7 +511,7 @@ void send_chat_message(shared_ptr<Client> c, uint32_t from_guild_card_number,
data.append(remove_language_marker(from_name));
data.append(u"\x09\x09J");
data.append(text);
send_header_text(c, 0x06, from_guild_card_number, data);
send_header_text(c->channel, 0x06, from_guild_card_number, data, false);
}
void send_simple_mail_gc(shared_ptr<Client> c, uint32_t from_guild_card_number,
@@ -677,7 +584,8 @@ void send_card_search_result_t(
// TODO: make this actually make sense... currently we just take the sockname
// for the target client. This also doesn't work if the client is on a virtual
// connection (the address and port are zero).
const sockaddr_in* local_addr = reinterpret_cast<const sockaddr_in*>(&result->local_addr);
const sockaddr_in* local_addr = reinterpret_cast<const sockaddr_in*>(
&result->channel.local_addr);
cmd.reconnect_command.address = local_addr->sin_addr.s_addr;
cmd.reconnect_command.port = ntohs(local_addr->sin_port);
cmd.reconnect_command.unused = 0;
@@ -1200,6 +1108,16 @@ void send_player_stats_change(shared_ptr<Lobby> l, shared_ptr<Client> c,
send_command_vt(l, 0x60, 0x00, subs);
}
void send_warp(Channel& ch, uint8_t client_id, uint32_t area) {
PSOSubcommand cmds[2];
cmds[0].byte[0] = 0x94;
cmds[0].byte[1] = 0x02;
cmds[0].byte[2] = client_id;
cmds[0].byte[3] = 0x00;
cmds[1].dword = area;
ch.send(0x62, client_id, cmds, 8);
}
void send_warp(shared_ptr<Client> c, uint32_t area) {
PSOSubcommand cmds[2];
cmds[0].byte[0] = 0x94;
+9 -4
View File
@@ -18,6 +18,9 @@
// TODO: Many of these functions should take a Channel& instead of a
// shared_ptr<Client>. Refactor functions appropriately.
// Note: There are so many versions of this function for a few reasons:
// - There are a lot of different target types (sometimes we want to send a
// command to one client, sometimes to everyone in a lobby, etc.)
@@ -26,9 +29,8 @@
// pointer is given but size is accidentally not given (e.g. if the type of
// data in the calling function is changed from string to void*).
void send_command(struct bufferevent* bev, GameVersion version,
PSOEncryption* crypt, uint16_t command, uint32_t flag, const void* data,
size_t size, const char* name_str = nullptr);
void send_command(Channel& ch, uint16_t command, uint32_t flag,
const void* data, size_t size);
void send_command(std::shared_ptr<Client> c, uint16_t command,
uint32_t flag, const void* data, size_t size);
@@ -137,9 +139,11 @@ void send_quest_info(std::shared_ptr<Client> c, const std::u16string& text,
bool is_download_quest);
void send_lobby_message_box(std::shared_ptr<Client> c, const std::u16string& text);
void send_ship_info(std::shared_ptr<Client> c, const std::u16string& text);
void send_text_message(Channel& ch, const std::u16string& text);
void send_text_message(std::shared_ptr<Client> c, const std::u16string& text);
void send_text_message(std::shared_ptr<Lobby> l, const std::u16string& text);
void send_text_message(std::shared_ptr<ServerState> l, const std::u16string& text);
void send_chat_message(Channel& ch, const std::u16string& text);
void send_chat_message(std::shared_ptr<Client> c, uint32_t from_serial_number,
const std::u16string& from_name, const std::u16string& text);
void send_simple_mail(std::shared_ptr<Client> c, uint32_t from_serial_number,
@@ -147,7 +151,7 @@ void send_simple_mail(std::shared_ptr<Client> c, uint32_t from_serial_number,
template <typename TargetT>
__attribute__((format(printf, 2, 3))) void send_text_message_printf(
std::shared_ptr<TargetT> t, const char* format, ...) {
TargetT& t, const char* format, ...) {
va_list va;
va_start(va, format);
std::string buf = string_vprintf(format, va);
@@ -196,6 +200,7 @@ enum PlayerStatsChange {
void send_player_stats_change(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c,
PlayerStatsChange which, uint32_t amount);
void send_warp(Channel& ch, uint8_t client_id, uint32_t area);
void send_warp(std::shared_ptr<Client> c, uint32_t area);
void send_ep3_change_music(std::shared_ptr<Client> c, uint32_t song);
+41 -119
View File
@@ -24,41 +24,25 @@
#include "ReceiveCommands.hh"
using namespace std;
using namespace std::placeholders;
void Server::disconnect_client(struct bufferevent* bev) {
this->disconnect_client(this->bev_to_client.at(bev));
}
void Server::disconnect_client(shared_ptr<Client> c) {
this->bev_to_client.erase(c->bev);
struct bufferevent* bev = c->bev;
c->bev = nullptr;
int fd = bufferevent_getfd(bev);
if (fd < 0) {
this->log(INFO, "Client on virtual connection %p disconnected", bev);
if (c->channel.is_virtual_connection) {
this->log(INFO, "Disconnecting client on virtual connection %p",
c->channel.bev.get());
} else {
this->log(INFO, "Client on fd %d disconnected", fd);
this->log(INFO, "Disconnecting client on fd %d",
bufferevent_getfd(c->channel.bev.get()));
}
// if the output buffer is not empty, move the client into the draining pool
// instead of disconnecting it, to make sure all the data gets sent
struct evbuffer* out_buffer = bufferevent_get_output(bev);
if (evbuffer_get_length(out_buffer) == 0) {
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
} else {
// the callbacks will free it when all the data is sent or the client
// disconnects
bufferevent_setcb(bev, nullptr,
Server::dispatch_on_disconnecting_client_output,
Server::dispatch_on_disconnecting_client_error, this);
bufferevent_disable(bev, EV_READ);
}
this->channel_to_client.erase(&c->channel);
c->channel.disconnect();
process_disconnect(this->state, c);
// c is destroyed here (process_disconnect should remove any other references
// to it, e.g. from Lobby objects)
}
void Server::dispatch_on_listen_accept(
@@ -73,25 +57,6 @@ void Server::dispatch_on_listen_error(struct evconnlistener* listener,
reinterpret_cast<Server*>(ctx)->on_listen_error(listener);
}
void Server::dispatch_on_client_input(struct bufferevent* bev, void* ctx) {
reinterpret_cast<Server*>(ctx)->on_client_input(bev);
}
void Server::dispatch_on_client_error(struct bufferevent* bev, short events,
void* ctx) {
reinterpret_cast<Server*>(ctx)->on_client_error(bev, events);
}
void Server::dispatch_on_disconnecting_client_output(struct bufferevent* bev,
void* ctx) {
reinterpret_cast<Server*>(ctx)->on_disconnecting_client_output(bev);
}
void Server::dispatch_on_disconnecting_client_error(struct bufferevent* bev,
short events, void* ctx) {
reinterpret_cast<Server*>(ctx)->on_disconnecting_client_error(bev, events);
}
void Server::on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr*, int) {
@@ -111,13 +76,12 @@ void Server::on_listen_accept(struct evconnlistener* listener,
struct bufferevent *bev = bufferevent_socket_new(this->base.get(), fd,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
shared_ptr<Client> c(new Client(bev, listening_socket->version,
listening_socket->behavior));
this->bev_to_client.emplace(make_pair(bev, c));
bufferevent_setcb(bev, &Server::dispatch_on_client_input, nullptr,
&Server::dispatch_on_client_error, this);
bufferevent_enable(bev, EV_READ | EV_WRITE);
shared_ptr<Client> c(new Client(
bev, listening_socket->version, listening_socket->behavior));
c->channel.on_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this;
this->channel_to_client.emplace(&c->channel, c);
process_connect(this->state, c);
}
@@ -128,19 +92,19 @@ void Server::connect_client(
this->log(INFO, "Client connected on virtual connection %p", bev);
shared_ptr<Client> c(new Client(bev, version, initial_state));
this->bev_to_client.emplace(make_pair(bev, c));
c->channel.on_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this;
this->channel_to_client.emplace(&c->channel, c);
// Manually set the remote address, since the bufferevent has no fd and the
// Client constructor can't figure out the virtual remote address
auto* sin = reinterpret_cast<sockaddr_in*>(&c->remote_addr);
// Channel constructor can't figure out the virtual remote address
auto* sin = reinterpret_cast<sockaddr_in*>(&c->channel.remote_addr);
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = htonl(address);
sin->sin_port = htons(port);
bufferevent_setcb(bev, &Server::dispatch_on_client_input, nullptr,
&Server::dispatch_on_client_error, this);
bufferevent_enable(bev, EV_READ | EV_WRITE);
process_connect(this->state, c);
}
@@ -151,73 +115,31 @@ void Server::on_listen_error(struct evconnlistener* listener) {
event_base_loopexit(this->base.get(), nullptr);
}
void Server::on_client_input(struct bufferevent* bev) {
shared_ptr<Client> c;
try {
c = this->bev_to_client.at(bev);
} catch (const out_of_range& e) {
this->log(WARNING, "Received message from client with no configuration");
// ignore all the data
// TODO: we probably should disconnect them or something
struct evbuffer* in_buffer = bufferevent_get_input(bev);
evbuffer_drain(in_buffer, evbuffer_get_length(in_buffer));
return;
}
void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
Server* server = reinterpret_cast<Server*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch);
if (c->should_disconnect) {
this->disconnect_client(bev);
return;
}
c->last_recv_time = now();
this->receive_and_process_commands(c);
if (c->should_disconnect) {
this->disconnect_client(bev);
return;
server->disconnect_client(c);
} else {
process_command(server->state, c, command, flag, data);
if (c->should_disconnect) {
server->disconnect_client(c);
}
}
}
void Server::on_disconnecting_client_output(struct bufferevent* bev) {
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
void Server::on_client_error(Channel& ch, short events) {
Server* server = reinterpret_cast<Server*>(ch.context_obj);
shared_ptr<Client> c = server->channel_to_client.at(&ch);
void Server::on_client_error(struct bufferevent* bev, short events) {
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
this->log(WARNING, "Client caused error %d (%s)", err,
server->log(WARNING, "Client caused error %d (%s)", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
this->disconnect_client(bev);
}
}
void Server::on_disconnecting_client_error(struct bufferevent* bev,
short events) {
if (events & BEV_EVENT_ERROR) {
int err = EVUTIL_SOCKET_ERROR();
this->log(WARNING, "Disconnecting client caused error %d (%s)", err,
evutil_socket_error_to_string(err));
}
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
bufferevent_free(bev);
}
}
void Server::receive_and_process_commands(shared_ptr<Client> c) {
try {
for_each_received_command(c->bev, c->version, c->crypt_in.get(),
[this, c](uint16_t command, uint16_t flag, const std::string& data) {
process_command(this->state, c, command, flag, data);
});
} catch (const exception& e) {
this->log(INFO, "Error in client stream: %s", e.what());
c->should_disconnect = true;
return;
server->disconnect_client(c);
}
}
@@ -275,11 +197,11 @@ void Server::add_socket(
}
shared_ptr<Client> Server::get_client() const {
if (this->bev_to_client.empty()) {
if (this->channel_to_client.empty()) {
throw runtime_error("no clients on game server");
}
if (this->bev_to_client.size() > 1) {
if (this->channel_to_client.size() > 1) {
throw runtime_error("multiple clients on game server");
}
return this->bev_to_client.begin()->second;
}
return this->channel_to_client.begin()->second;
}
+3 -14
View File
@@ -50,31 +50,20 @@ private:
ServerBehavior behavior);
};
std::unordered_map<int, ListeningSocket> listening_sockets;
std::unordered_map<struct bufferevent*, std::shared_ptr<Client>> bev_to_client;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::shared_ptr<ServerState> state;
static void dispatch_on_listen_accept(struct evconnlistener* listener,
evutil_socket_t fd, struct sockaddr *address, int socklen, void* ctx);
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
static void dispatch_on_client_input(struct bufferevent* bev, void* ctx);
static void dispatch_on_client_error(struct bufferevent* bev, short events,
void* ctx);
static void dispatch_on_disconnecting_client_output(struct bufferevent* bev,
void* ctx);
static void dispatch_on_disconnecting_client_error(struct bufferevent* bev,
short events, void* ctx);
void disconnect_client(struct bufferevent* bev);
void disconnect_client(std::shared_ptr<Client> c);
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd,
struct sockaddr *address, int socklen);
void on_listen_error(struct evconnlistener* listener);
void on_client_input(struct bufferevent* bev);
void on_client_error(struct bufferevent* bev, short events);
void on_disconnecting_client_output(struct bufferevent* bev);
void on_disconnecting_client_error(struct bufferevent* bev, short events);
void receive_and_process_commands(std::shared_ptr<Client> c);
static void on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
static void on_client_error(Channel& ch, short events);
};
+12 -18
View File
@@ -125,11 +125,6 @@ Proxy commands (these will only work when exactly one client is connected):\n\
Enable or disable chat filtering (enabled by default). Chat filtering\n\
applies newserv\'s standard character replacements to chat messages; for\n\
example, $ becomes a tab character and # becomes a newline.\n\
set-chat-safety <on|off>\n\
Enable or disable chat safety (enabled by default). When chat safety is on,\n\
all chat messages that begin with a $ are not sent to the remote server.\n\
This can prevent embarrassing situations if the remote server isn\'t a\n\
newserv instance and you have newserv commands in your chat shortcuts.\n\
set-infinite-hp <on|off>\n\
set-infinite-tp <on|off>\n\
Enable or disable infinite HP or TP. When infinite HP is enabled, attacks\n\
@@ -298,8 +293,11 @@ Proxy commands (these will only work when exactly one client is connected):\n\
} catch (const exception&) { }
if (proxy_session.get()) {
bool to_server = (command_name[1] == 's');
proxy_session->send_to_end_with_header(to_server, data);
if (command_name[1] == 's') {
proxy_session->server_channel.send(data);
} else {
proxy_session->client_channel.send(data);
}
} else {
if (command_name [1] == 's') {
@@ -324,11 +322,11 @@ Proxy commands (these will only work when exactly one client is connected):\n\
data.push_back('\0');
data.resize((data.size() + 3) & (~3));
session->send_to_end(true, 0x06, 0x00, data);
session->server_channel.send(0x06, 0x00, data);
} else if (command_name == "marker") {
auto session = this->get_proxy_session();
session->send_to_end(true, 0x89, stoul(command_args));
session->server_channel.send(0x89, stoul(command_args));
} else if (command_name == "warp") {
auto session = this->get_proxy_session();
@@ -338,8 +336,8 @@ Proxy commands (these will only work when exactly one client is connected):\n\
cmds[0].word[1] = session->lobby_client_id;
cmds[1].dword = stoul(command_args);
session->send_to_end(false, 0x60, 0x00, &cmds, sizeof(cmds));
session->send_to_end(true, 0x60, 0x00, &cmds, sizeof(cmds));
session->client_channel.send(0x60, 0x00, &cmds, sizeof(cmds));
session->server_channel.send(0x60, 0x00, &cmds, sizeof(cmds));
} else if ((command_name == "info-board") || (command_name == "info-board-data")) {
auto session = this->get_proxy_session();
@@ -353,7 +351,7 @@ Proxy commands (these will only work when exactly one client is connected):\n\
data.push_back('\0');
data.resize((data.size() + 3) & (~3));
session->send_to_end(true, 0xD9, 0x00, data);
session->server_channel.send(0xD9, 0x00, data);
} else if (command_name == "set-override-section-id") {
auto session = this->get_proxy_session();
@@ -369,7 +367,7 @@ Proxy commands (these will only work when exactly one client is connected):\n\
session->override_lobby_event = -1;
} else {
session->override_lobby_event = event_for_name(command_args);
session->send_to_end(false, 0xDA, session->override_lobby_event);
session->client_channel.send(0xDA, session->override_lobby_event);
}
} else if (command_name == "set-override-lobby-number") {
@@ -384,10 +382,6 @@ Proxy commands (these will only work when exactly one client is connected):\n\
auto session = this->get_proxy_session();
set_boolean(&session->enable_chat_filter, command_args);
} else if (command_name == "set-chat-safety") {
auto session = this->get_proxy_session();
set_boolean(&session->suppress_newserv_commands, command_args);
} else if (command_name == "set-infinite-hp") {
auto session = this->get_proxy_session();
set_boolean(&session->infinite_hp, command_args);
@@ -398,7 +392,7 @@ Proxy commands (these will only work when exactly one client is connected):\n\
} else if (command_name == "set-switch-assist") {
auto session = this->get_proxy_session();
set_boolean(&session->enable_switch_assist, command_args);
set_boolean(&session->switch_assist, command_args);
} else if (command_name == "set-save-files") {
auto session = this->get_proxy_session();
+4 -4
View File
@@ -176,17 +176,17 @@ shared_ptr<Client> ServerState::find_client(const std::u16string* identifier,
}
uint32_t ServerState::connect_address_for_client(std::shared_ptr<Client> c) {
if (c->is_virtual_connection) {
if (c->remote_addr.ss_family != AF_INET) {
if (c->channel.is_virtual_connection) {
if (c->channel.remote_addr.ss_family != AF_INET) {
throw logic_error("virtual connection is missing remote IPv4 address");
}
const auto* sin = reinterpret_cast<const sockaddr_in*>(&c->remote_addr);
const auto* sin = reinterpret_cast<const sockaddr_in*>(&c->channel.remote_addr);
return IPStackSimulator::connect_address_for_remote_address(
ntohl(sin->sin_addr.s_addr));
} else {
// TODO: we can do something smarter here, like use the sockname to find
// out which interface the client is connected to, and return that address
if (is_local_address(c->remote_addr)) {
if (is_local_address(c->channel.remote_addr)) {
return this->local_address;
} else {
return this->external_address;
+2 -1
View File
@@ -143,8 +143,9 @@ u16string u16name_for_section_id(uint8_t section_id) {
}
uint8_t section_id_for_name(const string& name) {
string lower_name = tolower(name);
try {
return name_to_section_id.at(name);
return name_to_section_id.at(lower_name);
} catch (const out_of_range&) { }
try {
uint64_t x = stoul(name);