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
+1
View File
@@ -45,6 +45,7 @@ endif()
# Executable definition
add_executable(newserv
src/Channel.cc
src/ChatCommands.cc
src/Client.cc
src/Compression.cc
+17 -15
View File
@@ -73,43 +73,46 @@ I mainly built the DOL loading functionality for documentation purposes. By now,
The server's shell supports a variety of administration commands. If the interactive shell is enabled, you can enter these commands at any time, even if the prompt isn't visible. Run `help` in the server's shell to see all of the commands and how to use them.
newserv also supports a variety of commands players can use via the chat interface. These commands work on the game server (that is, in lobbies and games hosted by newserv); they do not work on the proxy server. The chat commands are:
newserv also supports a variety of commands players can use via the chat interface. Any chat message that begins with `$` is treated as a chat command. (If you actually want to send a chat message starting with `$`, type `$$` instead.)
Some commands only work on the game server and not on the proxy server. The chat commands are:
* Information commands
* `$li`: Shows basic information about the lobby or game you're in.
* `$what`: Shows the type, name, and stats of the nearest item on the ground.
* `$li`: Shows basic information about the lobby or game you're in. If you're on the proxy server, shows information about your connection (remote Guild Card number, client ID, etc.) instead.
* `$what` (game server only): Shows the type, name, and stats of the nearest item on the ground.
* Personal state commands
* `$arrow <color-id>`: Changes your lobby arrow color.
* `$secid <section-id>`: Sets your override section ID. After running this command, any games you create will use your override section ID for rare drops instead of your character's actual section ID. To revert to your actual section id, run `$secid` with no name after it.
* Blue Burst player commands
* Blue Burst player commands (game server only)
* `$bbchar <username> <password> <1-4>`: Use this command when playing on a non-BB version of PSO. If the username and password are correct, this command converts your current character to BB format and saves it on the server in the given slot.
* `$edit <stat> <value>`: Modifies your character data.
* `$item <data>`: Sets the next item to be dropped from an enemy or box.
* Game state commands
* Game state commands (game server only)
* `$maxlevel <level>`: Sets the maximum level for players to join the current game.
* `$minlevel <level>`: Sets the minimum level for players to join the current game.
* `$password <password>`: Sets the game's join password. To unlock the game, run `$password` with nothing after it.
* Cheat mode commands
* `$cheat`: Enables or disables cheat mode for the current game. All other cheat mode commands do nothing if cheat mode is disabled.
* `$cheat`: Enables or disables cheat mode for the current game. All other cheat mode commands do nothing if cheat mode is disabled. This command does nothing on the proxy server, since cheat mode is always enabled there.
* `$infhp` / `$inftp`: Enables or disables infinite HP or TP mode. Applies to only you. In infinite HP mode, one-hit KO attacks will still kill you.
* `$warp <area-id>`: Warps yourself to the given area.
* `$next`: Warps yourself to the next area.
* `$next` (game server only): Warps yourself to the next area.
* `$swa`: Enables or disables switch assist. When enabled, the server will attempt to automatically unlock two-player doors in solo games if you step on both switches sequentially.
* Configuration commands
* `$event <event>` / `$allevent <event>`: Sets the current holiday event in the current lobby, or in all lobbies. Holiday events are documented in the "Using $event" item in the information menu.
* `$song <song-id>`: Plays a specific song in the current lobby (Episode 3 only).
* `$event <event>`: Sets the current holiday event in the current lobby. Holiday events are documented in the "Using $event" item in the information menu. If you're on the proxy server, only you will see the new event; other players will not.
* `$allevent <event>` (game server only): Sets the current holiday event in all lobbies.
* `$song <song-id>` (game server only, Episode 3 only): Plays a specific song in the current lobby.
* Administration commands
* Administration commands (game server only)
* `$ann <message>`: Sends an announcement message. The message text is sent to all players in all games and lobbies.
* `$ax <message>`: Sends a message to the server's terminal. This cannot be used to run server shell commands; it only prints text to stderr.
* `$silence <identifier>`: Silences a player (remove their ability to chat) or unsilences a player. The identifier may be the player's name or guild card number.
* `$kick <identifier>`: Disconnects a player. The identifier may be the player's name or guild card number.
* `$ban <identifier>`: Bans a player. The identifier may be the player's name or guild card number.
* `$silence <identifier>`: Silences a player (remove their ability to chat) or unsilences a player. The identifier may be the player's name or Guild Card number.
* `$kick <identifier>`: Disconnects a player. The identifier may be the player's name or Guild Card number.
* `$ban <identifier>`: Bans a player. The identifier may be the player's name or Guild Card number.
### Using newserv as a proxy
@@ -119,8 +122,7 @@ To use the proxy, add an entry to the ProxyDestinations dictionary in config.jso
A few things to be aware of when using the proxy server:
- On PC and GC, using the Change Ship or Change Block actions from the lobby counter will bring you back to newserv's main menu, not the remote server's ship select. You can go back to the server you were just on by choosing it from newserv's proxy server menu again.
- The remote server will probably try to assign you a guild card number that doesn't match the one you have on newserv. The proxy server rewrites the commands on the fly to make it look like the remote server assigned you the same guild card number as you have on newserv, but if the remote server has some external integrations (e.g. forum or Discord bots), they will use the guild card number that the remote server believes it has assigned to you. The number assigned by the remote server is shown to you when you connect to the remote server.
- The proxy server blocks chat commands that look like newserv commands by default, but you can change this with the `set-chat-safety off` shell command if needed.
- The remote server will probably try to assign you a Guild Card number that doesn't match the one you have on newserv. The proxy server rewrites the commands on the fly to make it look like the remote server assigned you the same Guild Card number as you have on newserv, but if the remote server has some external integrations (e.g. forum or Discord bots), they will use the Guild Card number that the remote server believes it has assigned to you. The number assigned by the remote server is shown to you when you first connect to the remote server, and you can retrieve it in lobbies or during games with the $li command.
- There are shell commands that affect clients on the proxy (run 'help' in the shell to see what they are). All proxy commands in the shell only work when there's exactly one client connected through the proxy, since there isn't (yet) a way to say via the shell which session you want to affect.
### Connecting local clients
+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);