rewrite proxy server to support multiple clients and integration with game server
This commit is contained in:
@@ -59,7 +59,6 @@ add_executable(newserv
|
||||
src/ProxyServer.cc
|
||||
src/Shell.cc
|
||||
src/ServerShell.cc
|
||||
src/ProxyShell.cc
|
||||
src/IPFrameInfo.cc
|
||||
src/IPStackSimulator.cc
|
||||
src/Main.cc
|
||||
|
||||
+9
-21
@@ -15,7 +15,7 @@ using namespace std;
|
||||
|
||||
|
||||
|
||||
static const uint64_t CLIENT_CONFIG_MAGIC = 0x492A890E82AC9839;
|
||||
const uint64_t CLIENT_CONFIG_MAGIC = 0x492A890E82AC9839;
|
||||
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ Client::Client(
|
||||
bev(bev),
|
||||
server_behavior(server_behavior),
|
||||
should_disconnect(false),
|
||||
proxy_destination_address(0),
|
||||
proxy_destination_port(0),
|
||||
play_time_begin(now()),
|
||||
last_recv_time(this->play_time_begin),
|
||||
last_send_time(0),
|
||||
@@ -52,32 +54,16 @@ Client::Client(
|
||||
memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr));
|
||||
}
|
||||
|
||||
bool Client::send(string&& data) {
|
||||
if (!this->bev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->crypt_out.get()) {
|
||||
this->crypt_out->encrypt(data.data(), data.size());
|
||||
}
|
||||
|
||||
struct evbuffer* buf = bufferevent_get_output(this->bev);
|
||||
evbuffer_add(buf, data.data(), data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
ClientConfig Client::export_config() const {
|
||||
ClientConfig cc;
|
||||
cc.magic = CLIENT_CONFIG_MAGIC;
|
||||
cc.bb_game_state = this->bb_game_state;
|
||||
cc.bb_player_index = this->bb_player_index;
|
||||
cc.flags = this->flags;
|
||||
for (size_t x = 0; x < 5; x++) {
|
||||
cc.unused[x] = 0xFFFFFFFF;
|
||||
}
|
||||
for (size_t x = 0; x < 2; x++) {
|
||||
cc.unused_bb_only[x] = 0xFFFFFFFF;
|
||||
}
|
||||
cc.proxy_destination_address = this->proxy_destination_address;
|
||||
cc.proxy_destination_port = this->proxy_destination_port;
|
||||
memset(cc.unused, 0xFF, sizeof(cc.unused));
|
||||
memset(cc.unused_bb_only, 0xFF, sizeof(cc.unused_bb_only));
|
||||
return cc;
|
||||
}
|
||||
|
||||
@@ -88,4 +74,6 @@ void Client::import_config(const ClientConfig& cc) {
|
||||
this->bb_game_state = cc.bb_game_state;
|
||||
this->bb_player_index = cc.bb_player_index;
|
||||
this->flags = cc.flags;
|
||||
this->proxy_destination_address = cc.proxy_destination_address;
|
||||
this->proxy_destination_port = cc.proxy_destination_port;
|
||||
}
|
||||
|
||||
+11
-5
@@ -10,12 +10,17 @@
|
||||
|
||||
|
||||
|
||||
extern const uint64_t CLIENT_CONFIG_MAGIC;
|
||||
|
||||
|
||||
|
||||
enum class ServerBehavior {
|
||||
SPLIT_RECONNECT = 0,
|
||||
LOGIN_SERVER,
|
||||
LOBBY_SERVER,
|
||||
DATA_SERVER_BB,
|
||||
PATCH_SERVER,
|
||||
PROXY_SERVER,
|
||||
};
|
||||
|
||||
struct ClientConfig {
|
||||
@@ -23,8 +28,10 @@ struct ClientConfig {
|
||||
uint8_t bb_game_state;
|
||||
uint8_t bb_player_index;
|
||||
uint16_t flags;
|
||||
uint32_t unused[5];
|
||||
uint32_t unused_bb_only[2];
|
||||
uint32_t proxy_destination_address;
|
||||
uint16_t proxy_destination_port;
|
||||
uint8_t unused[0x0E];
|
||||
uint8_t unused_bb_only[0x08];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Client {
|
||||
@@ -51,6 +58,8 @@ struct Client {
|
||||
ServerBehavior server_behavior;
|
||||
bool is_virtual_connection;
|
||||
bool should_disconnect;
|
||||
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)
|
||||
@@ -75,9 +84,6 @@ struct Client {
|
||||
Client(struct bufferevent* bev, GameVersion version,
|
||||
ServerBehavior server_behavior);
|
||||
|
||||
// adds data to the client's output buffer, encrypting it first
|
||||
bool send(std::string&& data);
|
||||
|
||||
ClientConfig export_config() const;
|
||||
void import_config(const ClientConfig& cc);
|
||||
};
|
||||
|
||||
+23
-19
@@ -77,13 +77,10 @@ string IPStackSimulator::str_for_tcp_connection(shared_ptr<const IPClient> c,
|
||||
IPStackSimulator::IPStackSimulator(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
std::shared_ptr<Server> game_server,
|
||||
std::shared_ptr<ProxyServer> proxy_server,
|
||||
std::shared_ptr<ServerState> state)
|
||||
: base(base),
|
||||
game_server(game_server),
|
||||
proxy_server(proxy_server),
|
||||
state(state),
|
||||
proxy_destination_address(0),
|
||||
pcap_text_log_file(state->ip_stack_debug ? fopen("IPStackSimulator-Log.txt", "wt") : nullptr) {
|
||||
memset(this->host_mac_address_bytes, 0x90, 6);
|
||||
memset(this->broadcast_mac_address_bytes, 0xFF, 6);
|
||||
@@ -421,9 +418,7 @@ void IPStackSimulator::on_client_udp_frame(
|
||||
// r_udp.size filled in later
|
||||
// r_udp.checksum filled in later
|
||||
|
||||
uint32_t resolved_address = this->proxy_destination_address
|
||||
? this->proxy_destination_address
|
||||
: this->connect_address_for_remote_address(c->ipv4_addr);
|
||||
uint32_t resolved_address = this->connect_address_for_remote_address(c->ipv4_addr);
|
||||
|
||||
string r_data = DNSServer::response_for_query(
|
||||
fi.payload, fi.payload_size, resolved_address);
|
||||
@@ -758,25 +753,34 @@ void IPStackSimulator::open_server_connection(
|
||||
// Link the client to the server - the server sees this as a normal TCP
|
||||
// connection and treats it as if the client connected to one of its listening
|
||||
// sockets
|
||||
string conn_str = this->str_for_tcp_connection(c, conn);
|
||||
if (this->game_server.get()) {
|
||||
const PortConfiguration* port_config;
|
||||
try {
|
||||
port_config = &this->state->numbered_port_configuration.at(conn.server_port);
|
||||
} catch (const out_of_range&) {
|
||||
bufferevent_free(bevs[1]);
|
||||
throw logic_error("client connected to port missing from configuration");
|
||||
}
|
||||
const PortConfiguration* port_config;
|
||||
try {
|
||||
port_config = &this->state->numbered_port_configuration.at(conn.server_port);
|
||||
} catch (const out_of_range&) {
|
||||
bufferevent_free(bevs[1]);
|
||||
throw logic_error("client connected to port missing from configuration");
|
||||
}
|
||||
|
||||
string conn_str = this->str_for_tcp_connection(c, conn);
|
||||
if (port_config->behavior == ServerBehavior::PROXY_SERVER) {
|
||||
if (!this->state->proxy_server.get()) {
|
||||
log(ERROR, "[IPStackSimulator] TCP connection %s is to non-running proxy server",
|
||||
conn_str.c_str());
|
||||
flush_and_free_bufferevent(bevs[1]);
|
||||
} else {
|
||||
this->state->proxy_server->connect_client(bevs[1], conn.server_port);
|
||||
log(INFO, "[IPStackSimulator] Connected TCP connection %s to proxy server",
|
||||
conn_str.c_str());
|
||||
}
|
||||
} else if (this->game_server.get()) {
|
||||
this->game_server->connect_client(bevs[1], c->ipv4_addr, conn.client_port,
|
||||
port_config->version, port_config->behavior);
|
||||
log(INFO, "[IPStackSimulator] Connected TCP connection %s to game server",
|
||||
conn_str.c_str());
|
||||
|
||||
} else if (this->proxy_server.get()) {
|
||||
this->proxy_server->connect_client(bevs[1], conn.server_addr, conn.server_port);
|
||||
log(INFO, "[IPStackSimulator] Connected TCP connection %s to proxy server",
|
||||
} else {
|
||||
log(ERROR, "[IPStackSimulator] No server available for TCP connection %s",
|
||||
conn_str.c_str());
|
||||
flush_and_free_bufferevent(bevs[1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ public:
|
||||
IPStackSimulator(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
std::shared_ptr<Server> game_server,
|
||||
std::shared_ptr<ProxyServer> proxy_server,
|
||||
std::shared_ptr<ServerState> state);
|
||||
~IPStackSimulator();
|
||||
|
||||
@@ -27,18 +26,12 @@ public:
|
||||
void listen(int port);
|
||||
void add_socket(int fd);
|
||||
|
||||
inline void set_proxy_destination_address(uint32_t addr) {
|
||||
this->proxy_destination_address = addr;
|
||||
}
|
||||
|
||||
static uint32_t connect_address_for_remote_address(uint32_t remote_addr);
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<Server> game_server;
|
||||
std::shared_ptr<ProxyServer> proxy_server;
|
||||
std::shared_ptr<ServerState> state;
|
||||
uint32_t proxy_destination_address;
|
||||
|
||||
using unique_listener = std::unique_ptr<struct evconnlistener, void(*)(struct evconnlistener*)>;
|
||||
using unique_bufferevent = std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)>;
|
||||
|
||||
+25
-6
@@ -53,6 +53,12 @@ LicenseManager::LicenseManager(const string& filename) : filename(filename) {
|
||||
auto licenses = load_vector_file<License>(this->filename);
|
||||
for (const auto& read_license : licenses) {
|
||||
shared_ptr<License> license(new License(read_license));
|
||||
|
||||
// Before the temporary flag existed, licenses with root privileges would
|
||||
// have the temporary flag set. To migrate these, explicitly unset the
|
||||
// flag for all licenses loaded from the license file.
|
||||
license->privileges &= ~Privilege::TEMPORARY;
|
||||
|
||||
uint32_t serial_number = license->serial_number;
|
||||
this->bb_username_to_license.emplace(license->username, license);
|
||||
this->serial_number_to_license.emplace(serial_number, license);
|
||||
@@ -67,6 +73,9 @@ LicenseManager::LicenseManager(const string& filename) : filename(filename) {
|
||||
void LicenseManager::save() const {
|
||||
auto f = fopen_unique(this->filename, "wb");
|
||||
for (const auto& it : this->serial_number_to_license) {
|
||||
if (it.second->privileges & Privilege::TEMPORARY) {
|
||||
continue;
|
||||
}
|
||||
fwritex(f.get(), it.second.get(), sizeof(License));
|
||||
}
|
||||
}
|
||||
@@ -154,33 +163,43 @@ vector<License> LicenseManager::snapshot() const {
|
||||
}
|
||||
|
||||
|
||||
shared_ptr<const License> LicenseManager::create_license_pc(
|
||||
uint32_t serial_number,const char* access_key, const char* password) {
|
||||
|
||||
shared_ptr<License> LicenseManager::create_license_pc(
|
||||
uint32_t serial_number,const char* access_key, const char* password, bool temporary) {
|
||||
shared_ptr<License> l(new License());
|
||||
l->serial_number = serial_number;
|
||||
strncpy(l->access_key, access_key, 8);
|
||||
if (password) {
|
||||
strncpy(l->gc_password, password, 8);
|
||||
}
|
||||
if (temporary) {
|
||||
l->privileges |= Privilege::TEMPORARY;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
shared_ptr<const License> LicenseManager::create_license_gc(
|
||||
uint32_t serial_number, const char* access_key, const char* password) {
|
||||
shared_ptr<License> LicenseManager::create_license_gc(
|
||||
uint32_t serial_number, const char* access_key, const char* password, bool temporary) {
|
||||
shared_ptr<License> l(new License());
|
||||
l->serial_number = serial_number;
|
||||
strncpy(l->access_key, access_key, 12);
|
||||
if (password) {
|
||||
strncpy(l->gc_password, password, 8);
|
||||
}
|
||||
if (temporary) {
|
||||
l->privileges |= Privilege::TEMPORARY;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
shared_ptr<const License> LicenseManager::create_license_bb(
|
||||
uint32_t serial_number, const char* username, const char* password) {
|
||||
shared_ptr<License> LicenseManager::create_license_bb(
|
||||
uint32_t serial_number, const char* username, const char* password, bool temporary) {
|
||||
shared_ptr<License> l(new License());
|
||||
l->serial_number = serial_number;
|
||||
strncpy(l->username, username, 19);
|
||||
strncpy(l->bb_password, password, 19);
|
||||
if (temporary) {
|
||||
l->privileges |= Privilege::TEMPORARY;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
+9
-7
@@ -17,7 +17,9 @@ enum Privilege {
|
||||
|
||||
MODERATOR = 0x00000007,
|
||||
ADMINISTRATOR = 0x0000003F,
|
||||
ROOT = 0xFFFFFFFF,
|
||||
ROOT = 0x7FFFFFFF,
|
||||
|
||||
TEMPORARY = 0x80000000,
|
||||
};
|
||||
|
||||
enum LicenseVerifyAction {
|
||||
@@ -59,12 +61,12 @@ public:
|
||||
void remove(uint32_t serial_number);
|
||||
std::vector<License> snapshot() const;
|
||||
|
||||
static std::shared_ptr<const License> create_license_pc(
|
||||
uint32_t serial_number, const char* access_key, const char* password);
|
||||
static std::shared_ptr<const License> create_license_gc(
|
||||
uint32_t serial_number, const char* access_key, const char* password);
|
||||
static std::shared_ptr<const License> create_license_bb(
|
||||
uint32_t serial_number, const char* username, const char* password);
|
||||
static std::shared_ptr<License> create_license_pc(
|
||||
uint32_t serial_number, const char* access_key, const char* password, bool temporary);
|
||||
static std::shared_ptr<License> create_license_gc(
|
||||
uint32_t serial_number, const char* access_key, const char* password, bool temporary);
|
||||
static std::shared_ptr<License> create_license_bb(
|
||||
uint32_t serial_number, const char* username, const char* password, bool temporary);
|
||||
|
||||
protected:
|
||||
void save() const;
|
||||
|
||||
+67
-65
@@ -19,7 +19,6 @@
|
||||
#include "FileContentsCache.hh"
|
||||
#include "Text.hh"
|
||||
#include "ServerShell.hh"
|
||||
#include "ProxyShell.hh"
|
||||
#include "IPStackSimulator.hh"
|
||||
|
||||
using namespace std;
|
||||
@@ -51,6 +50,9 @@ static const unordered_map<string, PortConfiguration> default_port_to_behavior({
|
||||
{"pc-lobby", {9420, GameVersion::PC, ServerBehavior::LOBBY_SERVER}},
|
||||
{"gc-lobby", {9421, GameVersion::GC, ServerBehavior::LOBBY_SERVER}},
|
||||
{"bb-lobby", {9422, GameVersion::BB, ServerBehavior::LOBBY_SERVER}},
|
||||
{"pc-proxy", {9520, GameVersion::PC, ServerBehavior::PROXY_SERVER}},
|
||||
{"gc-proxy", {9521, GameVersion::GC, ServerBehavior::PROXY_SERVER}},
|
||||
{"bb-proxy", {9522, GameVersion::BB, ServerBehavior::PROXY_SERVER}},
|
||||
});
|
||||
|
||||
|
||||
@@ -100,18 +102,45 @@ void populate_state_from_config(shared_ptr<ServerState> s,
|
||||
|
||||
information_menu->emplace_back(INFORMATION_MENU_GO_BACK, u"Go back",
|
||||
u"Return to the\nmain menu", 0);
|
||||
|
||||
uint32_t item_id = 0;
|
||||
for (const auto& item : d.at("InformationMenuContents")->as_list()) {
|
||||
auto& v = item->as_list();
|
||||
information_menu->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
|
||||
decode_sjis(v.at(1)->as_string()), MenuItemFlag::REQUIRES_MESSAGE_BOXES);
|
||||
information_contents->emplace_back(decode_sjis(v.at(2)->as_string()));
|
||||
item_id++;
|
||||
{
|
||||
uint32_t item_id = 0;
|
||||
for (const auto& item : d.at("InformationMenuContents")->as_list()) {
|
||||
auto& v = item->as_list();
|
||||
information_menu->emplace_back(item_id, decode_sjis(v.at(0)->as_string()),
|
||||
decode_sjis(v.at(1)->as_string()), MenuItemFlag::REQUIRES_MESSAGE_BOXES);
|
||||
information_contents->emplace_back(decode_sjis(v.at(2)->as_string()));
|
||||
item_id++;
|
||||
}
|
||||
}
|
||||
s->information_menu = information_menu;
|
||||
s->information_contents = information_contents;
|
||||
|
||||
s->proxy_destinations_menu.emplace_back(PROXY_DESTINATIONS_MENU_GO_BACK,
|
||||
u"Go back", u"Return to the\nmain menu", 0);
|
||||
{
|
||||
uint32_t item_id = 0;
|
||||
for (const auto& item : d.at("ProxyDestinations")->as_dict()) {
|
||||
const string& netloc_str = item.second->as_string();
|
||||
s->proxy_destinations_menu.emplace_back(item_id, decode_sjis(item.first),
|
||||
decode_sjis(netloc_str), 0);
|
||||
s->proxy_destinations.emplace_back(parse_netloc(netloc_str));
|
||||
item_id++;
|
||||
}
|
||||
}
|
||||
|
||||
s->main_menu.emplace_back(MAIN_MENU_GO_TO_LOBBY, u"Go to lobby",
|
||||
u"Join the lobby", 0);
|
||||
s->main_menu.emplace_back(MAIN_MENU_INFORMATION, u"Information",
|
||||
u"View server information", MenuItemFlag::REQUIRES_MESSAGE_BOXES);
|
||||
if (!s->proxy_destinations.empty()) {
|
||||
s->main_menu.emplace_back(MAIN_MENU_PROXY_DESTINATIONS, u"Proxy server",
|
||||
u"Connect to another\nserver", MenuItemFlag::REQUIRES_MESSAGE_BOXES);
|
||||
}
|
||||
s->main_menu.emplace_back(MAIN_MENU_DOWNLOAD_QUESTS, u"Download quests",
|
||||
u"Download quests", 0);
|
||||
s->main_menu.emplace_back(MAIN_MENU_DISCONNECT, u"Disconnect",
|
||||
u"Disconnect", 0);
|
||||
|
||||
try {
|
||||
s->welcome_message = decode_sjis(d.at("WelcomeMessage")->as_string());
|
||||
} catch (const out_of_range&) { }
|
||||
@@ -214,24 +243,7 @@ void drop_privileges(const string& username) {
|
||||
|
||||
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
string proxy_hostname;
|
||||
int proxy_port = 0;
|
||||
GameVersion proxy_version = GameVersion::GC;
|
||||
for (int x = 1; x < argc; x++) {
|
||||
if (!strncmp(argv[x], "--proxy-destination=", 20)) {
|
||||
auto netloc = parse_netloc(&argv[x][20], 9100);
|
||||
proxy_hostname = netloc.first;
|
||||
proxy_port = netloc.second;
|
||||
|
||||
} else if (!strncmp(argv[x], "--proxy-version=", 16)) {
|
||||
proxy_version = version_for_name(&argv[x][16]);
|
||||
|
||||
} else {
|
||||
throw invalid_argument(string_printf("unknown option: %s", argv[x]));
|
||||
}
|
||||
}
|
||||
|
||||
int main(int, char**) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
shared_ptr<ServerState> state(new ServerState());
|
||||
@@ -249,6 +261,18 @@ int main(int argc, char* argv[]) {
|
||||
auto config_json = JSONObject::parse(load_file("system/config.json"));
|
||||
populate_state_from_config(state, config_json);
|
||||
|
||||
log(INFO, "Loading license list");
|
||||
state->license_manager.reset(new LicenseManager("system/licenses.nsi"));
|
||||
|
||||
log(INFO, "Loading battle parameters");
|
||||
state->battle_params.reset(new BattleParamTable("system/blueburst/BattleParamEntry"));
|
||||
|
||||
log(INFO, "Loading level table");
|
||||
state->level_table.reset(new LevelTable("system/blueburst/PlyLevelTbl.prs", true));
|
||||
|
||||
log(INFO, "Collecting quest metadata");
|
||||
state->quest_index.reset(new QuestIndex("system/quests"));
|
||||
|
||||
shared_ptr<DNSServer> dns_server;
|
||||
if (state->dns_server_port) {
|
||||
log(INFO, "Starting DNS server");
|
||||
@@ -259,50 +283,31 @@ int main(int argc, char* argv[]) {
|
||||
log(INFO, "DNS server is disabled");
|
||||
}
|
||||
|
||||
shared_ptr<ProxyServer> proxy_server;
|
||||
shared_ptr<Server> game_server;
|
||||
uint32_t proxy_destination_address = 0;
|
||||
if (proxy_port) {
|
||||
if (!state->proxy_destinations.empty()) {
|
||||
log(INFO, "Starting proxy server");
|
||||
sockaddr_storage proxy_destination_ss = make_sockaddr_storage(
|
||||
proxy_hostname, proxy_port).first;
|
||||
if (proxy_destination_ss.ss_family != AF_INET) {
|
||||
throw runtime_error("proxy destination address is not IPv4");
|
||||
}
|
||||
proxy_destination_address = ntohl(
|
||||
reinterpret_cast<struct sockaddr_in*>(&proxy_destination_ss)->sin_addr.s_addr);
|
||||
proxy_server.reset(new ProxyServer(base, proxy_destination_ss, proxy_version));
|
||||
proxy_server->listen(proxy_port);
|
||||
if (proxy_version == GameVersion::BB) {
|
||||
proxy_server->listen(proxy_port + 1);
|
||||
}
|
||||
state->proxy_server.reset(new ProxyServer(base, state));
|
||||
}
|
||||
|
||||
} else {
|
||||
log(INFO, "Starting game server");
|
||||
game_server.reset(new Server(base, state));
|
||||
for (const auto& it : state->named_port_configuration) {
|
||||
log(INFO, "Starting game server");
|
||||
game_server.reset(new Server(base, state));
|
||||
|
||||
log(INFO, "Opening sockets");
|
||||
for (const auto& it : state->named_port_configuration) {
|
||||
if (it.second.behavior == ServerBehavior::PROXY_SERVER) {
|
||||
if (state->proxy_server.get()) {
|
||||
state->proxy_server->listen(it.second.port, it.second.version);
|
||||
}
|
||||
} else {
|
||||
game_server->listen("", it.second.port, it.second.version, it.second.behavior);
|
||||
}
|
||||
|
||||
log(INFO, "Loading license list");
|
||||
state->license_manager.reset(new LicenseManager("system/licenses.nsi"));
|
||||
|
||||
log(INFO, "Loading battle parameters");
|
||||
state->battle_params.reset(new BattleParamTable("system/blueburst/BattleParamEntry"));
|
||||
|
||||
log(INFO, "Loading level table");
|
||||
state->level_table.reset(new LevelTable("system/blueburst/PlyLevelTbl.prs", true));
|
||||
|
||||
log(INFO, "Collecting quest metadata");
|
||||
state->quest_index.reset(new QuestIndex("system/quests"));
|
||||
}
|
||||
|
||||
shared_ptr<IPStackSimulator> ip_stack_simulator;
|
||||
if (!state->ip_stack_addresses.empty()) {
|
||||
log(INFO, "Starting IP stack simulator");
|
||||
ip_stack_simulator.reset(new IPStackSimulator(
|
||||
base, game_server, proxy_server, state));
|
||||
ip_stack_simulator->set_proxy_destination_address(proxy_destination_address);
|
||||
base, game_server, state));
|
||||
for (const auto& it : state->ip_stack_addresses) {
|
||||
auto netloc = parse_netloc(it);
|
||||
ip_stack_simulator->listen(netloc.first, netloc.second);
|
||||
@@ -321,16 +326,13 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
shared_ptr<Shell> shell;
|
||||
if (should_run_shell) {
|
||||
if (proxy_port) {
|
||||
shell.reset(new ProxyShell(base, state, proxy_server));
|
||||
} else {
|
||||
shell.reset(new ServerShell(base, state));
|
||||
}
|
||||
shell.reset(new ServerShell(base, state));
|
||||
}
|
||||
|
||||
log(INFO, "Ready");
|
||||
event_base_dispatch(base.get());
|
||||
|
||||
log(INFO, "Normal shutdown");
|
||||
state->proxy_server.reset(); // Break reference cycle
|
||||
return 0;
|
||||
}
|
||||
|
||||
+18
@@ -6,6 +6,24 @@
|
||||
|
||||
|
||||
|
||||
#define MAIN_MENU_ID 0x11000011
|
||||
#define INFORMATION_MENU_ID 0x22000022
|
||||
#define LOBBY_MENU_ID 0x33000033
|
||||
#define GAME_MENU_ID 0x44000044
|
||||
#define QUEST_MENU_ID 0x55000055
|
||||
#define QUEST_FILTER_MENU_ID 0x66000066
|
||||
#define PROXY_DESTINATIONS_MENU_ID 0x77000077
|
||||
|
||||
#define MAIN_MENU_GO_TO_LOBBY 0x11AAAA11
|
||||
#define MAIN_MENU_INFORMATION 0x11BBBB11
|
||||
#define MAIN_MENU_DOWNLOAD_QUESTS 0x11CCCC11
|
||||
#define MAIN_MENU_PROXY_DESTINATIONS 0x11DDDD11
|
||||
#define MAIN_MENU_DISCONNECT 0x11EEEE11
|
||||
#define INFORMATION_MENU_GO_BACK 0x22FFFF22
|
||||
#define PROXY_DESTINATIONS_MENU_GO_BACK 0x77FFFF77
|
||||
|
||||
|
||||
|
||||
enum MenuItemFlag {
|
||||
INVISIBLE_ON_DC = 0x01,
|
||||
INVISIBLE_ON_PC = 0x02,
|
||||
|
||||
+112
-14
@@ -3,6 +3,9 @@
|
||||
#include <event2/buffer.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -17,43 +20,109 @@ PSOCommandHeader::PSOCommandHeader() {
|
||||
uint16_t PSOCommandHeader::command(GameVersion version) const {
|
||||
switch (version) {
|
||||
case GameVersion::DC:
|
||||
return this->dc.command;
|
||||
case GameVersion::GC:
|
||||
return reinterpret_cast<const PSOCommandHeaderDCGC*>(this)->command;
|
||||
return this->gc.command;
|
||||
case GameVersion::PC:
|
||||
case GameVersion::PATCH:
|
||||
return reinterpret_cast<const PSOCommandHeaderPC*>(this)->command;
|
||||
return this->pc.command;
|
||||
case GameVersion::BB:
|
||||
return reinterpret_cast<const PSOCommandHeaderBB*>(this)->command;
|
||||
return this->bb.command;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void PSOCommandHeader::set_command(GameVersion version, uint16_t command) {
|
||||
switch (version) {
|
||||
case GameVersion::DC:
|
||||
this->dc.command = command;
|
||||
break;
|
||||
case GameVersion::GC:
|
||||
this->gc.command = command;
|
||||
break;
|
||||
case GameVersion::PC:
|
||||
case GameVersion::PATCH:
|
||||
this->pc.command = command;
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
this->bb.command = command;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
|
||||
uint16_t PSOCommandHeader::size(GameVersion version) const {
|
||||
switch (version) {
|
||||
case GameVersion::DC:
|
||||
return this->dc.size;
|
||||
case GameVersion::GC:
|
||||
return reinterpret_cast<const PSOCommandHeaderDCGC*>(this)->size;
|
||||
return this->gc.size;
|
||||
case GameVersion::PC:
|
||||
case GameVersion::PATCH:
|
||||
return reinterpret_cast<const PSOCommandHeaderPC*>(this)->size;
|
||||
return this->pc.size;
|
||||
case GameVersion::BB:
|
||||
return reinterpret_cast<const PSOCommandHeaderBB*>(this)->size;
|
||||
return this->bb.size;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void PSOCommandHeader::set_size(GameVersion version, uint32_t size) {
|
||||
switch (version) {
|
||||
case GameVersion::DC:
|
||||
this->dc.size = size;
|
||||
break;
|
||||
case GameVersion::GC:
|
||||
this->gc.size = size;
|
||||
break;
|
||||
case GameVersion::PC:
|
||||
case GameVersion::PATCH:
|
||||
this->pc.size = size;
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
this->bb.size = size;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
|
||||
uint32_t PSOCommandHeader::flag(GameVersion version) const {
|
||||
switch (version) {
|
||||
case GameVersion::DC:
|
||||
return this->dc.flag;
|
||||
case GameVersion::GC:
|
||||
return reinterpret_cast<const PSOCommandHeaderDCGC*>(this)->flag;
|
||||
return this->gc.flag;
|
||||
case GameVersion::PC:
|
||||
case GameVersion::PATCH:
|
||||
return reinterpret_cast<const PSOCommandHeaderPC*>(this)->flag;
|
||||
return this->pc.flag;
|
||||
case GameVersion::BB:
|
||||
return reinterpret_cast<const PSOCommandHeaderBB*>(this)->flag;
|
||||
return this->bb.flag;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
void PSOCommandHeader::set_flag(GameVersion version, uint32_t flag) {
|
||||
switch (version) {
|
||||
case GameVersion::DC:
|
||||
this->dc.flag = flag;
|
||||
break;
|
||||
case GameVersion::GC:
|
||||
this->gc.flag = flag;
|
||||
break;
|
||||
case GameVersion::PC:
|
||||
case GameVersion::PATCH:
|
||||
this->pc.flag = flag;
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
this->bb.flag = flag;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
throw logic_error("unknown game version");
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +131,7 @@ void for_each_received_command(
|
||||
struct bufferevent* bev,
|
||||
GameVersion version,
|
||||
PSOEncryption* crypt,
|
||||
function<void(uint16_t, uint16_t, const string&)> fn) {
|
||||
function<void(uint16_t, uint16_t, string&)> fn) {
|
||||
struct evbuffer* buf = bufferevent_get_input(bev);
|
||||
|
||||
size_t header_size = version == GameVersion::BB ? 8 : 4;
|
||||
@@ -108,4 +177,33 @@ void for_each_received_command(
|
||||
|
||||
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) {
|
||||
string name_token;
|
||||
if (name && name[0]) {
|
||||
name_token = string(" from ") + name;
|
||||
}
|
||||
log(INFO, "Received%s (version=%d command=%04hX flag=%08X)",
|
||||
name_token.c_str(), static_cast<int>(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);
|
||||
|
||||
// TODO: This is unnecessarily slow. It'd be nice to have a print_data_v() so
|
||||
// we don't have to copy data around here.
|
||||
StringWriter w;
|
||||
w.write(&header, header_size);
|
||||
w.write(data, size);
|
||||
|
||||
print_data(stderr, w.str());
|
||||
}
|
||||
|
||||
+15
-1
@@ -33,8 +33,14 @@ union PSOCommandHeader {
|
||||
PSOCommandHeaderBB bb;
|
||||
|
||||
uint16_t command(GameVersion version) const;
|
||||
void set_command(GameVersion version, uint16_t command);
|
||||
uint16_t size(GameVersion version) const;
|
||||
void set_size(GameVersion version, uint32_t size);
|
||||
uint32_t flag(GameVersion version) const;
|
||||
void set_flag(GameVersion version, uint32_t flag);
|
||||
static inline size_t header_size(GameVersion version) {
|
||||
return (version == GameVersion::BB) ? 8 : 4;
|
||||
}
|
||||
|
||||
PSOCommandHeader();
|
||||
} __attribute__((packed));
|
||||
@@ -49,4 +55,12 @@ void for_each_received_command(
|
||||
struct bufferevent* bev,
|
||||
GameVersion version,
|
||||
PSOEncryption* crypt,
|
||||
std::function<void(uint16_t, uint16_t, const std::string&)> fn);
|
||||
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);
|
||||
|
||||
+651
-461
File diff suppressed because it is too large
Load Diff
+103
-57
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
|
||||
|
||||
@@ -22,77 +23,122 @@ public:
|
||||
ProxyServer(ProxyServer&&) = delete;
|
||||
ProxyServer(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& initial_destination,
|
||||
GameVersion version);
|
||||
std::shared_ptr<ServerState> state);
|
||||
virtual ~ProxyServer() = default;
|
||||
|
||||
void listen(int port);
|
||||
void listen(uint16_t port, GameVersion version);
|
||||
|
||||
void connect_client(struct bufferevent* bev, uint32_t server_ipv4_addr,
|
||||
uint16_t server_port);
|
||||
void connect_client(struct bufferevent* bev, uint16_t server_port);
|
||||
|
||||
void send_to_client(const std::string& data);
|
||||
void send_to_server(const std::string& data);
|
||||
struct LinkedSession {
|
||||
ProxyServer* server;
|
||||
|
||||
void set_save_quests(bool save_quests);
|
||||
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;
|
||||
uint16_t local_port;
|
||||
struct sockaddr_storage next_destination;
|
||||
GameVersion version;
|
||||
uint32_t sub_version;
|
||||
std::string character_name;
|
||||
|
||||
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;
|
||||
|
||||
struct SavingQuestFile {
|
||||
std::string basename;
|
||||
std::string output_filename;
|
||||
uint32_t remaining_bytes;
|
||||
std::unique_ptr<FILE, std::function<void(FILE*)>> f;
|
||||
|
||||
SavingQuestFile(
|
||||
const std::string& basename,
|
||||
const std::string& output_filename,
|
||||
uint32_t remaining_bytes);
|
||||
};
|
||||
bool save_quests;
|
||||
std::unordered_map<std::string, SavingQuestFile> saving_quest_files;
|
||||
|
||||
LinkedSession(
|
||||
ProxyServer* server,
|
||||
uint16_t local_port,
|
||||
GameVersion version,
|
||||
std::shared_ptr<const License> license,
|
||||
uint32_t dest_addr,
|
||||
uint16_t dest_port);
|
||||
|
||||
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,
|
||||
uint32_t sub_version,
|
||||
const std::string& character_name);
|
||||
|
||||
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);
|
||||
void on_client_input();
|
||||
void on_server_input();
|
||||
void on_stream_error(short events, bool is_server_stream);
|
||||
|
||||
void send_to_end(const std::string& data, bool to_server);
|
||||
|
||||
void disconnect();
|
||||
|
||||
bool is_open() const;
|
||||
};
|
||||
|
||||
std::shared_ptr<LinkedSession> get_session();
|
||||
void delete_session(uint32_t serial_number);
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::map<int, std::unique_ptr<struct evconnlistener, void(*)(struct evconnlistener*)>> listeners;
|
||||
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> client_bev;
|
||||
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> server_bev;
|
||||
struct sockaddr_storage next_destination;
|
||||
struct sockaddr_storage default_destination;
|
||||
int listen_port;
|
||||
GameVersion version;
|
||||
struct ListeningSocket {
|
||||
ProxyServer* server;
|
||||
|
||||
size_t header_size;
|
||||
PSOCommandHeader client_input_header;
|
||||
PSOCommandHeader server_input_header;
|
||||
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;
|
||||
int fd;
|
||||
uint16_t port;
|
||||
std::unique_ptr<struct evconnlistener, void(*)(struct evconnlistener*)> listener;
|
||||
GameVersion version;
|
||||
|
||||
struct SavingQuestFile {
|
||||
std::string basename;
|
||||
std::string output_filename;
|
||||
uint32_t remaining_bytes;
|
||||
std::unique_ptr<FILE, std::function<void(FILE*)>> f;
|
||||
|
||||
SavingQuestFile(
|
||||
const std::string& basename,
|
||||
const std::string& output_filename,
|
||||
uint32_t remaining_bytes);
|
||||
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);
|
||||
void on_listen_accept(struct sockaddr *address, int socklen);
|
||||
void on_listen_error();
|
||||
};
|
||||
bool save_quests;
|
||||
std::unordered_map<std::string, SavingQuestFile> saving_quest_files;
|
||||
|
||||
void send_to_end(const std::string& data, bool to_server);
|
||||
struct UnlinkedSession {
|
||||
ProxyServer* server;
|
||||
|
||||
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_server_input(struct bufferevent* bev, void* ctx);
|
||||
static void dispatch_on_server_error(struct bufferevent* bev, short events,
|
||||
void* ctx);
|
||||
std::unique_ptr<struct bufferevent, void(*)(struct bufferevent*)> bev;
|
||||
uint16_t local_port;
|
||||
GameVersion version;
|
||||
|
||||
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_server_input(struct bufferevent* bev);
|
||||
void on_server_error(struct bufferevent* bev, short events);
|
||||
std::shared_ptr<PSOEncryption> crypt_out;
|
||||
std::shared_ptr<PSOEncryption> crypt_in;
|
||||
|
||||
void on_client_connect(struct bufferevent* bev,
|
||||
uint32_t server_ipv4_addr = 0, uint16_t server_port = 0);
|
||||
UnlinkedSession(ProxyServer* server, struct bufferevent* bev, uint16_t port, GameVersion version);
|
||||
|
||||
size_t get_size_field(const PSOCommandHeader* header);
|
||||
size_t get_command_field(const PSOCommandHeader* header);
|
||||
void receive_and_process_commands();
|
||||
|
||||
void receive_and_process_commands(bool from_server);
|
||||
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);
|
||||
};
|
||||
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::map<int, std::shared_ptr<ListeningSocket>> listeners;
|
||||
std::unordered_map<struct bufferevent*, std::shared_ptr<UnlinkedSession>> bev_to_unlinked_session;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<LinkedSession>> serial_number_to_session;
|
||||
|
||||
void on_client_connect(struct bufferevent* bev, uint16_t port, GameVersion version);
|
||||
};
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
#include "ProxyShell.hh"
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
|
||||
ProxyShell::ProxyShell(std::shared_ptr<struct event_base> base,
|
||||
std::shared_ptr<ServerState> state,
|
||||
std::shared_ptr<ProxyServer> proxy_server) : Shell(base, state),
|
||||
proxy_server(proxy_server) { }
|
||||
|
||||
void ProxyShell::execute_command(const string& command) {
|
||||
// find the entry in the command table and run the command
|
||||
size_t command_end = skip_non_whitespace(command, 0);
|
||||
size_t args_begin = skip_whitespace(command, command_end);
|
||||
string command_name = command.substr(0, command_end);
|
||||
string command_args = command.substr(args_begin);
|
||||
|
||||
if (command_name == "exit") {
|
||||
throw exit_shell();
|
||||
|
||||
} else if (command_name == "help") {
|
||||
fprintf(stderr, "\
|
||||
Commands:\n\
|
||||
help\n\
|
||||
You\'re reading it now.\n\
|
||||
exit (or ctrl+d)\n\
|
||||
Shut down the proxy.\n\
|
||||
sc <data>\n\
|
||||
Send a command to the client.\n\
|
||||
ss <data>\n\
|
||||
Send a command to the server.\n\
|
||||
chat <text>\n\
|
||||
Send a chat message to the server.\n\
|
||||
dchat <data>\n\
|
||||
Send a chat message to the server with arbitrary data in it.\n\
|
||||
info-board <text>\n\
|
||||
Set your info board contents.\n\
|
||||
info-board-data <data>\n\
|
||||
Set your info board contents with arbitrary data.\n\
|
||||
marker <color-id>\n\
|
||||
Send a lobby marker message to the server.\n\
|
||||
event <event-id>\n\
|
||||
Send a lobby event update to yourself.\n\
|
||||
ship\n\
|
||||
Request the ship select menu from the server.\n\
|
||||
");
|
||||
|
||||
} else if ((command_name == "sc") || (command_name == "ss")) {
|
||||
bool to_client = (command_name[1] == 'c');
|
||||
string data = parse_data_string(command_args);
|
||||
if (data.size() & 3) {
|
||||
throw invalid_argument("data size is not a multiple of 4");
|
||||
}
|
||||
if (data.size() == 0) {
|
||||
throw invalid_argument("no data given");
|
||||
}
|
||||
uint16_t* size_field = reinterpret_cast<uint16_t*>(data.data() + 2);
|
||||
*size_field = data.size();
|
||||
|
||||
log(INFO, "%s (from proxy):", to_client ? "server" : "client");
|
||||
print_data(stderr, data);
|
||||
|
||||
if (to_client) {
|
||||
this->proxy_server->send_to_client(data);
|
||||
} else {
|
||||
this->proxy_server->send_to_server(data);
|
||||
}
|
||||
|
||||
} else if ((command_name == "chat") || (command_name == "dchat")) {
|
||||
string data(12, '\0');
|
||||
data[0] = 0x06;
|
||||
data.push_back('\x09');
|
||||
data.push_back('E');
|
||||
if (command_name == "dchat") {
|
||||
data += parse_data_string(command_args);
|
||||
} else {
|
||||
data += command_args;
|
||||
}
|
||||
data.push_back('\0');
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
uint16_t* size_field = reinterpret_cast<uint16_t*>(data.data() + 2);
|
||||
*size_field = data.size();
|
||||
|
||||
log(INFO, "Client (from proxy):");
|
||||
print_data(stderr, data);
|
||||
this->proxy_server->send_to_server(data);
|
||||
|
||||
} else if (command_name == "marker") {
|
||||
string data("\x89\x00\x04\x00", 4);
|
||||
data[1] = stod(command_args);
|
||||
|
||||
log(INFO, "Client (from proxy):");
|
||||
print_data(stderr, data);
|
||||
this->proxy_server->send_to_server(data);
|
||||
|
||||
} else if (command_name == "event") {
|
||||
string data("\xDA\x00\x04\x00", 4);
|
||||
data[1] = stod(command_args);
|
||||
|
||||
log(INFO, "Server (from proxy):");
|
||||
print_data(stderr, data);
|
||||
this->proxy_server->send_to_client(data);
|
||||
|
||||
} else if (command_name == "ship") {
|
||||
static const string data("\xA0\x00\x04\x00", 4);
|
||||
|
||||
log(INFO, "Server (from proxy):");
|
||||
print_data(stderr, data);
|
||||
this->proxy_server->send_to_server(data);
|
||||
|
||||
} else if ((command_name == "info-board") || (command_name == "info-board-data")) {
|
||||
string data(4, '\0');
|
||||
data[0] = 0xD9;
|
||||
if (command_name == "info-board-data") {
|
||||
data += parse_data_string(command_args);
|
||||
} else {
|
||||
data += command_args;
|
||||
}
|
||||
data.push_back('\0');
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
uint16_t* size_field = reinterpret_cast<uint16_t*>(data.data() + 2);
|
||||
*size_field = data.size();
|
||||
|
||||
log(INFO, "Client (from proxy):");
|
||||
print_data(stderr, data);
|
||||
this->proxy_server->send_to_server(data);
|
||||
|
||||
} else if (command_name == "set-save-quests") {
|
||||
if (command_args == "on") {
|
||||
this->proxy_server->set_save_quests(true);
|
||||
} else if (command_args == "off") {
|
||||
this->proxy_server->set_save_quests(false);
|
||||
} else {
|
||||
throw invalid_argument("argument must be \"on\" or \"off\"");
|
||||
}
|
||||
|
||||
} else {
|
||||
throw invalid_argument("unknown command; try \'help\'");
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include "Shell.hh"
|
||||
#include "ProxyServer.hh"
|
||||
|
||||
|
||||
|
||||
class ProxyShell : public Shell {
|
||||
public:
|
||||
ProxyShell(std::shared_ptr<struct event_base> base,
|
||||
std::shared_ptr<ServerState> state,
|
||||
std::shared_ptr<ProxyServer> proxy_server);
|
||||
virtual ~ProxyShell() = default;
|
||||
ProxyShell(const ProxyShell&) = delete;
|
||||
ProxyShell(ProxyShell&&) = delete;
|
||||
ProxyShell& operator=(const ProxyShell&) = delete;
|
||||
ProxyShell& operator=(ProxyShell&&) = delete;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<ProxyServer> proxy_server;
|
||||
|
||||
virtual void execute_command(const std::string& command);
|
||||
};
|
||||
+98
-109
@@ -1,10 +1,11 @@
|
||||
#include "SendCommands.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include "SendCommands.hh"
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "ChatCommands.hh"
|
||||
#include "ProxyServer.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -196,21 +198,10 @@ void process_disconnect(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
|
||||
void process_verify_license_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, uint16_t size, const void* data) { // DB
|
||||
struct Cmd {
|
||||
char unused[0x20];
|
||||
char serial_number[0x10];
|
||||
char access_key[0x10];
|
||||
char unused2[0x08];
|
||||
uint32_t sub_version;
|
||||
char unused3[0x60];
|
||||
char password[0x10];
|
||||
char unused4[0x20];
|
||||
};
|
||||
check_size(size, sizeof(Cmd));
|
||||
const auto* cmd = reinterpret_cast<const Cmd*>(data);
|
||||
check_size(size, sizeof(VerifyLicenseCommand_GC_DB));
|
||||
const auto* cmd = reinterpret_cast<const VerifyLicenseCommand_GC_DB*>(data);
|
||||
|
||||
uint32_t serial_number = 0;
|
||||
sscanf(cmd->serial_number, "%8" PRIX32, &serial_number);
|
||||
uint32_t serial_number = strtoul(cmd->serial_number, nullptr, 16);
|
||||
try {
|
||||
c->license = s->license_manager->verify_gc(serial_number, cmd->access_key,
|
||||
cmd->password);
|
||||
@@ -221,8 +212,10 @@ void process_verify_license_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else {
|
||||
c->license = LicenseManager::create_license_pc(serial_number,
|
||||
cmd->access_key, cmd->password);
|
||||
auto l = LicenseManager::create_license_gc(serial_number,
|
||||
cmd->access_key, cmd->password, true);
|
||||
s->license_manager->add(l);
|
||||
c->license = l;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,8 +233,7 @@ void process_login_a_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
check_size(size, sizeof(Cmd));
|
||||
const auto* cmd = reinterpret_cast<const Cmd*>(data);
|
||||
|
||||
uint32_t serial_number = 0;
|
||||
sscanf(cmd->serial_number, "%8" PRIX32, &serial_number);
|
||||
uint32_t serial_number = strtoul(cmd->serial_number, nullptr, 16);
|
||||
try {
|
||||
if (c->version == GameVersion::GC) {
|
||||
c->license = s->license_manager->verify_gc(serial_number, cmd->access_key,
|
||||
@@ -251,20 +243,14 @@ void process_login_a_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
nullptr);
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
if (!s->allow_unregistered_users) {
|
||||
u16string message = u"Login failed: " + decode_sjis(e.what());
|
||||
send_message_box(c, message.c_str());
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else {
|
||||
if (c->version == GameVersion::GC) {
|
||||
c->license = LicenseManager::create_license_gc(serial_number,
|
||||
cmd->access_key, nullptr);
|
||||
} else {
|
||||
c->license = LicenseManager::create_license_pc(serial_number,
|
||||
cmd->access_key, nullptr);
|
||||
}
|
||||
}
|
||||
// The client should have sent a different command containing the password
|
||||
// already, which should have created and added a temporary license. If no
|
||||
// license exists, disconnect the client even if unregistered clients are
|
||||
// allowed.
|
||||
u16string message = u"Login failed: " + decode_sjis(e.what());
|
||||
send_message_box(c, message.c_str());
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
}
|
||||
|
||||
send_command(c, 0x9C, 0x01);
|
||||
@@ -285,8 +271,7 @@ void process_login_c_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
c->flags |= flags_for_version(c->version, cmd->sub_version);
|
||||
|
||||
uint32_t serial_number = 0;
|
||||
sscanf(cmd->serial_number, "%8" PRIX32, &serial_number);
|
||||
uint32_t serial_number = strtoul(cmd->serial_number, nullptr, 16);
|
||||
try {
|
||||
if (c->version == GameVersion::GC) {
|
||||
c->license = s->license_manager->verify_gc(serial_number, cmd->access_key,
|
||||
@@ -302,13 +287,16 @@ void process_login_c_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else {
|
||||
shared_ptr<License> l;
|
||||
if (c->version == GameVersion::GC) {
|
||||
c->license = LicenseManager::create_license_gc(serial_number,
|
||||
cmd->access_key, cmd->password);
|
||||
l = LicenseManager::create_license_gc(serial_number,
|
||||
cmd->access_key, cmd->password, true);
|
||||
} else {
|
||||
c->license = LicenseManager::create_license_pc(serial_number,
|
||||
cmd->access_key, cmd->password);
|
||||
l = LicenseManager::create_license_pc(serial_number,
|
||||
cmd->access_key, cmd->password, true);
|
||||
}
|
||||
s->license_manager->add(l);
|
||||
c->license = l;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,28 +305,13 @@ void process_login_c_dc_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
|
||||
void process_login_d_e_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t, uint32_t, uint16_t size, const void* data) { // 9D 9E
|
||||
struct Cmd {
|
||||
char unused[0x10];
|
||||
uint8_t sub_version;
|
||||
uint8_t unused2[0x27];
|
||||
char serial_number[0x10];
|
||||
char access_key[0x10];
|
||||
char unused3[0x60];
|
||||
char name[0x10];
|
||||
// Note: there are 8 bytes at the end of cfg that are technically not
|
||||
// included in the client config on GC, but the field after it is
|
||||
// sufficiently large and unused anyway
|
||||
ClientConfig cfg;
|
||||
uint8_t unused4[0x5C];
|
||||
};
|
||||
// sometimes the unused bytes aren't sent?
|
||||
check_size(size, sizeof(Cmd) - 0x64, sizeof(Cmd));
|
||||
const auto* cmd = reinterpret_cast<const Cmd*>(data);
|
||||
check_size(size, sizeof(LoginCommand_GC_9E) - 0x64, sizeof(LoginCommand_GC_9E));
|
||||
const auto* cmd = reinterpret_cast<const LoginCommand_GC_9E*>(data);
|
||||
|
||||
c->flags |= flags_for_version(c->version, cmd->sub_version);
|
||||
|
||||
uint32_t serial_number = 0;
|
||||
sscanf(cmd->serial_number, "%8" PRIX32, &serial_number);
|
||||
uint32_t serial_number = strtoul(cmd->serial_number, nullptr, 16);
|
||||
try {
|
||||
if (c->version == GameVersion::GC) {
|
||||
c->license = s->license_manager->verify_gc(serial_number, cmd->access_key,
|
||||
@@ -348,20 +321,12 @@ void process_login_d_e_pc_gc(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
nullptr);
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
if (!s->allow_unregistered_users) {
|
||||
u16string message = u"Login failed: " + decode_sjis(e.what());
|
||||
send_message_box(c, message.c_str());
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
} else {
|
||||
if (c->version == GameVersion::GC) {
|
||||
c->license = LicenseManager::create_license_gc(serial_number,
|
||||
cmd->access_key, nullptr);
|
||||
} else {
|
||||
c->license = LicenseManager::create_license_pc(serial_number,
|
||||
cmd->access_key, nullptr);
|
||||
}
|
||||
}
|
||||
// See comment in 9A handler about why we do this even if unregistered users
|
||||
// are allowed on the server
|
||||
u16string message = u"Login failed: " + decode_sjis(e.what());
|
||||
send_message_box(c, message.c_str());
|
||||
c->should_disconnect = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -612,7 +577,13 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
send_ship_info(c, u"Go to the lobby.");
|
||||
break;
|
||||
case MAIN_MENU_INFORMATION:
|
||||
send_ship_info(c, u"View server information.");
|
||||
send_ship_info(c, u"View server\ninformation.");
|
||||
break;
|
||||
case MAIN_MENU_PROXY_DESTINATIONS:
|
||||
send_ship_info(c, u"Connect to another\nserver.");
|
||||
break;
|
||||
case MAIN_MENU_DOWNLOAD_QUESTS:
|
||||
send_ship_info(c, u"Download a quest.");
|
||||
break;
|
||||
case MAIN_MENU_DISCONNECT:
|
||||
send_ship_info(c, u"End your session.");
|
||||
@@ -636,6 +607,19 @@ void process_menu_item_info_request(shared_ptr<ServerState> s, shared_ptr<Client
|
||||
}
|
||||
break;
|
||||
|
||||
case PROXY_DESTINATIONS_MENU_ID:
|
||||
if (cmd->item_id == PROXY_DESTINATIONS_MENU_GO_BACK) {
|
||||
send_ship_info(c, u"Return to the\nmain menu.");
|
||||
} else {
|
||||
try {
|
||||
// we use item_id + 1 here because "go back" is the first item
|
||||
send_ship_info(c, s->proxy_destinations_menu.at(cmd->item_id + 1).description.c_str());
|
||||
} catch (const out_of_range&) {
|
||||
send_ship_info(c, u"$C6No such information exists.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case QUEST_MENU_ID: {
|
||||
if (!s->quest_index) {
|
||||
send_quest_info(c, u"$C6Quests are not available.");
|
||||
@@ -690,6 +674,11 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
c->flags |= ClientFlag::IN_INFORMATION_MENU;
|
||||
break;
|
||||
|
||||
case MAIN_MENU_PROXY_DESTINATIONS:
|
||||
send_menu(c, u"Proxy server", PROXY_DESTINATIONS_MENU_ID,
|
||||
s->proxy_destinations_menu, false);
|
||||
break;
|
||||
|
||||
case MAIN_MENU_DOWNLOAD_QUESTS:
|
||||
send_quest_menu(c, QUEST_FILTER_MENU_ID, quest_download_menu, true);
|
||||
break;
|
||||
@@ -720,6 +709,41 @@ void process_menu_selection(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
break;
|
||||
}
|
||||
|
||||
case PROXY_DESTINATIONS_MENU_ID: {
|
||||
if (cmd->item_id == PROXY_DESTINATIONS_MENU_GO_BACK) {
|
||||
send_menu(c, s->name.c_str(), MAIN_MENU_ID, s->main_menu, false);
|
||||
|
||||
} else {
|
||||
pair<string, uint16_t>* dest = nullptr;
|
||||
try {
|
||||
dest = &s->proxy_destinations.at(cmd->item_id);
|
||||
} catch (const out_of_range&) { }
|
||||
|
||||
if (!dest) {
|
||||
send_message_box(c, u"$C6No such destination exists.");
|
||||
c->should_disconnect = true;
|
||||
} else {
|
||||
// TODO: We can probably avoid using client config and reconnecting the
|
||||
// client here; it's likely we could build a way to just directly link
|
||||
// the client to the proxy server instead (would have to provide
|
||||
// license/char name/etc. for remote auth)
|
||||
|
||||
c->proxy_destination_address = resolve_ipv4(dest->first);
|
||||
c->proxy_destination_port = dest->second;
|
||||
send_update_client_config(c);
|
||||
|
||||
s->proxy_server->delete_session(c->license->serial_number);
|
||||
|
||||
static const vector<string> version_to_port_name({
|
||||
"dc-proxy", "pc-proxy", "", "gc-proxy", "bb-proxy"});
|
||||
const auto& port_name = version_to_port_name.at(static_cast<size_t>(c->version));
|
||||
send_reconnect(c, s->connect_address_for_client(c),
|
||||
s->named_port_configuration.at(port_name).port);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GAME_MENU_ID: {
|
||||
auto game = s->find_lobby(cmd->item_id);
|
||||
if (!game) {
|
||||
@@ -2219,43 +2243,8 @@ static process_command_t* handlers[6] = {
|
||||
|
||||
void process_command(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint16_t command, uint32_t flag, uint16_t size, const void* data) {
|
||||
// TODO: this is slow; make it better somehow
|
||||
{
|
||||
string name_token;
|
||||
if (c->player.disp.name[0]) {
|
||||
name_token = " from " + remove_language_marker(encode_sjis(c->player.disp.name));
|
||||
}
|
||||
log(INFO, "Received%s version=%d size=%04hX command=%04hX flag=%08X",
|
||||
name_token.c_str(), static_cast<int>(c->version), size, command, flag);
|
||||
|
||||
string data_to_print;
|
||||
if (c->version == GameVersion::BB) {
|
||||
data_to_print.resize(size + 8);
|
||||
PSOCommandHeaderBB* header = reinterpret_cast<PSOCommandHeaderBB*>(
|
||||
data_to_print.data());
|
||||
header->command = command;
|
||||
header->flag = flag;
|
||||
header->size = size + 8;
|
||||
memcpy(data_to_print.data() + 8, data, size);
|
||||
} else if (c->version == GameVersion::PC) {
|
||||
data_to_print.resize(size + 4);
|
||||
PSOCommandHeaderPC* header = reinterpret_cast<PSOCommandHeaderPC*>(
|
||||
data_to_print.data());
|
||||
header->command = command;
|
||||
header->flag = flag;
|
||||
header->size = size + 4;
|
||||
memcpy(data_to_print.data() + 4, data, size);
|
||||
} else { // DC/GC
|
||||
data_to_print.resize(size + 4);
|
||||
PSOCommandHeaderDCGC* header = reinterpret_cast<PSOCommandHeaderDCGC*>(
|
||||
data_to_print.data());
|
||||
header->command = command;
|
||||
header->flag = flag;
|
||||
header->size = size + 4;
|
||||
memcpy(data_to_print.data() + 4, data, size);
|
||||
}
|
||||
print_data(stderr, data_to_print);
|
||||
}
|
||||
string encoded_name = remove_language_marker(encode_sjis(c->player.disp.name));
|
||||
print_received_command(command, flag, data, size, c->version, encoded_name.c_str());
|
||||
|
||||
auto fn = handlers[static_cast<size_t>(c->version)][command & 0xFF];
|
||||
if (fn) {
|
||||
|
||||
@@ -5,6 +5,39 @@
|
||||
#include "ServerState.hh"
|
||||
|
||||
|
||||
|
||||
// These commands' structures are defined here because they're used by both the
|
||||
// game server and proxy server
|
||||
|
||||
struct VerifyLicenseCommand_GC_DB {
|
||||
char unused[0x20];
|
||||
char serial_number[0x10];
|
||||
char access_key[0x10];
|
||||
char unused2[0x08];
|
||||
uint32_t sub_version;
|
||||
char serial_number2[0x30];
|
||||
char access_key2[0x30];
|
||||
char password[0x30];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct LoginCommand_GC_9E {
|
||||
char unused[0x10]; // 00 00 FF FF FF FF FF FF 00 00 00 00 00 00 00 00
|
||||
uint32_t sub_version;
|
||||
uint8_t unused2[0x24]; // 00 01 00 00 ... (rest is 00)
|
||||
char serial_number[0x10];
|
||||
char access_key[0x10];
|
||||
char serial_number2[0x30];
|
||||
char access_key2[0x30];
|
||||
char name[0x10];
|
||||
// Note: there are 8 bytes at the end of cfg that are technically not
|
||||
// included in the client config on GC, but the field after it is
|
||||
// sufficiently large and unused anyway
|
||||
ClientConfig cfg;
|
||||
uint8_t unused4[0x5C];
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
|
||||
void process_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
|
||||
void process_disconnect(std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Client> c);
|
||||
|
||||
+37
-18
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <event2/buffer.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
@@ -24,11 +25,18 @@ static FileContentsCache file_cache;
|
||||
|
||||
|
||||
|
||||
void send_command(shared_ptr<Client> c, uint16_t command, uint32_t flag,
|
||||
const void* data, size_t size) {
|
||||
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;
|
||||
|
||||
switch (c->version) {
|
||||
switch (version) {
|
||||
case GameVersion::GC:
|
||||
case GameVersion::DC: {
|
||||
PSOCommandHeaderDCGC header;
|
||||
@@ -74,15 +82,32 @@ void send_command(shared_ptr<Client> c, uint16_t command, uint32_t flag,
|
||||
throw logic_error("unimplemented game version in send_command");
|
||||
}
|
||||
|
||||
string name_token;
|
||||
if (c->player.disp.name[0]) {
|
||||
name_token = " to " + remove_language_marker(encode_sjis(c->player.disp.name));
|
||||
if (name_str) {
|
||||
string name_token;
|
||||
if (name_str[0]) {
|
||||
name_token = string(" to ") + name_str;
|
||||
}
|
||||
log(INFO, "Sending%s (version=%d command=%04hX flag=%08X)",
|
||||
name_token.c_str(), static_cast<int>(version), command, flag);
|
||||
print_data(stderr, send_data.data(), send_data.size());
|
||||
}
|
||||
log(INFO, "Sending%s version=%d size=%04zX command=%04hX flag=%08X",
|
||||
name_token.c_str(), static_cast<int>(c->version), size, command, flag);
|
||||
print_data(stderr, send_data.data(), send_data.size());
|
||||
|
||||
c->send(move(send_data));
|
||||
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 = remove_language_marker(encode_sjis(c->player.disp.name));
|
||||
send_command(c->bev, c->version, c->crypt_out.get(), command, flag, data,
|
||||
size, encoded_name.c_str());
|
||||
}
|
||||
|
||||
void send_command_excluding_client(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
@@ -132,14 +157,8 @@ string prepare_server_init_contents_dc_pc_gc(
|
||||
bool initial_connection,
|
||||
uint32_t server_key,
|
||||
uint32_t client_key) {
|
||||
struct Command {
|
||||
char copyright[0x40];
|
||||
uint32_t server_key;
|
||||
uint32_t client_key;
|
||||
char after_message[200];
|
||||
};
|
||||
string ret(sizeof(Command), '\0');
|
||||
auto* cmd = reinterpret_cast<Command*>(ret.data());
|
||||
string ret(sizeof(ServerInitCommand_GC_02_17), '\0');
|
||||
auto* cmd = reinterpret_cast<ServerInitCommand_GC_02_17*>(ret.data());
|
||||
|
||||
strcpy(cmd->copyright, initial_connection ? dc_port_map_copyright : dc_lobby_server_copyright);
|
||||
cmd->server_key = server_key;
|
||||
|
||||
+9
-14
@@ -16,20 +16,9 @@
|
||||
|
||||
|
||||
|
||||
#define MAIN_MENU_ID 0x60000000
|
||||
#define INFORMATION_MENU_ID 0x60000030
|
||||
#define LOBBY_MENU_ID 0x60000060
|
||||
#define GAME_MENU_ID 0x60000090
|
||||
#define QUEST_MENU_ID 0x600000C0
|
||||
#define QUEST_FILTER_MENU_ID 0x600000F0
|
||||
|
||||
#define MAIN_MENU_GO_TO_LOBBY 0x00000001
|
||||
#define MAIN_MENU_INFORMATION 0x00000002
|
||||
#define MAIN_MENU_DOWNLOAD_QUESTS 0x00000003
|
||||
#define MAIN_MENU_DISCONNECT 0x00000004
|
||||
#define INFORMATION_MENU_GO_BACK 0xFFFFFFFF
|
||||
|
||||
|
||||
void send_command(struct bufferevent* bev, GameVersion version,
|
||||
PSOEncryption* crypt, uint16_t command, uint32_t flag = 0,
|
||||
const void* data = nullptr, size_t size = 0, const char* name_str = nullptr);
|
||||
|
||||
void send_command(std::shared_ptr<Client> c, uint16_t command,
|
||||
uint32_t flag = 0, const void* data = nullptr, size_t size = 0);
|
||||
@@ -73,6 +62,12 @@ void send_command(std::shared_ptr<TARGET> c, uint16_t command, uint32_t flag,
|
||||
|
||||
|
||||
|
||||
struct ServerInitCommand_GC_02_17 {
|
||||
char copyright[0x40];
|
||||
uint32_t server_key;
|
||||
uint32_t client_key;
|
||||
char after_message[200];
|
||||
} __attribute__((packed));
|
||||
|
||||
std::string prepare_server_init_contents_dc_pc_gc(
|
||||
bool initial_connection, uint32_t server_key, uint32_t client_key);
|
||||
|
||||
+129
-3
@@ -14,14 +14,23 @@ using namespace std;
|
||||
|
||||
|
||||
|
||||
ServerShell::ServerShell(std::shared_ptr<struct event_base> base,
|
||||
std::shared_ptr<ServerState> state) : Shell(base, state) { }
|
||||
ServerShell::ServerShell(
|
||||
shared_ptr<struct event_base> base,
|
||||
shared_ptr<ServerState> state)
|
||||
: Shell(base, state) { }
|
||||
|
||||
void ServerShell::print_prompt() {
|
||||
fwrite("newserv> ", 9, 1, stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
shared_ptr<ProxyServer::LinkedSession> ServerShell::get_proxy_session() {
|
||||
if (!this->state->proxy_server.get()) {
|
||||
throw runtime_error("the proxy server is disabled");
|
||||
}
|
||||
return this->state->proxy_server->get_session();
|
||||
}
|
||||
|
||||
void ServerShell::execute_command(const string& command) {
|
||||
// find the entry in the command table and run the command
|
||||
size_t command_end = skip_non_whitespace(command, 0);
|
||||
@@ -34,9 +43,11 @@ void ServerShell::execute_command(const string& command) {
|
||||
|
||||
} else if (command_name == "help") {
|
||||
fprintf(stderr, "\
|
||||
Commands:\n\
|
||||
General commands:\n\
|
||||
help\n\
|
||||
You\'re reading it now.\n\
|
||||
\n\
|
||||
Server commands:\n\
|
||||
exit (or ctrl+d)\n\
|
||||
Shut down the server.\n\
|
||||
reload <item> ...\n\
|
||||
@@ -66,8 +77,32 @@ Commands:\n\
|
||||
Song IDs are 0 through 51; the default song is -1.\n\
|
||||
announce <message>\n\
|
||||
Send an announcement message to all players.\n\
|
||||
\n\
|
||||
Proxy commands (these will only work when exactly one client is connected):\n\
|
||||
sc <data>\n\
|
||||
Send a command to the client.\n\
|
||||
ss <data>\n\
|
||||
Send a command to the server.\n\
|
||||
chat <text>\n\
|
||||
Send a chat message to the server.\n\
|
||||
dchat <data>\n\
|
||||
Send a chat message to the server with arbitrary data in it.\n\
|
||||
info-board <text>\n\
|
||||
Set your info board contents.\n\
|
||||
info-board-data <data>\n\
|
||||
Set your info board contents with arbitrary data.\n\
|
||||
marker <color-id>\n\
|
||||
Send a lobby marker message to the server.\n\
|
||||
event <event-id>\n\
|
||||
Send a lobby event update to yourself.\n\
|
||||
ship\n\
|
||||
Request the ship select menu from the server.\n\
|
||||
");
|
||||
|
||||
|
||||
|
||||
// SERVER COMMANDS
|
||||
|
||||
} else if (command_name == "reload") {
|
||||
auto types = split(command_args, ' ');
|
||||
if (types.empty()) {
|
||||
@@ -189,6 +224,97 @@ Commands:\n\
|
||||
u16string message16 = decode_sjis(command_args);
|
||||
send_text_message(this->state, message16.c_str());
|
||||
|
||||
|
||||
|
||||
// PROXY COMMANDS
|
||||
|
||||
} else if ((command_name == "sc") || (command_name == "ss")) {
|
||||
auto session = this->get_proxy_session();
|
||||
|
||||
bool to_server = (command_name[1] == 's');
|
||||
string data = parse_data_string(command_args);
|
||||
if (data.size() & 3) {
|
||||
throw invalid_argument("data size is not a multiple of 4");
|
||||
}
|
||||
if (data.size() == 0) {
|
||||
throw invalid_argument("no data given");
|
||||
}
|
||||
uint16_t* size_field = reinterpret_cast<uint16_t*>(data.data() + 2);
|
||||
*size_field = data.size();
|
||||
|
||||
session->send_to_end(data, to_server);
|
||||
|
||||
} else if ((command_name == "chat") || (command_name == "dchat")) {
|
||||
auto session = this->get_proxy_session();
|
||||
|
||||
string data(12, '\0');
|
||||
data[0] = 0x06;
|
||||
data.push_back('\x09');
|
||||
data.push_back('E');
|
||||
if (command_name == "dchat") {
|
||||
data += parse_data_string(command_args);
|
||||
} else {
|
||||
data += command_args;
|
||||
}
|
||||
data.push_back('\0');
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
uint16_t* size_field = reinterpret_cast<uint16_t*>(data.data() + 2);
|
||||
*size_field = data.size();
|
||||
|
||||
session->send_to_end(data, true);
|
||||
|
||||
} else if (command_name == "marker") {
|
||||
auto session = this->get_proxy_session();
|
||||
|
||||
string data("\x89\x00\x04\x00", 4);
|
||||
data[1] = stod(command_args);
|
||||
|
||||
session->send_to_end(data, true);
|
||||
|
||||
} else if (command_name == "event") {
|
||||
auto session = this->get_proxy_session();
|
||||
|
||||
string data("\xDA\x00\x04\x00", 4);
|
||||
data[1] = stod(command_args);
|
||||
|
||||
session->send_to_end(data, false);
|
||||
|
||||
} else if (command_name == "ship") {
|
||||
auto session = this->get_proxy_session();
|
||||
|
||||
static const string data("\xA0\x00\x04\x00", 4);
|
||||
session->send_to_end(data, true);
|
||||
|
||||
} else if ((command_name == "info-board") || (command_name == "info-board-data")) {
|
||||
auto session = this->get_proxy_session();
|
||||
|
||||
string data(4, '\0');
|
||||
data[0] = 0xD9;
|
||||
if (command_name == "info-board-data") {
|
||||
data += parse_data_string(command_args);
|
||||
} else {
|
||||
data += command_args;
|
||||
}
|
||||
data.push_back('\0');
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
uint16_t* size_field = reinterpret_cast<uint16_t*>(data.data() + 2);
|
||||
*size_field = data.size();
|
||||
|
||||
session->send_to_end(data, true);
|
||||
|
||||
} else if (command_name == "set-save-quests") {
|
||||
auto session = this->get_proxy_session();
|
||||
|
||||
if (command_args == "on") {
|
||||
session->save_quests = true;
|
||||
} else if (command_args == "off") {
|
||||
session->save_quests = false;
|
||||
} else {
|
||||
throw invalid_argument("argument must be \"on\" or \"off\"");
|
||||
}
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
throw invalid_argument("unknown command; try \'help\'");
|
||||
}
|
||||
|
||||
+5
-1
@@ -6,12 +6,14 @@
|
||||
#include <event2/event.h>
|
||||
|
||||
#include "Shell.hh"
|
||||
#include "ProxyServer.hh"
|
||||
|
||||
|
||||
|
||||
class ServerShell : public Shell {
|
||||
public:
|
||||
ServerShell(std::shared_ptr<struct event_base> base,
|
||||
ServerShell(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
std::shared_ptr<ServerState> state);
|
||||
virtual ~ServerShell() = default;
|
||||
ServerShell(const ServerShell&) = delete;
|
||||
@@ -20,6 +22,8 @@ public:
|
||||
ServerShell& operator=(ServerShell&&) = delete;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<ProxyServer::LinkedSession> get_proxy_session();
|
||||
|
||||
virtual void print_prompt();
|
||||
virtual void execute_command(const std::string& command);
|
||||
};
|
||||
|
||||
@@ -22,15 +22,6 @@ ServerState::ServerState()
|
||||
ep3_menu_song(-1) {
|
||||
memset(&this->default_key_file, 0, sizeof(this->default_key_file));
|
||||
|
||||
this->main_menu.emplace_back(MAIN_MENU_GO_TO_LOBBY, u"Go to lobby",
|
||||
u"Join the lobby.", 0);
|
||||
this->main_menu.emplace_back(MAIN_MENU_INFORMATION, u"Information",
|
||||
u"View server information.", MenuItemFlag::REQUIRES_MESSAGE_BOXES);
|
||||
this->main_menu.emplace_back(MAIN_MENU_DOWNLOAD_QUESTS, u"Download quests",
|
||||
u"Download quests.", 0);
|
||||
this->main_menu.emplace_back(MAIN_MENU_DISCONNECT, u"Disconnect",
|
||||
u"Disconnect.", 0);
|
||||
|
||||
for (size_t x = 0; x < 20; x++) {
|
||||
auto lobby_name = decode_sjis(string_printf("LOBBY%zu", x + 1));
|
||||
shared_ptr<Lobby> l(new Lobby());
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
|
||||
|
||||
|
||||
// Forwawrd declaration due to reference cycle
|
||||
class ProxyServer;
|
||||
|
||||
struct PortConfiguration {
|
||||
uint16_t port;
|
||||
GameVersion version;
|
||||
@@ -51,6 +54,8 @@ struct ServerState {
|
||||
std::vector<MenuItem> main_menu;
|
||||
std::shared_ptr<std::vector<MenuItem>> information_menu;
|
||||
std::shared_ptr<std::vector<std::u16string>> information_contents;
|
||||
std::vector<MenuItem> proxy_destinations_menu;
|
||||
std::vector<std::pair<std::string, uint16_t>> proxy_destinations;
|
||||
std::u16string welcome_message;
|
||||
|
||||
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
|
||||
@@ -62,6 +67,10 @@ struct ServerState {
|
||||
uint32_t local_address;
|
||||
uint32_t external_address;
|
||||
|
||||
// TODO: This is only here because the menu selection handler has to call
|
||||
// delete_session on it. Find a cleaner way to do this.
|
||||
std::shared_ptr<ProxyServer> proxy_server;
|
||||
|
||||
ServerState();
|
||||
|
||||
void add_client_to_available_lobby(std::shared_ptr<Client> c);
|
||||
|
||||
@@ -30,6 +30,13 @@
|
||||
// simulator. This is generally only useful for finding bugs in the interface.
|
||||
// "IPStackDebug": true,
|
||||
|
||||
// Other servers to support proxying to. If this is empty, the proxy server is
|
||||
// disabled.
|
||||
"ProxyDestinations": {
|
||||
// "name (appears in menu)": "netloc"
|
||||
"Schtserv": "149.56.167.128:9103",
|
||||
},
|
||||
|
||||
// By default, the interactive shell runs if stdin is a terminal, and doesn't
|
||||
// run if it's not. This option, if present, overrides that behavior.
|
||||
// "RunInteractiveShell": false,
|
||||
|
||||
Reference in New Issue
Block a user