add parent pointers to eliminate code duplication in many places
This commit is contained in:
+251
-258
File diff suppressed because it is too large
Load Diff
+2
-4
@@ -10,7 +10,5 @@
|
||||
#include "ProxyServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_chat_command(std::shared_ptr<ServerState> s, std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<Client> c, const std::u16string& text);
|
||||
void on_chat_command(std::shared_ptr<ServerState> s,
|
||||
ProxyServer::LinkedSession& session, const std::u16string& text);
|
||||
void on_chat_command(std::shared_ptr<Client> c, const std::u16string& text);
|
||||
void on_chat_command(shared_ptr<ProxyServer::LinkedSession> ses, const std::u16string& text);
|
||||
|
||||
+66
-2
@@ -12,6 +12,7 @@
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "Server.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
using namespace std;
|
||||
@@ -42,10 +43,12 @@ ClientOptions::ClientOptions()
|
||||
function_call_return_value(-1) {}
|
||||
|
||||
Client::Client(
|
||||
shared_ptr<Server> server,
|
||||
struct bufferevent* bev,
|
||||
GameVersion version,
|
||||
ServerBehavior server_behavior)
|
||||
: id(next_id++),
|
||||
: server(server),
|
||||
id(next_id++),
|
||||
log(string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level),
|
||||
bb_game_state(0),
|
||||
flags(flags_for_version(version, -1)),
|
||||
@@ -60,7 +63,6 @@ Client::Client(
|
||||
x(0.0f),
|
||||
z(0.0f),
|
||||
area(0),
|
||||
lobby_id(0),
|
||||
lobby_client_id(0),
|
||||
lobby_arrow_color(0),
|
||||
preferred_lobby_id(-1),
|
||||
@@ -69,6 +71,16 @@ Client::Client(
|
||||
bufferevent_get_base(bev), -1, EV_TIMEOUT | EV_PERSIST,
|
||||
&Client::dispatch_save_game_data, this),
|
||||
event_free),
|
||||
send_ping_event(
|
||||
event_new(
|
||||
bufferevent_get_base(bev), -1, EV_TIMEOUT | EV_PERSIST,
|
||||
&Client::dispatch_send_ping, this),
|
||||
event_free),
|
||||
idle_timeout_event(
|
||||
event_new(
|
||||
bufferevent_get_base(bev), -1, EV_TIMEOUT | EV_PERSIST,
|
||||
&Client::dispatch_idle_timeout, this),
|
||||
event_free),
|
||||
card_battle_table_number(-1),
|
||||
card_battle_table_seat_number(0),
|
||||
card_battle_table_seat_state(0),
|
||||
@@ -83,6 +95,7 @@ Client::Client(
|
||||
struct timeval tv = usecs_to_timeval(60000000); // 1 minute
|
||||
event_add(this->save_game_data_event.get(), &tv);
|
||||
}
|
||||
this->reschedule_ping_and_timeout_events();
|
||||
|
||||
this->log.info("Created");
|
||||
}
|
||||
@@ -98,6 +111,13 @@ Client::~Client() {
|
||||
this->log.info("Deleted");
|
||||
}
|
||||
|
||||
void Client::reschedule_ping_and_timeout_events() {
|
||||
struct timeval ping_tv = usecs_to_timeval(30000000); // 30 seconds
|
||||
event_add(this->send_ping_event.get(), &ping_tv);
|
||||
struct timeval idle_tv = usecs_to_timeval(60000000); // 1 minute
|
||||
event_add(this->idle_timeout_event.get(), &idle_tv);
|
||||
}
|
||||
|
||||
QuestScriptVersion Client::quest_version() const {
|
||||
switch (this->version()) {
|
||||
case GameVersion::DC:
|
||||
@@ -135,6 +155,22 @@ void Client::set_license(shared_ptr<const License> l) {
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<ServerState> Client::require_server_state() const {
|
||||
auto server = this->server.lock();
|
||||
if (!server) {
|
||||
throw logic_error("server is deleted");
|
||||
}
|
||||
return server->get_state();
|
||||
}
|
||||
|
||||
shared_ptr<Lobby> Client::require_lobby() const {
|
||||
auto l = this->lobby.lock();
|
||||
if (!l) {
|
||||
throw runtime_error("client not in any lobby");
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
ClientConfig Client::export_config() const {
|
||||
ClientConfig cc;
|
||||
cc.magic = CLIENT_CONFIG_MAGIC;
|
||||
@@ -186,3 +222,31 @@ void Client::save_game_data() {
|
||||
this->game_data.save_player_data();
|
||||
}
|
||||
}
|
||||
|
||||
void Client::dispatch_send_ping(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<Client*>(ctx)->send_ping();
|
||||
}
|
||||
|
||||
void Client::send_ping() {
|
||||
if (this->version() == GameVersion::PATCH) {
|
||||
throw logic_error("send_ping called for patch client");
|
||||
}
|
||||
this->log.info("Sending ping command");
|
||||
// The game doesn't use this timestamp; we only use it for debugging purposes
|
||||
be_uint64_t timestamp = now();
|
||||
this->channel.send(0x1D, 0x00, ×tamp, sizeof(be_uint64_t));
|
||||
}
|
||||
|
||||
void Client::dispatch_idle_timeout(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<Client*>(ctx)->idle_timeout();
|
||||
}
|
||||
|
||||
void Client::idle_timeout() {
|
||||
this->log.info("Idle timeout expired");
|
||||
auto s = this->server.lock();
|
||||
if (s) {
|
||||
s->disconnect_client(this->shared_from_this());
|
||||
} else {
|
||||
this->log.info("Server is deleted; cannot disconnect client");
|
||||
}
|
||||
}
|
||||
|
||||
+26
-6
@@ -20,6 +20,9 @@
|
||||
|
||||
extern const uint64_t CLIENT_CONFIG_MAGIC;
|
||||
|
||||
class Server;
|
||||
struct Lobby;
|
||||
|
||||
struct ClientOptions {
|
||||
// Options used on both game and proxy server
|
||||
bool switch_assist;
|
||||
@@ -47,7 +50,7 @@ struct ClientOptions {
|
||||
ClientOptions();
|
||||
};
|
||||
|
||||
struct Client {
|
||||
struct Client : public std::enable_shared_from_this<Client> {
|
||||
enum Flag {
|
||||
// Client is DC Network Trial Edition, which is missing a lot of features
|
||||
// and uses some different command numbers than any other version
|
||||
@@ -113,6 +116,8 @@ struct Client {
|
||||
UNUSED_FLAG_BITS = 0xFF010000,
|
||||
};
|
||||
|
||||
std::weak_ptr<Server> server;
|
||||
std::weak_ptr<ServerState> server_state;
|
||||
uint64_t id;
|
||||
PrefixedLogger log;
|
||||
|
||||
@@ -144,13 +149,15 @@ struct Client {
|
||||
ClientOptions options;
|
||||
float x;
|
||||
float z;
|
||||
uint32_t area; // which area is the client in?
|
||||
uint32_t lobby_id; // which lobby is this person in?
|
||||
uint8_t lobby_client_id; // which client number is this person?
|
||||
uint8_t lobby_arrow_color; // lobby arrow color ID
|
||||
uint32_t area;
|
||||
std::weak_ptr<Lobby> lobby;
|
||||
uint8_t lobby_client_id;
|
||||
uint8_t lobby_arrow_color;
|
||||
int64_t preferred_lobby_id; // <0 = no preference
|
||||
ClientGameData game_data;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> save_game_data_event;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> send_ping_event;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
||||
int16_t card_battle_table_number;
|
||||
uint16_t card_battle_table_seat_number;
|
||||
uint16_t card_battle_table_seat_state;
|
||||
@@ -170,9 +177,15 @@ struct Client {
|
||||
std::shared_ptr<DOLFileIndex::DOLFile> loading_dol_file;
|
||||
std::unordered_map<std::string, std::shared_ptr<const std::string>> sending_files;
|
||||
|
||||
Client(struct bufferevent* bev, GameVersion version, ServerBehavior server_behavior);
|
||||
Client(
|
||||
shared_ptr<Server> server,
|
||||
struct bufferevent* bev,
|
||||
GameVersion version,
|
||||
ServerBehavior server_behavior);
|
||||
~Client();
|
||||
|
||||
void reschedule_ping_and_timeout_events();
|
||||
|
||||
inline GameVersion version() const {
|
||||
return this->channel.version;
|
||||
}
|
||||
@@ -180,6 +193,9 @@ struct Client {
|
||||
|
||||
void set_license(std::shared_ptr<const License> l);
|
||||
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
std::shared_ptr<Lobby> require_lobby() const;
|
||||
|
||||
ClientConfig export_config() const;
|
||||
ClientConfigBB export_config_bb() const;
|
||||
void import_config(const ClientConfig& cc);
|
||||
@@ -187,4 +203,8 @@ struct Client {
|
||||
|
||||
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
|
||||
void save_game_data();
|
||||
static void dispatch_send_ping(evutil_socket_t, short, void* ctx);
|
||||
void send_ping();
|
||||
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||
void idle_timeout();
|
||||
};
|
||||
|
||||
@@ -660,7 +660,7 @@ void Tournament::start() {
|
||||
}
|
||||
}
|
||||
|
||||
void Tournament::send_all_state_updates(shared_ptr<ServerState> s) const {
|
||||
void Tournament::send_all_state_updates() const {
|
||||
for (const auto& team : this->teams) {
|
||||
for (const auto& player : team->players) {
|
||||
auto c = player.client.lock();
|
||||
@@ -671,7 +671,7 @@ void Tournament::send_all_state_updates(shared_ptr<ServerState> s) const {
|
||||
(c->flags & Client::Flag::IS_EPISODE_3) &&
|
||||
!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) &&
|
||||
(c->ep3_tournament_team.lock() == team)) {
|
||||
send_ep3_confirm_tournament_entry(s, c, this->shared_from_this());
|
||||
send_ep3_confirm_tournament_entry(c, this->shared_from_this());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -685,7 +685,7 @@ void Tournament::send_all_state_updates_on_deletion() const {
|
||||
(c->flags & Client::Flag::IS_EPISODE_3) &&
|
||||
!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) &&
|
||||
(c->ep3_tournament_team.lock() == team)) {
|
||||
send_ep3_confirm_tournament_entry(nullptr, c, nullptr);
|
||||
send_ep3_confirm_tournament_entry(c, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -874,7 +874,7 @@ shared_ptr<Tournament::Team> TournamentIndex::team_for_serial_number(uint32_t se
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TournamentIndex::link_client(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
void TournamentIndex::link_client(shared_ptr<Client> c) {
|
||||
if (!(c->flags & Client::Flag::IS_EPISODE_3)) {
|
||||
return;
|
||||
}
|
||||
@@ -887,7 +887,7 @@ void TournamentIndex::link_client(shared_ptr<ServerState> s, shared_ptr<Client>
|
||||
c->ep3_tournament_team = team;
|
||||
player.client = c;
|
||||
if (!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) {
|
||||
send_ep3_confirm_tournament_entry(s, c, tourn);
|
||||
send_ep3_confirm_tournament_entry(c, tourn);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -896,14 +896,14 @@ void TournamentIndex::link_client(shared_ptr<ServerState> s, shared_ptr<Client>
|
||||
} else {
|
||||
c->ep3_tournament_team.reset();
|
||||
if (!(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) {
|
||||
send_ep3_confirm_tournament_entry(s, c, nullptr);
|
||||
send_ep3_confirm_tournament_entry(c, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TournamentIndex::link_all_clients(std::shared_ptr<ServerState> s) {
|
||||
for (const auto& c_it : s->channel_to_client) {
|
||||
this->link_client(s, c_it.second);
|
||||
this->link_client(c_it.second);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ public:
|
||||
|
||||
void start();
|
||||
|
||||
void send_all_state_updates(std::shared_ptr<ServerState> s) const;
|
||||
void send_all_state_updates() const;
|
||||
void send_all_state_updates_on_deletion() const;
|
||||
|
||||
void print_bracket(FILE* stream) const;
|
||||
@@ -232,7 +232,7 @@ public:
|
||||
|
||||
std::shared_ptr<Tournament::Team> team_for_serial_number(uint32_t serial_number) const;
|
||||
|
||||
void link_client(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
|
||||
void link_client(std::shared_ptr<Client> c);
|
||||
void link_all_clients(std::shared_ptr<ServerState> s);
|
||||
|
||||
private:
|
||||
|
||||
+9
-4
@@ -8,7 +8,9 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t item_index) {
|
||||
void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
// On PC (and presumably DC), the client sends a 6x29 after this to delete the
|
||||
// used item. On GC and later versions, this does not happen, so we should
|
||||
// delete the item here.
|
||||
@@ -152,8 +154,10 @@ void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t ite
|
||||
item.data.data1.clear_after(3);
|
||||
should_delete_item = false;
|
||||
|
||||
auto l = s->find_lobby(c->lobby_id);
|
||||
send_create_inventory_item(l, c, item.data);
|
||||
auto l = c->lobby.lock();
|
||||
if (l) {
|
||||
send_create_inventory_item(c, item.data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -218,7 +222,7 @@ void player_use_item(shared_ptr<ServerState> s, shared_ptr<Client> c, size_t ite
|
||||
}
|
||||
}
|
||||
|
||||
void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index) {
|
||||
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index) {
|
||||
static const unordered_map<uint32_t, size_t> result_index_for_fed_item({
|
||||
{0x030000, 0}, // Monomate
|
||||
{0x030001, 1}, // Dimate
|
||||
@@ -233,6 +237,7 @@ void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
|
||||
{0x030500, 10}, // Star Atomizer
|
||||
});
|
||||
|
||||
auto s = c->require_server_state();
|
||||
auto player = c->game_data.player();
|
||||
auto& fed_item = player->inventory.items[fed_item_index];
|
||||
auto& mag_item = player->inventory.items[mag_item_index];
|
||||
|
||||
+2
-2
@@ -9,5 +9,5 @@
|
||||
#include "ServerState.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
void player_use_item(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c, size_t item_index);
|
||||
void player_feed_mag(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
|
||||
void player_use_item(std::shared_ptr<Client> c, size_t item_index);
|
||||
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
|
||||
|
||||
+20
-8
@@ -10,8 +10,9 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
Lobby::Lobby(uint32_t id)
|
||||
: log(string_printf("[Lobby/%" PRIX32 "] ", id), lobby_log.min_level),
|
||||
Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id)
|
||||
: server_state(s),
|
||||
log(string_printf("[Lobby/%" PRIX32 "] ", id), lobby_log.min_level),
|
||||
lobby_id(id),
|
||||
min_level(0),
|
||||
max_level(0xFFFFFFFF),
|
||||
@@ -33,6 +34,14 @@ Lobby::Lobby(uint32_t id)
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<ServerState> Lobby::require_server_state() const {
|
||||
auto s = this->server_state.lock();
|
||||
if (!s) {
|
||||
throw logic_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void Lobby::reassign_leader_on_client_departure(size_t leaving_client_index) {
|
||||
for (size_t x = 0; x < this->max_clients; x++) {
|
||||
if (x == leaving_client_index) {
|
||||
@@ -102,7 +111,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
}
|
||||
|
||||
c->lobby_client_id = index;
|
||||
c->lobby_id = this->lobby_id;
|
||||
c->lobby = this->weak_from_this();
|
||||
|
||||
// If there's no one else in the lobby, set the leader id as well
|
||||
size_t leader_index;
|
||||
@@ -162,11 +171,14 @@ void Lobby::remove_client(shared_ptr<Client> c) {
|
||||
|
||||
this->clients[c->lobby_client_id] = nullptr;
|
||||
|
||||
// Unassign the client's lobby if it matches the current lobby's id (it may
|
||||
// not match if the client was already added to another lobby - this can
|
||||
// happen during the lobby change procedure)
|
||||
if (c->lobby_id == this->lobby_id) {
|
||||
c->lobby_id = 0;
|
||||
// Unassign the client's lobby if it matches the current lobby (it may not
|
||||
// match if the client was already added to another lobby - this can happen
|
||||
// during the lobby change procedure)
|
||||
{
|
||||
auto c_lobby = c->lobby.lock();
|
||||
if (c_lobby.get() == this) {
|
||||
c->lobby.reset();
|
||||
}
|
||||
}
|
||||
|
||||
this->reassign_leader_on_client_departure(c->lobby_client_id);
|
||||
|
||||
+10
-1
@@ -22,6 +22,8 @@
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
struct ServerState;
|
||||
|
||||
struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
enum Flag {
|
||||
GAME = 0x00000001,
|
||||
@@ -45,6 +47,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
DEFAULT = 0x02000000,
|
||||
};
|
||||
|
||||
std::weak_ptr<ServerState> server_state;
|
||||
PrefixedLogger log;
|
||||
|
||||
uint32_t lobby_id;
|
||||
@@ -110,7 +113,13 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
// Keys in this map are client_id
|
||||
std::unordered_map<size_t, std::weak_ptr<Client>> clients_to_add;
|
||||
|
||||
explicit Lobby(uint32_t id);
|
||||
Lobby(std::shared_ptr<ServerState> s, uint32_t id);
|
||||
Lobby(const Lobby&) = delete;
|
||||
Lobby(Lobby&&) = delete;
|
||||
Lobby& operator=(const Lobby&) = delete;
|
||||
Lobby& operator=(Lobby&&) = delete;
|
||||
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
|
||||
inline bool is_game() const {
|
||||
return this->flags & Flag::GAME;
|
||||
|
||||
+467
-514
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,7 @@
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_proxy_command(
|
||||
std::shared_ptr<ServerState> s,
|
||||
ProxyServer::LinkedSession& session,
|
||||
shared_ptr<ProxyServer::LinkedSession> ses,
|
||||
bool from_server,
|
||||
uint16_t command,
|
||||
uint32_t flag,
|
||||
|
||||
+117
-101
@@ -145,31 +145,31 @@ void ProxyServer::on_client_connect(
|
||||
this->next_unlicensed_session_id = 0xFF00000000000001;
|
||||
}
|
||||
|
||||
auto emplace_ret = this->id_to_session.emplace(session_id, new LinkedSession(this, session_id, listen_port, version, *default_destination));
|
||||
auto emplace_ret = this->id_to_session.emplace(session_id, new LinkedSession(this->shared_from_this(), session_id, listen_port, version, *default_destination));
|
||||
if (!emplace_ret.second) {
|
||||
throw logic_error("linked session already exists for unlicensed client");
|
||||
}
|
||||
auto session = emplace_ret.first->second;
|
||||
session->log.info("Opened linked session");
|
||||
auto ses = emplace_ret.first->second;
|
||||
ses->log.info("Opened linked session");
|
||||
|
||||
Channel ch(bev, version, nullptr, nullptr, session.get(), "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN);
|
||||
session->resume(std::move(ch));
|
||||
Channel ch(bev, version, nullptr, nullptr, ses.get(), "", TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN);
|
||||
ses->resume(std::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
|
||||
// client's config, which we'll get via a 9E command soon.
|
||||
} else {
|
||||
auto emplace_ret = this->bev_to_unlinked_session.emplace(bev, new UnlinkedSession(this, bev, listen_port, version));
|
||||
auto emplace_ret = this->bev_to_unlinked_session.emplace(bev, new UnlinkedSession(this->shared_from_this(), bev, listen_port, version));
|
||||
if (!emplace_ret.second) {
|
||||
throw logic_error("stale unlinked session exists");
|
||||
}
|
||||
auto session = emplace_ret.first->second;
|
||||
auto ses = emplace_ret.first->second;
|
||||
proxy_server_log.info("Opened unlinked session");
|
||||
|
||||
// Note that this should only be set when the linked session is created, not
|
||||
// when it is resumed!
|
||||
if (default_destination) {
|
||||
session->next_destination = *default_destination;
|
||||
ses->next_destination = *default_destination;
|
||||
}
|
||||
|
||||
switch (version) {
|
||||
@@ -183,13 +183,13 @@ void ProxyServer::on_client_connect(
|
||||
uint32_t client_key = random_object<uint32_t>();
|
||||
auto cmd = prepare_server_init_contents_console(
|
||||
server_key, client_key, 0);
|
||||
session->channel.send(0x02, 0x00, &cmd, sizeof(cmd));
|
||||
ses->channel.send(0x02, 0x00, &cmd, sizeof(cmd));
|
||||
if ((version == GameVersion::DC) || (version == GameVersion::PC)) {
|
||||
session->channel.crypt_out.reset(new PSOV2Encryption(server_key));
|
||||
session->channel.crypt_in.reset(new PSOV2Encryption(client_key));
|
||||
ses->channel.crypt_out.reset(new PSOV2Encryption(server_key));
|
||||
ses->channel.crypt_in.reset(new PSOV2Encryption(client_key));
|
||||
} else {
|
||||
session->channel.crypt_out.reset(new PSOV3Encryption(server_key));
|
||||
session->channel.crypt_in.reset(new PSOV3Encryption(client_key));
|
||||
ses->channel.crypt_out.reset(new PSOV3Encryption(server_key));
|
||||
ses->channel.crypt_in.reset(new PSOV3Encryption(client_key));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -199,15 +199,15 @@ 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, 0);
|
||||
session->channel.send(0x03, 0x00, &cmd, sizeof(cmd));
|
||||
session->detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption(
|
||||
ses->channel.send(0x03, 0x00, &cmd, sizeof(cmd));
|
||||
ses->detector_crypt.reset(new PSOBBMultiKeyDetectorEncryption(
|
||||
this->state->bb_private_keys,
|
||||
bb_crypt_initial_client_commands,
|
||||
cmd.basic_cmd.client_key.data(),
|
||||
sizeof(cmd.basic_cmd.client_key)));
|
||||
session->channel.crypt_in = session->detector_crypt;
|
||||
session->channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
|
||||
session->detector_crypt,
|
||||
ses->channel.crypt_in = ses->detector_crypt;
|
||||
ses->channel.crypt_out.reset(new PSOBBMultiKeyImitatorEncryption(
|
||||
ses->detector_crypt,
|
||||
cmd.basic_cmd.server_key.data(),
|
||||
sizeof(cmd.basic_cmd.server_key),
|
||||
true));
|
||||
@@ -220,7 +220,7 @@ void ProxyServer::on_client_connect(
|
||||
}
|
||||
|
||||
ProxyServer::UnlinkedSession::UnlinkedSession(
|
||||
ProxyServer* server,
|
||||
shared_ptr<ProxyServer> server,
|
||||
struct bufferevent* bev,
|
||||
uint16_t local_port,
|
||||
GameVersion version)
|
||||
@@ -240,8 +240,22 @@ ProxyServer::UnlinkedSession::UnlinkedSession(
|
||||
memset(&this->next_destination, 0, sizeof(this->next_destination));
|
||||
}
|
||||
|
||||
std::shared_ptr<ProxyServer> ProxyServer::UnlinkedSession::require_server() const {
|
||||
auto server = this->server.lock();
|
||||
if (!server) {
|
||||
throw logic_error("server is deleted");
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
std::shared_ptr<ServerState> ProxyServer::UnlinkedSession::require_server_state() const {
|
||||
return this->require_server()->state;
|
||||
}
|
||||
|
||||
void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint32_t, std::string& data) {
|
||||
auto* session = reinterpret_cast<UnlinkedSession*>(ch.context_obj);
|
||||
auto* ses = reinterpret_cast<UnlinkedSession*>(ch.context_obj);
|
||||
auto server = ses->require_server();
|
||||
auto s = server->state;
|
||||
|
||||
bool should_close_unlinked_session = false;
|
||||
shared_ptr<const License> license;
|
||||
@@ -253,12 +267,12 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
|
||||
string hardware_id;
|
||||
|
||||
try {
|
||||
if (session->version == GameVersion::DC) {
|
||||
if (ses->version == GameVersion::DC) {
|
||||
// We should only get a 93 or 9D while the session is unlinked; if we get
|
||||
// anything else, disconnect
|
||||
if (command == 0x93) {
|
||||
const auto& cmd = check_size_t<C_LoginV1_DC_93>(data);
|
||||
license = session->server->state->license_manager->verify_pc(
|
||||
license = s->license_manager->verify_pc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
language = cmd.language;
|
||||
@@ -267,7 +281,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
|
||||
client_config.cfg.flags |= Client::Flag::IS_DC_V1;
|
||||
} else if (command == 0x9D) {
|
||||
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_DC_GC_9D));
|
||||
license = session->server->state->license_manager->verify_pc(
|
||||
license = s->license_manager->verify_pc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
language = cmd.language;
|
||||
@@ -276,20 +290,20 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
|
||||
throw runtime_error("command is not 93 or 9D");
|
||||
}
|
||||
|
||||
} else if (session->version == GameVersion::PC) {
|
||||
} else if (ses->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_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_PC_9D));
|
||||
license = session->server->state->license_manager->verify_pc(
|
||||
license = s->license_manager->verify_pc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
language = cmd.language;
|
||||
character_name = cmd.name;
|
||||
|
||||
} else if (session->version == GameVersion::GC) {
|
||||
} else if (ses->version == GameVersion::GC) {
|
||||
// We should only get a 9E while the session is unlinked; if we get
|
||||
// anything else, disconnect
|
||||
// TODO: GCTE will send 9D; we should presumably handle that too, sigh
|
||||
@@ -297,17 +311,17 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
|
||||
throw runtime_error("command is not 9E");
|
||||
}
|
||||
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_LoginExtended_GC_9E));
|
||||
license = session->server->state->license_manager->verify_gc(
|
||||
license = s->license_manager->verify_gc(
|
||||
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
|
||||
sub_version = cmd.sub_version;
|
||||
language = cmd.language;
|
||||
character_name = cmd.name;
|
||||
client_config.cfg = cmd.client_config.cfg;
|
||||
|
||||
} else if (session->version == GameVersion::XB) {
|
||||
} else if (ses->version == GameVersion::XB) {
|
||||
throw runtime_error("xbox licenses are not implemented");
|
||||
|
||||
} else if (session->version == GameVersion::BB) {
|
||||
} else if (ses->version == GameVersion::BB) {
|
||||
// We should only get a 93 while the session is unlinked; if we get
|
||||
// anything else, disconnect
|
||||
if (command != 0x93) {
|
||||
@@ -315,15 +329,15 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
|
||||
}
|
||||
const auto& cmd = check_size_t<C_Login_BB_93>(data);
|
||||
try {
|
||||
license = session->server->state->license_manager->verify_bb(
|
||||
license = s->license_manager->verify_bb(
|
||||
cmd.username, cmd.password);
|
||||
} catch (const missing_license&) {
|
||||
if (!session->server->state->allow_unregistered_users) {
|
||||
if (!s->allow_unregistered_users) {
|
||||
throw;
|
||||
}
|
||||
shared_ptr<License> l = LicenseManager::create_license_bb(
|
||||
fnv1a32(cmd.username) & 0x7FFFFFFF, cmd.username, cmd.password, true);
|
||||
session->server->state->license_manager->add(l);
|
||||
s->license_manager->add(l);
|
||||
license = l;
|
||||
}
|
||||
login_command_bb = std::move(data);
|
||||
@@ -333,7 +347,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
session->log.error("Failed to process command from unlinked client: %s", e.what());
|
||||
ses->log.error("Failed to process command from unlinked client: %s", e.what());
|
||||
should_close_unlinked_session = true;
|
||||
}
|
||||
|
||||
@@ -350,94 +364,84 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
|
||||
should_close_unlinked_session = true;
|
||||
|
||||
// Look up the linked session for this license (if any)
|
||||
shared_ptr<LinkedSession> linked_session;
|
||||
shared_ptr<LinkedSession> linked_ses;
|
||||
try {
|
||||
linked_session = session->server->id_to_session.at(license->serial_number);
|
||||
linked_session->log.info("Resuming linked session from unlinked session");
|
||||
linked_ses = server->id_to_session.at(license->serial_number);
|
||||
linked_ses->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) {
|
||||
linked_session.reset(new LinkedSession(
|
||||
session->server,
|
||||
session->local_port,
|
||||
session->version,
|
||||
license,
|
||||
client_config));
|
||||
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,
|
||||
session->next_destination));
|
||||
linked_session->log.info("Opened licensed session for unlinked session based on unlinked default destination");
|
||||
linked_ses.reset(new LinkedSession(
|
||||
server, ses->local_port, ses->version, license, client_config));
|
||||
linked_ses->log.info("Opened licensed session for unlinked session based on client config");
|
||||
} else if (ses->next_destination.ss_family == AF_INET) {
|
||||
linked_ses.reset(new LinkedSession(
|
||||
server, ses->local_port, ses->version, license, ses->next_destination));
|
||||
linked_ses->log.info("Opened licensed session for unlinked session based on unlinked default destination");
|
||||
} else {
|
||||
session->log.error("Cannot open linked session: no valid destination in client config or unlinked session");
|
||||
ses->log.error("Cannot open linked session: no valid destination in client config or unlinked session");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
if (linked_ses.get()) {
|
||||
server->id_to_session.emplace(license->serial_number, linked_ses);
|
||||
if (linked_ses->version != ses->version) {
|
||||
linked_ses->log.error("Linked session has different game version");
|
||||
} else {
|
||||
// Resume the linked session using the unlinked session
|
||||
try {
|
||||
if (session->version == GameVersion::BB) {
|
||||
linked_session->resume(
|
||||
std::move(session->channel),
|
||||
session->detector_crypt,
|
||||
if (ses->version == GameVersion::BB) {
|
||||
linked_ses->resume(
|
||||
std::move(ses->channel),
|
||||
ses->detector_crypt,
|
||||
std::move(login_command_bb));
|
||||
} else {
|
||||
linked_session->resume(
|
||||
std::move(session->channel),
|
||||
session->detector_crypt,
|
||||
linked_ses->resume(
|
||||
std::move(ses->channel),
|
||||
ses->detector_crypt,
|
||||
sub_version,
|
||||
language,
|
||||
character_name,
|
||||
hardware_id);
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
linked_session->log.error("Failed to resume linked session: %s", e.what());
|
||||
linked_ses->log.error("Failed to resume linked session: %s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (should_close_unlinked_session) {
|
||||
session->server->delete_session(session_key);
|
||||
server->delete_session(session_key);
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyServer::UnlinkedSession::on_error(Channel& ch, short events) {
|
||||
auto* session = reinterpret_cast<UnlinkedSession*>(ch.context_obj);
|
||||
auto* ses = reinterpret_cast<UnlinkedSession*>(ch.context_obj);
|
||||
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
session->log.warning("Error %d (%s) in unlinked client stream", err,
|
||||
ses->log.warning("Error %d (%s) in unlinked client stream", err,
|
||||
evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
|
||||
session->log.info("Client has disconnected");
|
||||
session->server->delete_session(session->channel.bev.get());
|
||||
ses->log.info("Client has disconnected");
|
||||
ses->require_server()->delete_session(ses->channel.bev.get());
|
||||
}
|
||||
}
|
||||
|
||||
ProxyServer::LinkedSession::LinkedSession(
|
||||
ProxyServer* server,
|
||||
shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
uint16_t local_port,
|
||||
GameVersion version)
|
||||
: server(server),
|
||||
id(id),
|
||||
log(string_printf("[ProxyServer:LinkedSession:%08" PRIX64 "] ", this->id), proxy_server_log.min_level),
|
||||
timeout_event(event_new(this->server->base.get(), -1, EV_TIMEOUT,
|
||||
&LinkedSession::dispatch_on_timeout, this),
|
||||
event_free),
|
||||
timeout_event(event_new(server->base.get(), -1, EV_TIMEOUT, &LinkedSession::dispatch_on_timeout, this), event_free),
|
||||
license(nullptr),
|
||||
client_channel(
|
||||
version,
|
||||
@@ -477,7 +481,7 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
}
|
||||
|
||||
ProxyServer::LinkedSession::LinkedSession(
|
||||
ProxyServer* server,
|
||||
shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
GameVersion version,
|
||||
shared_ptr<const License> license,
|
||||
@@ -493,7 +497,7 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
}
|
||||
|
||||
ProxyServer::LinkedSession::LinkedSession(
|
||||
ProxyServer* server,
|
||||
shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
GameVersion version,
|
||||
std::shared_ptr<const License> license,
|
||||
@@ -504,7 +508,7 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
}
|
||||
|
||||
ProxyServer::LinkedSession::LinkedSession(
|
||||
ProxyServer* server,
|
||||
shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
uint16_t local_port,
|
||||
GameVersion version,
|
||||
@@ -513,6 +517,18 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
this->next_destination = destination;
|
||||
}
|
||||
|
||||
shared_ptr<ProxyServer> ProxyServer::LinkedSession::require_server() const {
|
||||
auto server = this->server.lock();
|
||||
if (!server) {
|
||||
throw logic_error("server is deleted");
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
std::shared_ptr<ServerState> ProxyServer::LinkedSession::require_server_state() const {
|
||||
return this->require_server()->state;
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::resume(
|
||||
Channel&& client_channel,
|
||||
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
@@ -579,7 +595,7 @@ void ProxyServer::LinkedSession::connect() {
|
||||
this->log.info("Connecting to %s", netloc_str.c_str());
|
||||
|
||||
this->server_channel.set_bufferevent(bufferevent_socket_new(
|
||||
this->server->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS));
|
||||
this->require_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*>(dest_sin), sizeof(*dest_sin)) != 0) {
|
||||
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
@@ -617,38 +633,38 @@ void ProxyServer::LinkedSession::dispatch_on_timeout(
|
||||
}
|
||||
|
||||
void ProxyServer::LinkedSession::on_timeout() {
|
||||
this->server->delete_session(this->id);
|
||||
this->require_server()->delete_session(this->id);
|
||||
}
|
||||
|
||||
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);
|
||||
auto* ses = reinterpret_cast<LinkedSession*>(ch.context_obj);
|
||||
bool is_server_stream = (&ch == &ses->server_channel);
|
||||
|
||||
if (events & BEV_EVENT_CONNECTED) {
|
||||
session->log.info("%s channel connected", is_server_stream ? "Server" : "Client");
|
||||
ses->log.info("%s channel connected", is_server_stream ? "Server" : "Client");
|
||||
|
||||
if (is_server_stream && (session->options.override_lobby_event >= 0) &&
|
||||
(((session->version == GameVersion::GC) && !(session->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) ||
|
||||
(session->version == GameVersion::XB) ||
|
||||
(session->version == GameVersion::BB))) {
|
||||
session->client_channel.send(0xDA, session->options.override_lobby_event);
|
||||
if (is_server_stream && (ses->options.override_lobby_event >= 0) &&
|
||||
(((ses->version == GameVersion::GC) && !(ses->newserv_client_config.cfg.flags & Client::Flag::IS_GC_TRIAL_EDITION)) ||
|
||||
(ses->version == GameVersion::XB) ||
|
||||
(ses->version == GameVersion::BB))) {
|
||||
ses->client_channel.send(0xDA, ses->options.override_lobby_event);
|
||||
}
|
||||
}
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
session->log.warning("Error %d (%s) in %s stream",
|
||||
ses->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)) {
|
||||
session->log.info("%s has disconnected",
|
||||
ses->log.info("%s has disconnected",
|
||||
is_server_stream ? "Server" : "Client");
|
||||
// If the server disconnected, send the client back to the game server so
|
||||
// they're not disconnected completely.
|
||||
if (is_server_stream) {
|
||||
session->send_to_game_server("The server has\ndisconnected.");
|
||||
ses->send_to_game_server("The server has\ndisconnected.");
|
||||
}
|
||||
session->disconnect();
|
||||
ses->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,7 +699,8 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message)
|
||||
this->client_channel.send(this->is_in_game ? 0x66 : 0x69, leaving_id, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
string encoded_name = encode_sjis(this->server->state->name);
|
||||
auto s = this->require_server_state();
|
||||
string encoded_name = encode_sjis(s->name);
|
||||
if (this->is_in_game) {
|
||||
send_ship_info(this->client_channel, decode_sjis(string_printf("You cannot return\nto $C6%s$C7\nwhile in a game.\n\n%s", encoded_name.c_str(), error_message ? error_message : "")));
|
||||
this->disconnect();
|
||||
@@ -701,7 +718,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message)
|
||||
const auto& port_name = version_to_login_port_name.at(static_cast<size_t>(
|
||||
this->version));
|
||||
|
||||
S_Reconnect_19 reconnect_cmd = {{0, this->server->state->name_to_port_config.at(port_name)->port, 0}};
|
||||
S_Reconnect_19 reconnect_cmd = {{0, s->name_to_port_config.at(port_name)->port, 0}};
|
||||
|
||||
// If the client is on a virtual connection, we can use any address
|
||||
// here and they should be able to connect back to the game server. If
|
||||
@@ -760,25 +777,24 @@ bool ProxyServer::LinkedSession::is_connected() const {
|
||||
}
|
||||
|
||||
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);
|
||||
auto* ses = reinterpret_cast<LinkedSession*>(ch.context_obj);
|
||||
bool is_server_stream = (&ch == &ses->server_channel);
|
||||
|
||||
try {
|
||||
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);
|
||||
size_t bytes_to_save = min<size_t>(data.size(), sizeof(ses->prev_server_command_bytes));
|
||||
memcpy(ses->prev_server_command_bytes, data.data(), bytes_to_save);
|
||||
}
|
||||
on_proxy_command(
|
||||
session->server->state,
|
||||
*session,
|
||||
ses->shared_from_this(),
|
||||
is_server_stream,
|
||||
command,
|
||||
flag,
|
||||
data);
|
||||
} catch (const exception& e) {
|
||||
session->log.error("Failed to process command from %s: %s",
|
||||
ses->log.error("Failed to process command from %s: %s",
|
||||
is_server_stream ? "server" : "client", e.what());
|
||||
session->disconnect();
|
||||
ses->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -808,7 +824,7 @@ shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
|
||||
shared_ptr<const License> l, uint16_t local_port, GameVersion version,
|
||||
const ClientConfigBB& newserv_client_config) {
|
||||
shared_ptr<LinkedSession> session(new LinkedSession(
|
||||
this, local_port, version, l, newserv_client_config));
|
||||
this->shared_from_this(), local_port, version, l, newserv_client_config));
|
||||
auto emplace_ret = this->id_to_session.emplace(session->id, session);
|
||||
if (!emplace_ret.second) {
|
||||
throw runtime_error("session already exists for this license");
|
||||
|
||||
+18
-15
@@ -16,7 +16,7 @@
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class ProxyServer {
|
||||
class ProxyServer : public std::enable_shared_from_this<ProxyServer> {
|
||||
public:
|
||||
ProxyServer() = delete;
|
||||
ProxyServer(const ProxyServer&) = delete;
|
||||
@@ -31,8 +31,8 @@ public:
|
||||
|
||||
void connect_client(struct bufferevent* bev, uint16_t server_port);
|
||||
|
||||
struct LinkedSession {
|
||||
ProxyServer* server;
|
||||
struct LinkedSession : std::enable_shared_from_this<LinkedSession> {
|
||||
std::weak_ptr<ProxyServer> server;
|
||||
uint64_t id;
|
||||
PrefixedLogger log;
|
||||
|
||||
@@ -77,13 +77,10 @@ public:
|
||||
uint32_t next_item_id;
|
||||
|
||||
struct LobbyPlayer {
|
||||
uint32_t guild_card_number;
|
||||
uint32_t guild_card_number = 0;
|
||||
std::string name;
|
||||
uint8_t section_id;
|
||||
uint8_t char_class;
|
||||
LobbyPlayer() : guild_card_number(0),
|
||||
section_id(0),
|
||||
char_class(0) {}
|
||||
uint8_t section_id = 0;
|
||||
uint8_t char_class = 0;
|
||||
};
|
||||
std::vector<LobbyPlayer> lobby_players;
|
||||
size_t lobby_client_id;
|
||||
@@ -115,29 +112,32 @@ public:
|
||||
|
||||
// TODO: This first constructor should be private
|
||||
LinkedSession(
|
||||
ProxyServer* server,
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
uint16_t local_port,
|
||||
GameVersion version);
|
||||
LinkedSession(
|
||||
ProxyServer* server,
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
GameVersion version,
|
||||
std::shared_ptr<const License> license,
|
||||
const ClientConfigBB& newserv_client_config);
|
||||
LinkedSession(
|
||||
ProxyServer* server,
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
GameVersion version,
|
||||
std::shared_ptr<const License> license,
|
||||
const struct sockaddr_storage& next_destination);
|
||||
LinkedSession(
|
||||
ProxyServer* server,
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
uint16_t local_port,
|
||||
GameVersion version,
|
||||
const struct sockaddr_storage& next_destination);
|
||||
|
||||
std::shared_ptr<ProxyServer> require_server() const;
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
|
||||
void resume(
|
||||
Channel&& client_channel,
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
@@ -205,7 +205,7 @@ private:
|
||||
};
|
||||
|
||||
struct UnlinkedSession {
|
||||
ProxyServer* server;
|
||||
std::weak_ptr<ProxyServer> server;
|
||||
|
||||
PrefixedLogger log;
|
||||
Channel channel;
|
||||
@@ -215,7 +215,10 @@ private:
|
||||
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
|
||||
|
||||
UnlinkedSession(ProxyServer* server, struct bufferevent* bev, uint16_t port, GameVersion version);
|
||||
UnlinkedSession(std::shared_ptr<ProxyServer> server, struct bufferevent* bev, uint16_t port, GameVersion version);
|
||||
|
||||
std::shared_ptr<ProxyServer> require_server() const;
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
|
||||
void receive_and_process_commands();
|
||||
|
||||
|
||||
+304
-338
File diff suppressed because it is too large
Load Diff
@@ -16,10 +16,7 @@ std::shared_ptr<Lobby> create_game_generic(
|
||||
std::shared_ptr<Lobby> watched_lobby = nullptr,
|
||||
std::shared_ptr<Episode3::BattleRecordPlayer> battle_player = nullptr);
|
||||
|
||||
void on_connect(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
|
||||
void on_disconnect(std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Client> c);
|
||||
void on_command(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c,
|
||||
uint16_t command, uint32_t flag, const std::string& data);
|
||||
void on_command_with_header(std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Client> c, std::string& data);
|
||||
void on_connect(std::shared_ptr<Client> c);
|
||||
void on_disconnect(std::shared_ptr<Client> c);
|
||||
void on_command(std::shared_ptr<Client> c, uint16_t command, uint32_t flag, const std::string& data);
|
||||
void on_command_with_header(std::shared_ptr<Client> c, std::string& data);
|
||||
|
||||
+236
-296
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,9 @@
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_subcommand_multi(
|
||||
std::shared_ptr<ServerState> s, std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
||||
std::shared_ptr<Client> c,
|
||||
uint8_t command,
|
||||
uint8_t flag,
|
||||
const std::string& data);
|
||||
|
||||
bool subcommand_is_implemented(uint8_t which);
|
||||
|
||||
+62
-57
@@ -188,8 +188,7 @@ prepare_server_init_contents_bb(
|
||||
return cmd;
|
||||
}
|
||||
|
||||
void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
uint8_t flags) {
|
||||
void send_server_init_bb(shared_ptr<Client> c, uint8_t flags) {
|
||||
bool use_secondary_message = (flags & SendServerInitFlag::USE_SECONDARY_MESSAGE);
|
||||
parray<uint8_t, 0x30> server_key;
|
||||
parray<uint8_t, 0x30> client_key;
|
||||
@@ -201,7 +200,7 @@ void send_server_init_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
static const string primary_expected_first_data("\xB4\x00\x93\x00\x00\x00\x00\x00", 8);
|
||||
static const string secondary_expected_first_data("\xDC\x00\xDB\x00\x00\x00\x00\x00", 8);
|
||||
shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt(new PSOBBMultiKeyDetectorEncryption(
|
||||
s->bb_private_keys,
|
||||
c->require_server_state()->bb_private_keys,
|
||||
bb_crypt_initial_client_commands,
|
||||
cmd.basic_cmd.client_key.data(),
|
||||
sizeof(cmd.basic_cmd.client_key)));
|
||||
@@ -225,8 +224,7 @@ void send_server_init_patch(shared_ptr<Client> c) {
|
||||
c->channel.crypt_in.reset(new PSOV2Encryption(client_key));
|
||||
}
|
||||
|
||||
void send_server_init(
|
||||
shared_ptr<ServerState> s, shared_ptr<Client> c, uint8_t flags) {
|
||||
void send_server_init(shared_ptr<Client> c, uint8_t flags) {
|
||||
switch (c->version()) {
|
||||
case GameVersion::DC:
|
||||
case GameVersion::PC:
|
||||
@@ -238,7 +236,7 @@ void send_server_init(
|
||||
send_server_init_patch(c);
|
||||
break;
|
||||
case GameVersion::BB:
|
||||
send_server_init_bb(s, c, flags);
|
||||
send_server_init_bb(c, flags);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unimplemented versioned command");
|
||||
@@ -291,12 +289,11 @@ void send_quest_open_file_t(
|
||||
send_command_t(c, command_num, 0x00, cmd);
|
||||
}
|
||||
|
||||
void send_quest_buffer_overflow(
|
||||
shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
void send_quest_buffer_overflow(shared_ptr<Client> c) {
|
||||
// PSO Episode 3 USA doesn't natively support the B2 command, but we can add
|
||||
// it back to the game with some tricky commands. For details on how this
|
||||
// works, see system/ppc/Episode3USAQuestBufferOverflow.s.
|
||||
auto fn = s->function_code_index->name_to_function.at("Episode3USAQuestBufferOverflow");
|
||||
auto fn = c->require_server_state()->function_code_index->name_to_function.at("Episode3USAQuestBufferOverflow");
|
||||
if (fn->code.size() > 0x400) {
|
||||
throw runtime_error("Episode 3 buffer overflow code must be a single segment");
|
||||
}
|
||||
@@ -317,7 +314,9 @@ void send_quest_buffer_overflow(
|
||||
|
||||
void empty_function_call_response_handler(uint32_t, uint32_t) {}
|
||||
|
||||
void prepare_client_for_patches(shared_ptr<ServerState> s, shared_ptr<Client> c, std::function<void()> on_complete) {
|
||||
void prepare_client_for_patches(shared_ptr<Client> c, std::function<void()> on_complete) {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
auto send_version_detect = [s, wc = weak_ptr<Client>(c), on_complete]() -> void {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
@@ -326,7 +325,11 @@ void prepare_client_for_patches(shared_ptr<ServerState> s, shared_ptr<Client> c,
|
||||
if (c->version() == GameVersion::GC &&
|
||||
c->specific_version == default_specific_version_for_version(GameVersion::GC, -1)) {
|
||||
send_function_call(c, s->function_code_index->name_to_function.at("VersionDetect"));
|
||||
c->function_call_response_queue.emplace_back([s, c, on_complete](uint32_t specific_version, uint32_t) -> void {
|
||||
c->function_call_response_queue.emplace_back([wc = weak_ptr<Client>(c), on_complete](uint32_t specific_version, uint32_t) -> void {
|
||||
auto c = wc.lock();
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
c->specific_version = specific_version;
|
||||
c->log.info("Version detected as %08" PRIX32, c->specific_version);
|
||||
on_complete();
|
||||
@@ -911,8 +914,9 @@ void send_simple_mail(shared_ptr<Client> c, uint32_t from_guild_card_number,
|
||||
// info board
|
||||
|
||||
template <typename CharT>
|
||||
void send_info_board_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
void send_info_board_t(shared_ptr<Client> c) {
|
||||
vector<S_InfoBoardEntry_D8<CharT>> entries;
|
||||
auto l = c->require_lobby();
|
||||
for (const auto& c : l->clients) {
|
||||
if (!c.get()) {
|
||||
continue;
|
||||
@@ -925,22 +929,22 @@ void send_info_board_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
send_command_vt(c, 0xD8, entries.size(), entries);
|
||||
}
|
||||
|
||||
void send_info_board(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
void send_info_board(shared_ptr<Client> c) {
|
||||
if (c->version() == GameVersion::PC ||
|
||||
c->version() == GameVersion::PATCH ||
|
||||
c->version() == GameVersion::BB) {
|
||||
send_info_board_t<char16_t>(c, l);
|
||||
send_info_board_t<char16_t>(c);
|
||||
} else {
|
||||
send_info_board_t<char>(c, l);
|
||||
send_info_board_t<char>(c);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CommandHeaderT, typename CharT>
|
||||
void send_card_search_result_t(
|
||||
shared_ptr<ServerState> s,
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<Client> result,
|
||||
shared_ptr<Lobby> result_lobby) {
|
||||
auto s = c->require_server_state();
|
||||
const auto& port_name = version_to_lobby_port_name.at(static_cast<size_t>(c->version()));
|
||||
|
||||
S_GuildCardSearchResult<CommandHeaderT, CharT> cmd;
|
||||
@@ -969,28 +973,24 @@ void send_card_search_result_t(
|
||||
}
|
||||
cmd.location_string = location_string;
|
||||
cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY;
|
||||
cmd.extension.lobby_refs[0].item_id = result->lobby_id;
|
||||
cmd.extension.lobby_refs[0].item_id = result_lobby->lobby_id;
|
||||
cmd.extension.player_name = result->game_data.player()->disp.name;
|
||||
|
||||
send_command_t(c, 0x41, 0x00, cmd);
|
||||
}
|
||||
|
||||
void send_card_search_result(
|
||||
shared_ptr<ServerState> s,
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<Client> result,
|
||||
shared_ptr<Lobby> result_lobby) {
|
||||
if ((c->version() == GameVersion::DC) ||
|
||||
(c->version() == GameVersion::GC) ||
|
||||
(c->version() == GameVersion::XB)) {
|
||||
send_card_search_result_t<PSOCommandHeaderDCV3, char>(
|
||||
s, c, result, result_lobby);
|
||||
send_card_search_result_t<PSOCommandHeaderDCV3, char>(c, result, result_lobby);
|
||||
} else if (c->version() == GameVersion::PC) {
|
||||
send_card_search_result_t<PSOCommandHeaderPC, char16_t>(
|
||||
s, c, result, result_lobby);
|
||||
send_card_search_result_t<PSOCommandHeaderPC, char16_t>(c, result, result_lobby);
|
||||
} else if (c->version() == GameVersion::BB) {
|
||||
send_card_search_result_t<PSOCommandHeaderBB, char16_t>(
|
||||
s, c, result, result_lobby);
|
||||
send_card_search_result_t<PSOCommandHeaderBB, char16_t>(c, result, result_lobby);
|
||||
} else {
|
||||
throw logic_error("unimplemented versioned command");
|
||||
}
|
||||
@@ -1164,9 +1164,10 @@ void send_menu(shared_ptr<Client> c, shared_ptr<const Menu> menu, bool is_info_m
|
||||
template <typename CharT>
|
||||
void send_game_menu_t(
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<ServerState> s,
|
||||
bool is_spectator_team_list,
|
||||
bool show_tournaments_only) {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
vector<S_GameMenuEntry<CharT>> entries;
|
||||
{
|
||||
auto& e = entries.emplace_back();
|
||||
@@ -1258,15 +1259,14 @@ void send_game_menu_t(
|
||||
|
||||
void send_game_menu(
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<ServerState> s,
|
||||
bool is_spectator_team_list,
|
||||
bool show_tournaments_only) {
|
||||
if ((c->version() == GameVersion::DC) ||
|
||||
(c->version() == GameVersion::GC) ||
|
||||
(c->version() == GameVersion::XB)) {
|
||||
send_game_menu_t<char>(c, s, is_spectator_team_list, show_tournaments_only);
|
||||
send_game_menu_t<char>(c, is_spectator_team_list, show_tournaments_only);
|
||||
} else {
|
||||
send_game_menu_t<char16_t>(c, s, is_spectator_team_list, show_tournaments_only);
|
||||
send_game_menu_t<char16_t>(c, is_spectator_team_list, show_tournaments_only);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1352,11 +1352,12 @@ void send_quest_menu(shared_ptr<Client> c, uint32_t menu_id,
|
||||
}
|
||||
}
|
||||
|
||||
void send_lobby_list(shared_ptr<Client> c, shared_ptr<ServerState> s) {
|
||||
void send_lobby_list(shared_ptr<Client> c) {
|
||||
// This command appears to be deprecated, as PSO expects it to be exactly how
|
||||
// this server sends it, and does not react if it's different, except by
|
||||
// changing the lobby IDs.
|
||||
|
||||
auto s = c->require_server_state();
|
||||
vector<S_LobbyListEntry_83> entries;
|
||||
for (shared_ptr<Lobby> l : s->all_lobbies()) {
|
||||
if (!(l->flags & Lobby::Flag::DEFAULT)) {
|
||||
@@ -1962,8 +1963,8 @@ static vector<G_UpdatePlayerStat_6x9A> generate_stats_change_subcommands(
|
||||
return subs;
|
||||
}
|
||||
|
||||
void send_player_stats_change(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
PlayerStatsChange stat, uint32_t amount) {
|
||||
void send_player_stats_change(shared_ptr<Client> c, PlayerStatsChange stat, uint32_t amount) {
|
||||
auto l = c->require_lobby();
|
||||
auto subs = generate_stats_change_subcommands(c->lobby_client_id, stat, amount);
|
||||
send_command_vt(l, (subs.size() > 0x400 / sizeof(G_UpdatePlayerStat_6x9A)) ? 0x6C : 0x60, 0x00, subs);
|
||||
}
|
||||
@@ -2047,16 +2048,15 @@ void send_drop_stacked_item(shared_ptr<Lobby> l, const ItemData& item,
|
||||
}
|
||||
}
|
||||
|
||||
void send_pick_up_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
uint32_t item_id, uint8_t area) {
|
||||
void send_pick_up_item(shared_ptr<Client> c, uint32_t item_id, uint8_t area) {
|
||||
auto l = c->require_lobby();
|
||||
uint16_t client_id = c->lobby_client_id;
|
||||
G_PickUpItem_6x59 cmd = {
|
||||
{0x59, 0x03, client_id}, client_id, area, item_id};
|
||||
G_PickUpItem_6x59 cmd = {{0x59, 0x03, client_id}, client_id, area, item_id};
|
||||
send_command_t(l, 0x60, 0x00, cmd);
|
||||
}
|
||||
|
||||
void send_create_inventory_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
const ItemData& item) {
|
||||
void send_create_inventory_item(shared_ptr<Client> c, const ItemData& item) {
|
||||
auto l = c->require_lobby();
|
||||
if (c->version() != GameVersion::BB) {
|
||||
throw logic_error("6xBE can only be sent to BB clients");
|
||||
}
|
||||
@@ -2065,14 +2065,15 @@ void send_create_inventory_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
send_command_t(l, 0x60, 0x00, cmd);
|
||||
}
|
||||
|
||||
void send_destroy_item(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
uint32_t item_id, uint32_t amount) {
|
||||
void send_destroy_item(shared_ptr<Client> c, uint32_t item_id, uint32_t amount) {
|
||||
auto l = c->require_lobby();
|
||||
uint16_t client_id = c->lobby_client_id;
|
||||
G_DeleteInventoryItem_6x29 cmd = {{0x29, 0x03, client_id}, item_id, amount};
|
||||
send_command_t(l, 0x60, 0x00, cmd);
|
||||
}
|
||||
|
||||
void send_item_identify_result(shared_ptr<Lobby> l, shared_ptr<Client> c) {
|
||||
void send_item_identify_result(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (c->version() != GameVersion::BB) {
|
||||
throw logic_error("cannot send item identify result to non-BB client");
|
||||
}
|
||||
@@ -2124,7 +2125,8 @@ void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
|
||||
}
|
||||
|
||||
// notifies players about a level up
|
||||
void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
|
||||
void send_level_up(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
CharacterStats stats = c->game_data.player()->disp.stats.char_stats;
|
||||
|
||||
for (size_t x = 0; x < c->game_data.player()->inventory.num_items; x++) {
|
||||
@@ -2150,8 +2152,8 @@ void send_level_up(shared_ptr<Lobby> l, shared_ptr<Client> c) {
|
||||
send_command_t(l, 0x60, 0x00, cmd);
|
||||
}
|
||||
|
||||
void send_give_experience(shared_ptr<Lobby> l, shared_ptr<Client> c,
|
||||
uint32_t amount) {
|
||||
void send_give_experience(shared_ptr<Client> c, uint32_t amount) {
|
||||
auto l = c->require_lobby();
|
||||
if (c->version() != GameVersion::BB) {
|
||||
throw logic_error("6xBF can only be sent to BB clients");
|
||||
}
|
||||
@@ -2187,8 +2189,9 @@ void send_rare_enemy_index_list(shared_ptr<Client> c, const vector<size_t>& inde
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// ep3 only commands
|
||||
|
||||
void send_ep3_card_list_update(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
void send_ep3_card_list_update(shared_ptr<Client> c) {
|
||||
if (!(c->flags & Client::Flag::HAS_EP3_CARD_DEFS)) {
|
||||
auto s = c->require_server_state();
|
||||
const auto& data = (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)
|
||||
? s->ep3_card_index_trial->get_compressed_definitions()
|
||||
: s->ep3_card_index->get_compressed_definitions();
|
||||
@@ -2215,7 +2218,8 @@ void send_ep3_media_update(
|
||||
send_command(c, 0xB9, 0x00, w.str());
|
||||
}
|
||||
|
||||
void send_ep3_rank_update(shared_ptr<ServerState> s, shared_ptr<Client> c) {
|
||||
void send_ep3_rank_update(shared_ptr<Client> c) {
|
||||
auto s = c->require_server_state();
|
||||
uint32_t meseta = s->ep3_infinite_meseta ? 1000000 : 0;
|
||||
S_RankUpdate_GC_Ep3_B7 cmd = {0, "\0\0\0\0\0\0\0\0\0\0\0", meseta, meseta, 0xFFFFFFFF};
|
||||
send_command_t(c, 0xB7, 0x00, cmd);
|
||||
@@ -2259,17 +2263,15 @@ void send_ep3_set_context_token(shared_ptr<Client> c, uint32_t context_token) {
|
||||
}
|
||||
|
||||
void send_ep3_confirm_tournament_entry(
|
||||
shared_ptr<ServerState> s,
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<const Episode3::Tournament> tourn) {
|
||||
// WARNING: s is permitted to be null if tourn is null
|
||||
|
||||
if (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) {
|
||||
throw runtime_error("cannot send tournament entry command to Episode 3 Trial Edition client");
|
||||
}
|
||||
|
||||
S_ConfirmTournamentEntry_GC_Ep3_CC cmd;
|
||||
if (tourn) {
|
||||
auto s = c->require_server_state();
|
||||
cmd.tournament_name = tourn->get_name();
|
||||
cmd.server_name = encode_sjis(s->name);
|
||||
// TODO: Fill this in appropriately when we support scheduled start times
|
||||
@@ -2286,9 +2288,10 @@ void send_ep3_confirm_tournament_entry(
|
||||
}
|
||||
|
||||
void send_ep3_tournament_list(
|
||||
shared_ptr<ServerState> s,
|
||||
shared_ptr<Client> c,
|
||||
bool is_for_spectator_team_create) {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
S_TournamentList_GC_Ep3_E0 cmd;
|
||||
size_t z = 0;
|
||||
for (const auto& it : s->ep3_tournament_index->all_tournaments()) {
|
||||
@@ -2496,11 +2499,11 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
}
|
||||
}
|
||||
|
||||
void send_ep3_set_tournament_player_decks(
|
||||
shared_ptr<ServerState> s,
|
||||
shared_ptr<Lobby> l,
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<const Episode3::Tournament::Match> match) {
|
||||
void send_ep3_set_tournament_player_decks(shared_ptr<Client> c) {
|
||||
auto s = c->require_server_state();
|
||||
auto l = c->require_lobby();
|
||||
|
||||
auto& match = l->tournament_match;
|
||||
auto tourn = match->tournament.lock();
|
||||
if (!tourn) {
|
||||
throw runtime_error("tournament is deleted");
|
||||
@@ -2554,8 +2557,9 @@ void send_ep3_set_tournament_player_decks(
|
||||
// TODO: Handle disconnection during the match (the other team should win)
|
||||
}
|
||||
|
||||
void send_ep3_tournament_match_result(
|
||||
shared_ptr<ServerState> s, shared_ptr<Lobby> l, shared_ptr<const Episode3::Tournament::Match> match) {
|
||||
void send_ep3_tournament_match_result(shared_ptr<Lobby> l) {
|
||||
auto s = l->require_server_state();
|
||||
auto& match = l->tournament_match;
|
||||
auto tourn = match->tournament.lock();
|
||||
if (!tourn) {
|
||||
return;
|
||||
@@ -2776,7 +2780,8 @@ bool send_quest_barrier_if_all_clients_ready(shared_ptr<Lobby> l) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void send_ep3_card_auction(shared_ptr<ServerState> s, shared_ptr<Lobby> l) {
|
||||
void send_ep3_card_auction(shared_ptr<Lobby> l) {
|
||||
auto s = l->require_server_state();
|
||||
if ((s->ep3_card_auction_points == 0) ||
|
||||
(s->ep3_card_auction_min_size == 0) ||
|
||||
(s->ep3_card_auction_max_size == 0)) {
|
||||
|
||||
+25
-53
@@ -127,18 +127,13 @@ prepare_server_init_contents_bb(
|
||||
const parray<uint8_t, 0x30>& server_key,
|
||||
const parray<uint8_t, 0x30>& client_key,
|
||||
uint8_t flags);
|
||||
void send_server_init(
|
||||
std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Client> c,
|
||||
uint8_t flags);
|
||||
void send_server_init(std::shared_ptr<Client> c, uint8_t flags);
|
||||
void send_update_client_config(std::shared_ptr<Client> c);
|
||||
|
||||
void empty_function_call_response_handler(uint32_t, uint32_t);
|
||||
|
||||
void send_quest_buffer_overflow(
|
||||
std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
|
||||
void prepare_client_for_patches(
|
||||
std::shared_ptr<ServerState> s, std::shared_ptr<Client> c, std::function<void()> on_complete);
|
||||
void send_quest_buffer_overflow(std::shared_ptr<Client> c);
|
||||
void prepare_client_for_patches(std::shared_ptr<Client> c, std::function<void()> on_complete);
|
||||
void send_function_call(
|
||||
Channel& ch,
|
||||
uint64_t client_flags,
|
||||
@@ -190,7 +185,7 @@ void send_ship_info(Channel& ch, 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_text_message(std::shared_ptr<ServerState> s, const std::u16string& text);
|
||||
|
||||
std::u16string prepare_chat_message(
|
||||
GameVersion version,
|
||||
@@ -235,10 +230,9 @@ __attribute__((format(printf, 2, 3))) void send_text_message_printf(
|
||||
__attribute__((format(printf, 2, 3))) void send_ep3_text_message_printf(
|
||||
std::shared_ptr<ServerState> s, const char* format, ...);
|
||||
|
||||
void send_info_board(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
|
||||
void send_info_board(std::shared_ptr<Client> c);
|
||||
|
||||
void send_card_search_result(
|
||||
std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<Client> result,
|
||||
std::shared_ptr<Lobby> result_lobby);
|
||||
@@ -255,32 +249,26 @@ void send_guild_card(std::shared_ptr<Client> c, std::shared_ptr<Client> source);
|
||||
void send_menu(std::shared_ptr<Client> c, std::shared_ptr<const Menu> menu, bool is_info_menu = false);
|
||||
void send_game_menu(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<ServerState> s,
|
||||
bool is_spectator_team_list,
|
||||
bool is_tournament_game_list);
|
||||
void send_quest_menu(std::shared_ptr<Client> c, uint32_t menu_id,
|
||||
const std::vector<std::shared_ptr<const Quest>>& quests, bool is_download_menu);
|
||||
void send_quest_menu(std::shared_ptr<Client> c, uint32_t menu_id,
|
||||
std::shared_ptr<const QuestCategoryIndex> category_index, uint8_t flags);
|
||||
void send_lobby_list(std::shared_ptr<Client> c, std::shared_ptr<ServerState> s);
|
||||
void send_lobby_list(std::shared_ptr<Client> c);
|
||||
|
||||
void send_player_records(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client = nullptr);
|
||||
void send_join_lobby(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
|
||||
void send_player_join_notification(std::shared_ptr<Client> c,
|
||||
std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);
|
||||
void send_player_leave_notification(std::shared_ptr<Lobby> l,
|
||||
uint8_t leaving_client_id);
|
||||
void send_player_join_notification(std::shared_ptr<Client> c, std::shared_ptr<Lobby> l, std::shared_ptr<Client> joining_client);
|
||||
void send_player_leave_notification(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
|
||||
void send_self_leave_notification(std::shared_ptr<Client> c);
|
||||
void send_get_player_info(std::shared_ptr<Client> c);
|
||||
|
||||
void send_execute_item_trade(std::shared_ptr<Client> c,
|
||||
const std::vector<ItemData>& items);
|
||||
void send_execute_card_trade(std::shared_ptr<Client> c,
|
||||
const std::vector<std::pair<uint32_t, uint32_t>>& card_to_count);
|
||||
void send_execute_item_trade(std::shared_ptr<Client> c, const std::vector<ItemData>& items);
|
||||
void send_execute_card_trade(std::shared_ptr<Client> c, const std::vector<std::pair<uint32_t, uint32_t>>& card_to_count);
|
||||
|
||||
void send_arrow_update(std::shared_ptr<Lobby> l);
|
||||
void send_resume_game(std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<Client> ready_client);
|
||||
void send_resume_game(std::shared_ptr<Lobby> l, std::shared_ptr<Client> ready_client);
|
||||
|
||||
enum PlayerStatsChange {
|
||||
SUBTRACT_HP = 0,
|
||||
@@ -290,8 +278,7 @@ enum PlayerStatsChange {
|
||||
ADD_TP = 4,
|
||||
};
|
||||
|
||||
void send_player_stats_change(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c,
|
||||
PlayerStatsChange stat, uint32_t amount);
|
||||
void send_player_stats_change(std::shared_ptr<Client> c, PlayerStatsChange stat, uint32_t amount);
|
||||
void send_player_stats_change(
|
||||
Channel& ch, uint16_t client_id, PlayerStatsChange stat, uint32_t amount);
|
||||
void send_warp(Channel& ch, uint8_t client_id, uint32_t area, bool is_private);
|
||||
@@ -299,9 +286,8 @@ void send_warp(std::shared_ptr<Client> c, uint32_t area, bool is_private);
|
||||
void send_warp(std::shared_ptr<Lobby> l, uint32_t area, bool is_private);
|
||||
|
||||
void send_ep3_change_music(Channel& ch, uint32_t song);
|
||||
void send_set_player_visibility(std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<Client> c, bool visible);
|
||||
void send_revive_player(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c);
|
||||
void send_set_player_visibility(std::shared_ptr<Client> c, bool visible);
|
||||
void send_revive_player(std::shared_ptr<Client> c);
|
||||
|
||||
void send_drop_item(Channel& ch, const ItemData& item,
|
||||
bool from_enemy, uint8_t area, float x, float z, uint16_t request_id);
|
||||
@@ -311,37 +297,30 @@ void send_drop_stacked_item(Channel& ch, const ItemData& item,
|
||||
uint8_t area, float x, float z);
|
||||
void send_drop_stacked_item(std::shared_ptr<Lobby> l, const ItemData& item,
|
||||
uint8_t area, float x, float z);
|
||||
void send_pick_up_item(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c, uint32_t id,
|
||||
uint8_t area);
|
||||
void send_create_inventory_item(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c,
|
||||
const ItemData& item);
|
||||
void send_destroy_item(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c,
|
||||
uint32_t item_id, uint32_t amount);
|
||||
void send_item_identify_result(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c);
|
||||
void send_pick_up_item(std::shared_ptr<Client> c, uint32_t id, uint8_t area);
|
||||
void send_create_inventory_item(std::shared_ptr<Client> c, const ItemData& item);
|
||||
void send_destroy_item(std::shared_ptr<Client> c, uint32_t item_id, uint32_t amount);
|
||||
void send_item_identify_result(std::shared_ptr<Client> c);
|
||||
void send_bank(std::shared_ptr<Client> c);
|
||||
void send_shop(std::shared_ptr<Client> c, uint8_t shop_type);
|
||||
void send_level_up(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c);
|
||||
void send_give_experience(std::shared_ptr<Lobby> l, std::shared_ptr<Client> c,
|
||||
uint32_t amount);
|
||||
void send_level_up(std::shared_ptr<Client> c);
|
||||
void send_give_experience(std::shared_ptr<Client> c, uint32_t amount);
|
||||
void send_set_exp_multiplier(std::shared_ptr<Lobby> l);
|
||||
void send_rare_enemy_index_list(std::shared_ptr<Client> c, const std::vector<size_t>& indexes);
|
||||
void send_ep3_card_list_update(
|
||||
std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
|
||||
void send_ep3_card_list_update(std::shared_ptr<Client> c);
|
||||
void send_ep3_media_update(
|
||||
std::shared_ptr<Client> c,
|
||||
uint32_t type,
|
||||
uint32_t which,
|
||||
const std::string& compressed_data);
|
||||
void send_ep3_rank_update(std::shared_ptr<ServerState> s, std::shared_ptr<Client> c);
|
||||
void send_ep3_rank_update(std::shared_ptr<Client> c);
|
||||
void send_ep3_card_battle_table_state(std::shared_ptr<Lobby> l, uint16_t table_number);
|
||||
void send_ep3_set_context_token(std::shared_ptr<Client> c, uint32_t context_token);
|
||||
|
||||
void send_ep3_confirm_tournament_entry(
|
||||
std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const Episode3::Tournament> t);
|
||||
void send_ep3_tournament_list(
|
||||
std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Client> c,
|
||||
bool is_for_spectator_team_create);
|
||||
void send_ep3_tournament_entry_list(
|
||||
@@ -351,15 +330,8 @@ void send_ep3_tournament_entry_list(
|
||||
void send_ep3_tournament_info(
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const Episode3::Tournament> t);
|
||||
void send_ep3_set_tournament_player_decks(
|
||||
std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<Client> c,
|
||||
std::shared_ptr<const Episode3::Tournament::Match> match);
|
||||
void send_ep3_tournament_match_result(
|
||||
std::shared_ptr<ServerState> s,
|
||||
std::shared_ptr<Lobby> l,
|
||||
std::shared_ptr<const Episode3::Tournament::Match> match);
|
||||
void send_ep3_set_tournament_player_decks(std::shared_ptr<Client> c);
|
||||
void send_ep3_tournament_match_result(std::shared_ptr<Lobby> l);
|
||||
|
||||
void send_ep3_tournament_details(
|
||||
std::shared_ptr<Client> c,
|
||||
@@ -367,7 +339,7 @@ void send_ep3_tournament_details(
|
||||
void send_ep3_game_details(
|
||||
std::shared_ptr<Client> c, std::shared_ptr<Lobby> l);
|
||||
void send_ep3_update_spectator_count(std::shared_ptr<Lobby> l);
|
||||
void send_ep3_card_auction(std::shared_ptr<ServerState> s, std::shared_ptr<Lobby> l);
|
||||
void send_ep3_card_auction(std::shared_ptr<Lobby> l);
|
||||
void send_ep3_disband_watcher_lobbies(std::shared_ptr<Lobby> primary_l);
|
||||
|
||||
// Pass mask_key = 0 to unmask the command
|
||||
|
||||
+11
-11
@@ -42,7 +42,7 @@ void Server::disconnect_client(shared_ptr<Client> c) {
|
||||
c->channel.disconnect();
|
||||
|
||||
try {
|
||||
on_disconnect(this->state, c);
|
||||
on_disconnect(c);
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error during client disconnect cleanup: %s", e.what());
|
||||
}
|
||||
@@ -91,13 +91,13 @@ void Server::dispatch_on_listen_accept(
|
||||
socklen);
|
||||
}
|
||||
|
||||
void Server::dispatch_on_listen_error(struct evconnlistener* listener,
|
||||
void* ctx) {
|
||||
void Server::dispatch_on_listen_error(
|
||||
struct evconnlistener* listener, void* ctx) {
|
||||
reinterpret_cast<Server*>(ctx)->on_listen_error(listener);
|
||||
}
|
||||
|
||||
void Server::on_listen_accept(struct evconnlistener* listener,
|
||||
evutil_socket_t fd, struct sockaddr*, int) {
|
||||
void Server::on_listen_accept(
|
||||
struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
|
||||
|
||||
int listen_fd = evconnlistener_get_fd(listener);
|
||||
ListeningSocket* listening_socket;
|
||||
@@ -113,7 +113,7 @@ 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->shared_from_this(), bev, listening_socket->version, listening_socket->behavior));
|
||||
c->game_data.should_save = this->state->allow_saving;
|
||||
c->channel.on_command_received = Server::on_client_input;
|
||||
c->channel.on_error = Server::on_client_error;
|
||||
@@ -124,7 +124,7 @@ void Server::on_listen_accept(struct evconnlistener* listener,
|
||||
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
|
||||
|
||||
try {
|
||||
on_connect(this->state, c);
|
||||
on_connect(c);
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error during client initialization: %s", e.what());
|
||||
this->disconnect_client(c);
|
||||
@@ -134,7 +134,7 @@ void Server::on_listen_accept(struct evconnlistener* listener,
|
||||
void Server::connect_client(
|
||||
struct bufferevent* bev, uint32_t address, uint16_t client_port,
|
||||
uint16_t server_port, GameVersion version, ServerBehavior initial_state) {
|
||||
shared_ptr<Client> c(new Client(bev, version, initial_state));
|
||||
shared_ptr<Client> c(new Client(this->shared_from_this(), bev, version, initial_state));
|
||||
c->game_data.should_save = this->state->allow_saving;
|
||||
c->channel.on_command_received = Server::on_client_input;
|
||||
c->channel.on_error = Server::on_client_error;
|
||||
@@ -158,7 +158,7 @@ void Server::connect_client(
|
||||
remote_sin->sin_port = htons(client_port);
|
||||
|
||||
try {
|
||||
on_connect(this->state, c);
|
||||
on_connect(c);
|
||||
} catch (const exception& e) {
|
||||
server_log.error("Error during client initialization: %s", e.what());
|
||||
this->disconnect_client(c);
|
||||
@@ -181,13 +181,13 @@ void Server::on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::
|
||||
} else {
|
||||
if (server->state->catch_handler_exceptions) {
|
||||
try {
|
||||
on_command(server->state, c, command, flag, data);
|
||||
on_command(c, command, flag, data);
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error processing client command: %s", e.what());
|
||||
c->should_disconnect = true;
|
||||
}
|
||||
} else {
|
||||
on_command(server->state, c, command, flag, data);
|
||||
on_command(c, command, flag, data);
|
||||
}
|
||||
if (c->should_disconnect) {
|
||||
server->disconnect_client(c);
|
||||
|
||||
+6
-3
@@ -10,7 +10,7 @@
|
||||
#include "Client.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class Server {
|
||||
class Server : public std::enable_shared_from_this<Server> {
|
||||
public:
|
||||
Server() = delete;
|
||||
Server(const Server&) = delete;
|
||||
@@ -27,12 +27,17 @@ public:
|
||||
void connect_client(struct bufferevent* bev, uint32_t address,
|
||||
uint16_t client_port, uint16_t server_port,
|
||||
GameVersion version, ServerBehavior initial_state);
|
||||
void disconnect_client(std::shared_ptr<Client> c);
|
||||
|
||||
std::shared_ptr<Client> get_client() const;
|
||||
std::vector<std::shared_ptr<Client>> get_clients_by_identifier(
|
||||
const std::string& ident) const;
|
||||
std::shared_ptr<struct event_base> get_base() const;
|
||||
|
||||
inline std::shared_ptr<ServerState> get_state() const {
|
||||
return this->state;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct event> destroy_clients_ev;
|
||||
@@ -64,8 +69,6 @@ private:
|
||||
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
|
||||
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
|
||||
|
||||
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);
|
||||
|
||||
+2
-2
@@ -569,7 +569,7 @@ Proxy session commands:\n\
|
||||
if (tourn) {
|
||||
tourn->start();
|
||||
this->state->ep3_tournament_index->save();
|
||||
tourn->send_all_state_updates(this->state);
|
||||
tourn->send_all_state_updates();
|
||||
send_ep3_text_message_printf(this->state, "$C7The tournament\n$C6%s$C7\nhas begun", tourn->get_name().c_str());
|
||||
fprintf(stderr, "tournament started\n");
|
||||
} else {
|
||||
@@ -625,7 +625,7 @@ Proxy session commands:\n\
|
||||
|
||||
if (c) {
|
||||
if (command_name[1] == 's') {
|
||||
on_command_with_header(this->state, c, data);
|
||||
on_command_with_header(c, data);
|
||||
} else {
|
||||
send_command_with_header(c->channel, data.data(), data.size());
|
||||
}
|
||||
|
||||
+16
-10
@@ -113,7 +113,7 @@ void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
|
||||
if (c->preferred_lobby_id >= 0) {
|
||||
try {
|
||||
auto l = this->find_lobby(c->preferred_lobby_id);
|
||||
if (!l->is_game() && (l->flags & Lobby::Flag::PUBLIC)) {
|
||||
if (l && !l->is_game() && (l->flags & Lobby::Flag::PUBLIC)) {
|
||||
l->add_client(c);
|
||||
added_to_lobby = l;
|
||||
}
|
||||
@@ -148,12 +148,14 @@ void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
|
||||
}
|
||||
|
||||
void ServerState::remove_client_from_lobby(shared_ptr<Client> c) {
|
||||
auto l = this->id_to_lobby.at(c->lobby_id);
|
||||
l->remove_client(c);
|
||||
if (!(l->flags & Lobby::Flag::PERSISTENT) && (l->count_clients() == 0)) {
|
||||
this->remove_lobby(l->lobby_id);
|
||||
} else {
|
||||
send_player_leave_notification(l, c->lobby_client_id);
|
||||
auto l = c->lobby.lock();
|
||||
if (l) {
|
||||
l->remove_client(c);
|
||||
if (!(l->flags & Lobby::Flag::PERSISTENT) && (l->count_clients() == 0)) {
|
||||
this->remove_lobby(l->lobby_id);
|
||||
} else {
|
||||
send_player_leave_notification(l, c->lobby_client_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +166,7 @@ bool ServerState::change_client_lobby(
|
||||
ssize_t required_client_id) {
|
||||
uint8_t old_lobby_client_id = c->lobby_client_id;
|
||||
|
||||
shared_ptr<Lobby> current_lobby = this->find_lobby(c->lobby_id);
|
||||
auto current_lobby = c->lobby.lock();
|
||||
try {
|
||||
if (current_lobby) {
|
||||
current_lobby->move_client_to_lobby(new_lobby, c, required_client_id);
|
||||
@@ -210,7 +212,11 @@ void ServerState::send_lobby_join_notifications(shared_ptr<Lobby> l,
|
||||
}
|
||||
|
||||
shared_ptr<Lobby> ServerState::find_lobby(uint32_t lobby_id) {
|
||||
return this->id_to_lobby.at(lobby_id);
|
||||
try {
|
||||
return this->id_to_lobby.at(lobby_id);
|
||||
} catch (const out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<Lobby>> ServerState::all_lobbies() {
|
||||
@@ -225,7 +231,7 @@ shared_ptr<Lobby> ServerState::create_lobby() {
|
||||
while (this->id_to_lobby.count(this->next_lobby_id)) {
|
||||
this->next_lobby_id++;
|
||||
}
|
||||
shared_ptr<Lobby> l(new Lobby(this->next_lobby_id++));
|
||||
shared_ptr<Lobby> l(new Lobby(this->shared_from_this(), this->next_lobby_id++));
|
||||
this->id_to_lobby.emplace(l->lobby_id, l);
|
||||
l->log.info("Created lobby");
|
||||
return l;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// 3. Unregistered users are allowed. This enables the tests to run on other
|
||||
// machines, which won't have the same license file.
|
||||
"ServerName": "Alexandria",
|
||||
"CatchHandlerExceptions": false,
|
||||
|
||||
"LocalAddress": "en0",
|
||||
"ExternalAddress": "en0",
|
||||
|
||||
Reference in New Issue
Block a user