add --cat-client behavior
This commit is contained in:
@@ -45,6 +45,7 @@ endif()
|
|||||||
# Executable definition
|
# Executable definition
|
||||||
|
|
||||||
add_executable(newserv
|
add_executable(newserv
|
||||||
|
src/CatSession.cc
|
||||||
src/Channel.cc
|
src/Channel.cc
|
||||||
src/ChatCommands.cc
|
src/ChatCommands.cc
|
||||||
src/Client.cc
|
src/Client.cc
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
#include "CatSession.hh"
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <event2/buffer.h>
|
||||||
|
#include <event2/bufferevent.h>
|
||||||
|
#include <event2/event.h>
|
||||||
|
#include <event2/listener.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
#include <phosg/Encoding.hh>
|
||||||
|
#include <phosg/Filesystem.hh>
|
||||||
|
#include <phosg/Network.hh>
|
||||||
|
#include <phosg/Random.hh>
|
||||||
|
#include <phosg/Strings.hh>
|
||||||
|
#include <phosg/Time.hh>
|
||||||
|
|
||||||
|
#include "Loggers.hh"
|
||||||
|
#include "PSOProtocol.hh"
|
||||||
|
#include "SendCommands.hh"
|
||||||
|
#include "ReceiveCommands.hh"
|
||||||
|
#include "ReceiveSubcommands.hh"
|
||||||
|
#include "ProxyCommands.hh"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CatSession::CatSession(
|
||||||
|
shared_ptr<struct event_base> base,
|
||||||
|
const struct sockaddr_storage& remote,
|
||||||
|
GameVersion version)
|
||||||
|
: Shell(base),
|
||||||
|
log("[CatSession] ", proxy_server_log.min_level),
|
||||||
|
channel(
|
||||||
|
version,
|
||||||
|
CatSession::dispatch_on_channel_input,
|
||||||
|
CatSession::dispatch_on_channel_error,
|
||||||
|
this,
|
||||||
|
"CatSession") {
|
||||||
|
if (remote.ss_family != AF_INET) {
|
||||||
|
throw runtime_error("remote is not AF_INET");
|
||||||
|
}
|
||||||
|
|
||||||
|
string netloc_str = render_sockaddr_storage(remote);
|
||||||
|
this->log.info("Connecting to %s", netloc_str.c_str());
|
||||||
|
|
||||||
|
struct bufferevent* bev = bufferevent_socket_new(
|
||||||
|
this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||||
|
if (!bev) {
|
||||||
|
throw runtime_error(string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
|
||||||
|
}
|
||||||
|
this->channel.set_bufferevent(bev);
|
||||||
|
|
||||||
|
if (bufferevent_socket_connect(this->channel.bev.get(),
|
||||||
|
reinterpret_cast<const sockaddr*>(&remote), sizeof(struct sockaddr_in)) != 0) {
|
||||||
|
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CatSession::dispatch_on_channel_input(
|
||||||
|
Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
|
||||||
|
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
|
||||||
|
session->on_channel_input(command, flag, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CatSession::on_channel_input(
|
||||||
|
uint16_t command, uint32_t flag, std::string& data) {
|
||||||
|
if (this->channel.version != GameVersion::BB) {
|
||||||
|
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
|
||||||
|
const auto& cmd = check_size_t<S_ServerInit_DC_PC_GC_02_17_91_9B>(data,
|
||||||
|
offsetof(S_ServerInit_DC_PC_GC_02_17_91_9B, after_message), 0xFFFF);
|
||||||
|
if (this->channel.version == GameVersion::GC) {
|
||||||
|
this->channel.crypt_in.reset(new PSOGCEncryption(cmd.server_key));
|
||||||
|
this->channel.crypt_out.reset(new PSOGCEncryption(cmd.client_key));
|
||||||
|
this->log.info("Enabled GC encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||||
|
cmd.server_key.load(), cmd.client_key.load());
|
||||||
|
} else { // PC, DC, or patch server
|
||||||
|
this->channel.crypt_in.reset(new PSOPCEncryption(cmd.server_key));
|
||||||
|
this->channel.crypt_out.reset(new PSOPCEncryption(cmd.client_key));
|
||||||
|
this->log.info("Enabled PC encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||||
|
cmd.server_key.load(), cmd.client_key.load());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // BB
|
||||||
|
// TODO: This can easily be done; I'm just lazy. We need to have the user
|
||||||
|
// pass in a key name, then get that key file from this->state, then create
|
||||||
|
// the crypts.
|
||||||
|
// TODO: We really should just move encryption handling into the Channel
|
||||||
|
// abstraction instead of the above, but this is a bit harder because then
|
||||||
|
// Channels would have to know about BB key files.
|
||||||
|
throw runtime_error("CatSession does not implement BB encryption yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
string full_cmd = prepend_command_header(
|
||||||
|
this->channel.version, this->channel.crypt_in.get(), command, flag, data);
|
||||||
|
print_data(stdout, full_cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CatSession::dispatch_on_channel_error(Channel& ch, short events) {
|
||||||
|
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
|
||||||
|
session->on_channel_error(events);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CatSession::on_channel_error(short events) {
|
||||||
|
if (events & BEV_EVENT_ERROR) {
|
||||||
|
int err = EVUTIL_SOCKET_ERROR();
|
||||||
|
this->log.warning("Error %d (%s) in unlinked client stream", err,
|
||||||
|
evutil_socket_error_to_string(err));
|
||||||
|
}
|
||||||
|
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
|
||||||
|
this->log.info("Session endpoint has disconnected");
|
||||||
|
this->channel.disconnect();
|
||||||
|
event_base_loopexit(this->base.get(), nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CatSession::print_prompt() { }
|
||||||
|
|
||||||
|
void CatSession::execute_command(const std::string& command) {
|
||||||
|
string full_cmd = parse_data_string(command);
|
||||||
|
send_command_with_header(this->channel, full_cmd.data(), full_cmd.size());
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <event2/event.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <phosg/Filesystem.hh>
|
||||||
|
|
||||||
|
#include "PSOEncryption.hh"
|
||||||
|
#include "PSOProtocol.hh"
|
||||||
|
#include "ServerState.hh"
|
||||||
|
#include "Shell.hh"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CatSession : public Shell {
|
||||||
|
public:
|
||||||
|
CatSession(
|
||||||
|
std::shared_ptr<struct event_base> base,
|
||||||
|
const struct sockaddr_storage& remote,
|
||||||
|
GameVersion version);
|
||||||
|
virtual ~CatSession() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PrefixedLogger log;
|
||||||
|
Channel channel;
|
||||||
|
|
||||||
|
virtual void print_prompt();
|
||||||
|
virtual void execute_command(const std::string& command);
|
||||||
|
|
||||||
|
static void dispatch_on_channel_input(
|
||||||
|
Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
|
||||||
|
static void dispatch_on_channel_error(Channel& ch, short events);
|
||||||
|
void on_channel_input(uint16_t command, uint32_t flag, std::string& msg);
|
||||||
|
void on_channel_error(short events);
|
||||||
|
};
|
||||||
+306
-220
@@ -10,6 +10,7 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "CatSession.hh"
|
||||||
#include "DNSServer.hh"
|
#include "DNSServer.hh"
|
||||||
#include "IPStackSimulator.hh"
|
#include "IPStackSimulator.hh"
|
||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
@@ -207,6 +208,59 @@ void drop_privileges(const string& username) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void print_usage() {
|
||||||
|
fputs("\
|
||||||
|
newserv - a Phantasy Star Online Swiss Army knife\n\
|
||||||
|
\n\
|
||||||
|
Usage:\n\
|
||||||
|
newserv [options]\n\
|
||||||
|
\n\
|
||||||
|
With no options, newserv runs in server mode. PSO clients can connect normally,\n\
|
||||||
|
join lobbies, play games, and use the proxy server. See README.md and\n\
|
||||||
|
system/config.json for more information.\n\
|
||||||
|
\n\
|
||||||
|
When options are given, newserv will do things other than running the server.\n\
|
||||||
|
Specifically:\n\
|
||||||
|
--decrypt-data\n\
|
||||||
|
--encrypt-data\n\
|
||||||
|
If either of these options is given, newserv will read from stdin and\n\
|
||||||
|
write the encrypted or decrypted result to stdout. By default, PSO GC\n\
|
||||||
|
encryption is used, but this can be overridden with --pc or --bb. The\n\
|
||||||
|
--seed option specifies the ecryption seed (4 hex bytes for PC or GC, or\n\
|
||||||
|
48 hex bytes for BB). For BB, the --key option is requires as well, and\n\
|
||||||
|
should refer to a .nsk file in system/blueburst/keys (without the\n\
|
||||||
|
directory or .nsk extension).\n\
|
||||||
|
--decode-sjis\n\
|
||||||
|
If this option is given, newserv applies its text decoding algorithm to\n\
|
||||||
|
the data on stding, producing little-endian UTF-16 data on stdout.\n\
|
||||||
|
--decode-gci=FILENAME\n\
|
||||||
|
--decode-dlq=FILENAME\n\
|
||||||
|
--decode-qst=FILENAME\n\
|
||||||
|
If any of these options are given, newserv will decode the given quest\n\
|
||||||
|
file into a compressed, unencrypted .bin or .dat file (or in the case of)\n\
|
||||||
|
--decode-qst, both a .bin and a .dat file).\n\
|
||||||
|
--cat-client=ADDR:PORT\n\
|
||||||
|
If this option is given, newserv will behave as if it's a PSO client, and\n\
|
||||||
|
will connect to the given server. It will then print all the received\n\
|
||||||
|
commands to stdout, and forward any commands typed into stdin to the\n\
|
||||||
|
remote server. It is assumed that the input and output are terminals, so\n\
|
||||||
|
all commands are hex-encoded. The --patch, --dc, --pc, --gc, and --bb\n\
|
||||||
|
options can be used to select the command format end encryption.\n\
|
||||||
|
--replay-log=FILENAME\n\
|
||||||
|
This option makes newserv replay terminal log as if it were a client\n\
|
||||||
|
session. This is used for regression testing, to make sure client\n\
|
||||||
|
sessions are repeatable and code changes don\'t affect existing (working)\n\
|
||||||
|
functionality.\n\
|
||||||
|
\n\
|
||||||
|
A few options apply to multiple modes described above:\n\
|
||||||
|
--parse-data\n\
|
||||||
|
For modes that take input on stdin, parse the input as a hex string\n\
|
||||||
|
before encrypting/decoding/etc.\n\
|
||||||
|
--config=FILENAME\n\
|
||||||
|
Use this file instead of system/config.json.\n\
|
||||||
|
", stderr);
|
||||||
|
}
|
||||||
|
|
||||||
enum class Behavior {
|
enum class Behavior {
|
||||||
RUN_SERVER = 0,
|
RUN_SERVER = 0,
|
||||||
DECRYPT_DATA,
|
DECRYPT_DATA,
|
||||||
@@ -214,12 +268,7 @@ enum class Behavior {
|
|||||||
DECODE_QUEST_FILE,
|
DECODE_QUEST_FILE,
|
||||||
DECODE_SJIS,
|
DECODE_SJIS,
|
||||||
REPLAY_LOG,
|
REPLAY_LOG,
|
||||||
};
|
CAT_CLIENT,
|
||||||
|
|
||||||
enum class EncryptionType {
|
|
||||||
PC = 0,
|
|
||||||
GC,
|
|
||||||
BB,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class QuestFileFormat {
|
enum class QuestFileFormat {
|
||||||
@@ -230,7 +279,7 @@ enum class QuestFileFormat {
|
|||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
Behavior behavior = Behavior::RUN_SERVER;
|
Behavior behavior = Behavior::RUN_SERVER;
|
||||||
EncryptionType crypt_type = EncryptionType::PC;
|
GameVersion cli_version = GameVersion::GC;
|
||||||
QuestFileFormat quest_file_type = QuestFileFormat::GCI;
|
QuestFileFormat quest_file_type = QuestFileFormat::GCI;
|
||||||
string quest_filename;
|
string quest_filename;
|
||||||
string seed;
|
string seed;
|
||||||
@@ -238,8 +287,12 @@ int main(int argc, char** argv) {
|
|||||||
const char* config_filename = "system/config.json";
|
const char* config_filename = "system/config.json";
|
||||||
bool parse_data = false;
|
bool parse_data = false;
|
||||||
const char* replay_log_filename = nullptr;
|
const char* replay_log_filename = nullptr;
|
||||||
|
struct sockaddr_storage cat_client_remote;
|
||||||
for (int x = 1; x < argc; x++) {
|
for (int x = 1; x < argc; x++) {
|
||||||
if (!strcmp(argv[x], "--decrypt-data")) {
|
if (!strcmp(argv[x], "--help")) {
|
||||||
|
print_usage();
|
||||||
|
return 0;
|
||||||
|
} else if (!strcmp(argv[x], "--decrypt-data")) {
|
||||||
behavior = Behavior::DECRYPT_DATA;
|
behavior = Behavior::DECRYPT_DATA;
|
||||||
} else if (!strcmp(argv[x], "--encrypt-data")) {
|
} else if (!strcmp(argv[x], "--encrypt-data")) {
|
||||||
behavior = Behavior::ENCRYPT_DATA;
|
behavior = Behavior::ENCRYPT_DATA;
|
||||||
@@ -257,12 +310,19 @@ int main(int argc, char** argv) {
|
|||||||
behavior = Behavior::DECODE_QUEST_FILE;
|
behavior = Behavior::DECODE_QUEST_FILE;
|
||||||
quest_file_type = QuestFileFormat::QST;
|
quest_file_type = QuestFileFormat::QST;
|
||||||
quest_filename = &argv[x][13];
|
quest_filename = &argv[x][13];
|
||||||
|
} else if (!strncmp(argv[x], "--cat-client=", 13)) {
|
||||||
|
behavior = Behavior::CAT_CLIENT;
|
||||||
|
cat_client_remote = make_sockaddr_storage(parse_netloc(&argv[x][13])).first;
|
||||||
|
} else if (!strcmp(argv[x], "--patch")) {
|
||||||
|
cli_version = GameVersion::PATCH;
|
||||||
|
} else if (!strcmp(argv[x], "--dc")) {
|
||||||
|
cli_version = GameVersion::DC;
|
||||||
} else if (!strcmp(argv[x], "--pc")) {
|
} else if (!strcmp(argv[x], "--pc")) {
|
||||||
crypt_type = EncryptionType::PC;
|
cli_version = GameVersion::PC;
|
||||||
} else if (!strcmp(argv[x], "--gc")) {
|
} else if (!strcmp(argv[x], "--gc")) {
|
||||||
crypt_type = EncryptionType::GC;
|
cli_version = GameVersion::GC;
|
||||||
} else if (!strcmp(argv[x], "--bb")) {
|
} else if (!strcmp(argv[x], "--bb")) {
|
||||||
crypt_type = EncryptionType::BB;
|
cli_version = GameVersion::BB;
|
||||||
} else if (!strncmp(argv[x], "--seed=", 7)) {
|
} else if (!strncmp(argv[x], "--seed=", 7)) {
|
||||||
seed = &argv[x][7];
|
seed = &argv[x][7];
|
||||||
} else if (!strncmp(argv[x], "--key=", 6)) {
|
} else if (!strncmp(argv[x], "--key=", 6)) {
|
||||||
@@ -279,224 +339,250 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (behavior == Behavior::DECRYPT_DATA || behavior == Behavior::ENCRYPT_DATA) {
|
switch (behavior) {
|
||||||
shared_ptr<PSOEncryption> crypt;
|
case Behavior::DECRYPT_DATA:
|
||||||
if (crypt_type == EncryptionType::PC) {
|
case Behavior::ENCRYPT_DATA: {
|
||||||
crypt.reset(new PSOPCEncryption(stoul(seed, nullptr, 16)));
|
shared_ptr<PSOEncryption> crypt;
|
||||||
} else if (crypt_type == EncryptionType::GC) {
|
switch (cli_version) {
|
||||||
crypt.reset(new PSOGCEncryption(stoul(seed, nullptr, 16)));
|
case GameVersion::PATCH:
|
||||||
} else if (crypt_type == EncryptionType::BB) {
|
case GameVersion::DC:
|
||||||
seed = parse_data_string(seed);
|
case GameVersion::PC:
|
||||||
auto key = load_object_file<PSOBBEncryption::KeyFile>(
|
crypt.reset(new PSOPCEncryption(stoul(seed, nullptr, 16)));
|
||||||
"system/blueburst/keys/" + key_file_name + ".nsk");
|
break;
|
||||||
crypt.reset(new PSOBBEncryption(key, seed.data(), seed.size()));
|
case GameVersion::GC:
|
||||||
} else {
|
crypt.reset(new PSOGCEncryption(stoul(seed, nullptr, 16)));
|
||||||
throw logic_error("invalid encryption type");
|
break;
|
||||||
}
|
case GameVersion::BB: {
|
||||||
|
seed = parse_data_string(seed);
|
||||||
string data = read_all(stdin);
|
auto key = load_object_file<PSOBBEncryption::KeyFile>(
|
||||||
if (parse_data) {
|
"system/blueburst/keys/" + key_file_name + ".nsk");
|
||||||
data = parse_data_string(data);
|
crypt.reset(new PSOBBEncryption(key, seed.data(), seed.size()));
|
||||||
}
|
break;
|
||||||
|
|
||||||
if (behavior == Behavior::DECRYPT_DATA) {
|
|
||||||
crypt->decrypt(data.data(), data.size());
|
|
||||||
} else if (behavior == Behavior::ENCRYPT_DATA) {
|
|
||||||
crypt->encrypt(data.data(), data.size());
|
|
||||||
} else {
|
|
||||||
throw logic_error("invalid behavior");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isatty(fileno(stdout))) {
|
|
||||||
print_data(stdout, data);
|
|
||||||
} else {
|
|
||||||
fwritex(stdout, data);
|
|
||||||
}
|
|
||||||
fflush(stdout);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
} else if (behavior == Behavior::DECODE_QUEST_FILE) {
|
|
||||||
if (quest_file_type == QuestFileFormat::GCI) {
|
|
||||||
save_file(quest_filename + ".dec", Quest::decode_gci(quest_filename));
|
|
||||||
} else if (quest_file_type == QuestFileFormat::DLQ) {
|
|
||||||
save_file(quest_filename + ".dec", Quest::decode_dlq(quest_filename));
|
|
||||||
} else if (quest_file_type == QuestFileFormat::QST) {
|
|
||||||
auto data = Quest::decode_qst(quest_filename);
|
|
||||||
save_file(quest_filename + ".bin", data.first);
|
|
||||||
save_file(quest_filename + ".dat", data.second);
|
|
||||||
} else {
|
|
||||||
throw logic_error("invalid quest file format");
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
} else if (behavior == Behavior::DECODE_SJIS) {
|
|
||||||
string data = read_all(stdin);
|
|
||||||
if (parse_data) {
|
|
||||||
data = parse_data_string(data);
|
|
||||||
}
|
|
||||||
auto decoded = decode_sjis(data);
|
|
||||||
print_data(stderr, decoded.data(), decoded.size() * sizeof(decoded[0]));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
|
||||||
|
|
||||||
if (isatty(fileno(stderr))) {
|
|
||||||
use_terminal_colors = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<ServerState> state(new ServerState());
|
|
||||||
|
|
||||||
shared_ptr<struct event_base> base(event_base_new(), event_base_free);
|
|
||||||
|
|
||||||
config_log.info("Reading network addresses");
|
|
||||||
state->all_addresses = get_local_addresses();
|
|
||||||
for (const auto& it : state->all_addresses) {
|
|
||||||
string addr_str = string_for_address(it.second);
|
|
||||||
config_log.info("Found interface: %s = %s", it.first.c_str(), addr_str.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
config_log.info("Loading configuration");
|
|
||||||
auto config_json = JSONObject::parse(load_file(config_filename));
|
|
||||||
populate_state_from_config(state, config_json);
|
|
||||||
|
|
||||||
config_log.info("Loading license list");
|
|
||||||
state->license_manager.reset(new LicenseManager("system/licenses.nsi"));
|
|
||||||
|
|
||||||
config_log.info("Loading battle parameters");
|
|
||||||
state->battle_params.reset(new BattleParamTable("system/blueburst/BattleParamEntry"));
|
|
||||||
|
|
||||||
config_log.info("Loading level table");
|
|
||||||
state->level_table.reset(new LevelTable("system/blueburst/PlyLevelTbl.prs", true));
|
|
||||||
|
|
||||||
config_log.info("Collecting Episode 3 data");
|
|
||||||
state->ep3_data_index.reset(new Ep3DataIndex("system/ep3"));
|
|
||||||
|
|
||||||
config_log.info("Collecting quest metadata");
|
|
||||||
state->quest_index.reset(new QuestIndex("system/quests"));
|
|
||||||
|
|
||||||
if (!replay_log_filename) {
|
|
||||||
config_log.info("Compiling client functions");
|
|
||||||
state->function_code_index.reset(new FunctionCodeIndex("system/ppc"));
|
|
||||||
config_log.info("Loading DOL files");
|
|
||||||
state->dol_file_index.reset(new DOLFileIndex("system/dol"));
|
|
||||||
} else {
|
|
||||||
state->function_code_index.reset(new FunctionCodeIndex());
|
|
||||||
state->dol_file_index.reset(new DOLFileIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
config_log.info("Creating menus");
|
|
||||||
state->create_menus(config_json);
|
|
||||||
|
|
||||||
if (replay_log_filename) {
|
|
||||||
state->allow_saving = false;
|
|
||||||
state->license_manager->set_autosave(false);
|
|
||||||
config_log.info("Saving disabled because this is a replay session");
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<DNSServer> dns_server;
|
|
||||||
if (state->dns_server_port && !replay_log_filename) {
|
|
||||||
config_log.info("Starting DNS server");
|
|
||||||
dns_server.reset(new DNSServer(base, state->local_address,
|
|
||||||
state->external_address));
|
|
||||||
dns_server->listen("", state->dns_server_port);
|
|
||||||
} else {
|
|
||||||
config_log.info("DNS server is disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<Shell> shell;
|
|
||||||
shared_ptr<ReplaySession> replay_session;
|
|
||||||
shared_ptr<IPStackSimulator> ip_stack_simulator;
|
|
||||||
if (behavior == Behavior::REPLAY_LOG) {
|
|
||||||
config_log.info("Starting proxy server");
|
|
||||||
state->proxy_server.reset(new ProxyServer(base, state));
|
|
||||||
config_log.info("Starting game server");
|
|
||||||
state->game_server.reset(new Server(base, state));
|
|
||||||
|
|
||||||
auto f = fopen_unique(replay_log_filename, "rt");
|
|
||||||
replay_session.reset(new ReplaySession(base, f.get(), state));
|
|
||||||
replay_session->start();
|
|
||||||
|
|
||||||
} else if (behavior == Behavior::RUN_SERVER) {
|
|
||||||
config_log.info("Opening sockets");
|
|
||||||
for (const auto& it : state->name_to_port_config) {
|
|
||||||
const auto& pc = it.second;
|
|
||||||
if (pc->behavior == ServerBehavior::PROXY_SERVER) {
|
|
||||||
if (!state->proxy_server.get()) {
|
|
||||||
config_log.info("Starting proxy server");
|
|
||||||
state->proxy_server.reset(new ProxyServer(base, state));
|
|
||||||
}
|
}
|
||||||
if (state->proxy_server.get()) {
|
default:
|
||||||
// For PC and GC, proxy sessions are dynamically created when a client
|
throw logic_error("invalid game version");
|
||||||
// picks a destination from the menu. For patch and BB clients, there's
|
}
|
||||||
// no way to ask the client which destination they want, so only one
|
|
||||||
// destination is supported, and we have to manually specify the
|
string data = read_all(stdin);
|
||||||
// destination netloc here.
|
if (parse_data) {
|
||||||
if (pc->version == GameVersion::PATCH) {
|
data = parse_data_string(data);
|
||||||
struct sockaddr_storage ss = make_sockaddr_storage(
|
}
|
||||||
state->proxy_destination_patch.first,
|
|
||||||
state->proxy_destination_patch.second).first;
|
if (behavior == Behavior::DECRYPT_DATA) {
|
||||||
state->proxy_server->listen(pc->port, pc->version, &ss);
|
crypt->decrypt(data.data(), data.size());
|
||||||
} else if (pc->version == GameVersion::BB) {
|
} else if (behavior == Behavior::ENCRYPT_DATA) {
|
||||||
struct sockaddr_storage ss = make_sockaddr_storage(
|
crypt->encrypt(data.data(), data.size());
|
||||||
state->proxy_destination_bb.first,
|
} else {
|
||||||
state->proxy_destination_bb.second).first;
|
throw logic_error("invalid behavior");
|
||||||
state->proxy_server->listen(pc->port, pc->version, &ss);
|
}
|
||||||
|
|
||||||
|
if (isatty(fileno(stdout))) {
|
||||||
|
print_data(stdout, data);
|
||||||
|
} else {
|
||||||
|
fwritex(stdout, data);
|
||||||
|
}
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Behavior::DECODE_QUEST_FILE:
|
||||||
|
if (quest_file_type == QuestFileFormat::GCI) {
|
||||||
|
save_file(quest_filename + ".dec", Quest::decode_gci(quest_filename));
|
||||||
|
} else if (quest_file_type == QuestFileFormat::DLQ) {
|
||||||
|
save_file(quest_filename + ".dec", Quest::decode_dlq(quest_filename));
|
||||||
|
} else if (quest_file_type == QuestFileFormat::QST) {
|
||||||
|
auto data = Quest::decode_qst(quest_filename);
|
||||||
|
save_file(quest_filename + ".bin", data.first);
|
||||||
|
save_file(quest_filename + ".dat", data.second);
|
||||||
|
} else {
|
||||||
|
throw logic_error("invalid quest file format");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Behavior::DECODE_SJIS: {
|
||||||
|
string data = read_all(stdin);
|
||||||
|
if (parse_data) {
|
||||||
|
data = parse_data_string(data);
|
||||||
|
}
|
||||||
|
auto decoded = decode_sjis(data);
|
||||||
|
print_data(stderr, decoded.data(), decoded.size() * sizeof(decoded[0]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Behavior::CAT_CLIENT: {
|
||||||
|
shared_ptr<struct event_base> base(event_base_new(), event_base_free);
|
||||||
|
CatSession session(base, cat_client_remote, cli_version);
|
||||||
|
event_base_dispatch(base.get());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Behavior::REPLAY_LOG:
|
||||||
|
case Behavior::RUN_SERVER: {
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
|
if (isatty(fileno(stderr))) {
|
||||||
|
use_terminal_colors = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<ServerState> state(new ServerState());
|
||||||
|
|
||||||
|
shared_ptr<struct event_base> base(event_base_new(), event_base_free);
|
||||||
|
|
||||||
|
config_log.info("Reading network addresses");
|
||||||
|
state->all_addresses = get_local_addresses();
|
||||||
|
for (const auto& it : state->all_addresses) {
|
||||||
|
string addr_str = string_for_address(it.second);
|
||||||
|
config_log.info("Found interface: %s = %s", it.first.c_str(), addr_str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
config_log.info("Loading configuration");
|
||||||
|
auto config_json = JSONObject::parse(load_file(config_filename));
|
||||||
|
populate_state_from_config(state, config_json);
|
||||||
|
|
||||||
|
config_log.info("Loading license list");
|
||||||
|
state->license_manager.reset(new LicenseManager("system/licenses.nsi"));
|
||||||
|
|
||||||
|
config_log.info("Loading battle parameters");
|
||||||
|
state->battle_params.reset(new BattleParamTable("system/blueburst/BattleParamEntry"));
|
||||||
|
|
||||||
|
config_log.info("Loading level table");
|
||||||
|
state->level_table.reset(new LevelTable("system/blueburst/PlyLevelTbl.prs", true));
|
||||||
|
|
||||||
|
config_log.info("Collecting Episode 3 data");
|
||||||
|
state->ep3_data_index.reset(new Ep3DataIndex("system/ep3"));
|
||||||
|
|
||||||
|
config_log.info("Collecting quest metadata");
|
||||||
|
state->quest_index.reset(new QuestIndex("system/quests"));
|
||||||
|
|
||||||
|
if (!replay_log_filename) {
|
||||||
|
config_log.info("Compiling client functions");
|
||||||
|
state->function_code_index.reset(new FunctionCodeIndex("system/ppc"));
|
||||||
|
config_log.info("Loading DOL files");
|
||||||
|
state->dol_file_index.reset(new DOLFileIndex("system/dol"));
|
||||||
|
} else {
|
||||||
|
state->function_code_index.reset(new FunctionCodeIndex());
|
||||||
|
state->dol_file_index.reset(new DOLFileIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
config_log.info("Creating menus");
|
||||||
|
state->create_menus(config_json);
|
||||||
|
|
||||||
|
if (replay_log_filename) {
|
||||||
|
state->allow_saving = false;
|
||||||
|
state->license_manager->set_autosave(false);
|
||||||
|
config_log.info("Saving disabled because this is a replay session");
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<DNSServer> dns_server;
|
||||||
|
if (state->dns_server_port && !replay_log_filename) {
|
||||||
|
config_log.info("Starting DNS server");
|
||||||
|
dns_server.reset(new DNSServer(base, state->local_address,
|
||||||
|
state->external_address));
|
||||||
|
dns_server->listen("", state->dns_server_port);
|
||||||
|
} else {
|
||||||
|
config_log.info("DNS server is disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_ptr<Shell> shell;
|
||||||
|
shared_ptr<ReplaySession> replay_session;
|
||||||
|
shared_ptr<IPStackSimulator> ip_stack_simulator;
|
||||||
|
if (behavior == Behavior::REPLAY_LOG) {
|
||||||
|
config_log.info("Starting proxy server");
|
||||||
|
state->proxy_server.reset(new ProxyServer(base, state));
|
||||||
|
config_log.info("Starting game server");
|
||||||
|
state->game_server.reset(new Server(base, state));
|
||||||
|
|
||||||
|
auto f = fopen_unique(replay_log_filename, "rt");
|
||||||
|
replay_session.reset(new ReplaySession(base, f.get(), state));
|
||||||
|
replay_session->start();
|
||||||
|
|
||||||
|
} else if (behavior == Behavior::RUN_SERVER) {
|
||||||
|
config_log.info("Opening sockets");
|
||||||
|
for (const auto& it : state->name_to_port_config) {
|
||||||
|
const auto& pc = it.second;
|
||||||
|
if (pc->behavior == ServerBehavior::PROXY_SERVER) {
|
||||||
|
if (!state->proxy_server.get()) {
|
||||||
|
config_log.info("Starting proxy server");
|
||||||
|
state->proxy_server.reset(new ProxyServer(base, state));
|
||||||
|
}
|
||||||
|
if (state->proxy_server.get()) {
|
||||||
|
// For PC and GC, proxy sessions are dynamically created when a client
|
||||||
|
// picks a destination from the menu. For patch and BB clients, there's
|
||||||
|
// no way to ask the client which destination they want, so only one
|
||||||
|
// destination is supported, and we have to manually specify the
|
||||||
|
// destination netloc here.
|
||||||
|
if (pc->version == GameVersion::PATCH) {
|
||||||
|
struct sockaddr_storage ss = make_sockaddr_storage(
|
||||||
|
state->proxy_destination_patch.first,
|
||||||
|
state->proxy_destination_patch.second).first;
|
||||||
|
state->proxy_server->listen(pc->port, pc->version, &ss);
|
||||||
|
} else if (pc->version == GameVersion::BB) {
|
||||||
|
struct sockaddr_storage ss = make_sockaddr_storage(
|
||||||
|
state->proxy_destination_bb.first,
|
||||||
|
state->proxy_destination_bb.second).first;
|
||||||
|
state->proxy_server->listen(pc->port, pc->version, &ss);
|
||||||
|
} else {
|
||||||
|
state->proxy_server->listen(pc->port, pc->version);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
state->proxy_server->listen(pc->port, pc->version);
|
if (!state->game_server.get()) {
|
||||||
|
config_log.info("Starting game server");
|
||||||
|
state->game_server.reset(new Server(base, state));
|
||||||
|
}
|
||||||
|
string spec = string_printf("T-%hu-%s-%s-%s",
|
||||||
|
pc->port, name_for_version(pc->version), pc->name.c_str(),
|
||||||
|
name_for_server_behavior(pc->behavior));
|
||||||
|
state->game_server->listen(spec, "", pc->port, pc->version, pc->behavior);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (!state->game_server.get()) {
|
if (!state->ip_stack_addresses.empty()) {
|
||||||
config_log.info("Starting game server");
|
config_log.info("Starting IP stack simulator");
|
||||||
state->game_server.reset(new Server(base, state));
|
ip_stack_simulator.reset(new IPStackSimulator(base, state));
|
||||||
|
for (const auto& it : state->ip_stack_addresses) {
|
||||||
|
auto netloc = parse_netloc(it);
|
||||||
|
ip_stack_simulator->listen(netloc.first, netloc.second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
string spec = string_printf("T-%hu-%s-%s-%s",
|
|
||||||
pc->port, name_for_version(pc->version), pc->name.c_str(),
|
} else {
|
||||||
name_for_server_behavior(pc->behavior));
|
throw logic_error("invalid behavior");
|
||||||
state->game_server->listen(spec, "", pc->port, pc->version, pc->behavior);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!state->username.empty()) {
|
||||||
|
config_log.info("Switching to user %s", state->username.c_str());
|
||||||
|
drop_privileges(state->username);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool should_run_shell;
|
||||||
|
if (state->run_shell_behavior == ServerState::RunShellBehavior::DEFAULT) {
|
||||||
|
should_run_shell = isatty(fileno(stdin));
|
||||||
|
} else if (state->run_shell_behavior == ServerState::RunShellBehavior::ALWAYS) {
|
||||||
|
should_run_shell = true;
|
||||||
|
} else {
|
||||||
|
should_run_shell = false;
|
||||||
|
}
|
||||||
|
if (should_run_shell) {
|
||||||
|
should_run_shell = !replay_session.get();
|
||||||
|
}
|
||||||
|
if (should_run_shell) {
|
||||||
|
shell.reset(new ServerShell(base, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
config_log.info("Ready");
|
||||||
|
event_base_dispatch(base.get());
|
||||||
|
|
||||||
|
config_log.info("Normal shutdown");
|
||||||
|
state->proxy_server.reset(); // Break reference cycle
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state->ip_stack_addresses.empty()) {
|
default:
|
||||||
config_log.info("Starting IP stack simulator");
|
throw logic_error("invalid behavior");
|
||||||
ip_stack_simulator.reset(new IPStackSimulator(base, state));
|
|
||||||
for (const auto& it : state->ip_stack_addresses) {
|
|
||||||
auto netloc = parse_netloc(it);
|
|
||||||
ip_stack_simulator->listen(netloc.first, netloc.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw logic_error("invalid behavior");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state->username.empty()) {
|
|
||||||
config_log.info("Switching to user %s", state->username.c_str());
|
|
||||||
drop_privileges(state->username);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool should_run_shell;
|
|
||||||
if (state->run_shell_behavior == ServerState::RunShellBehavior::DEFAULT) {
|
|
||||||
should_run_shell = isatty(fileno(stdin));
|
|
||||||
} else if (state->run_shell_behavior == ServerState::RunShellBehavior::ALWAYS) {
|
|
||||||
should_run_shell = true;
|
|
||||||
} else {
|
|
||||||
should_run_shell = false;
|
|
||||||
}
|
|
||||||
if (should_run_shell) {
|
|
||||||
should_run_shell = !replay_session.get();
|
|
||||||
}
|
|
||||||
if (should_run_shell) {
|
|
||||||
shell.reset(new ServerShell(base, state));
|
|
||||||
}
|
|
||||||
|
|
||||||
config_log.info("Ready");
|
|
||||||
event_base_dispatch(base.get());
|
|
||||||
|
|
||||||
config_log.info("Normal shutdown");
|
|
||||||
state->proxy_server.reset(); // Break reference cycle
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-10
@@ -538,24 +538,19 @@ void ProxyServer::LinkedSession::resume_inner(
|
|||||||
void ProxyServer::LinkedSession::connect() {
|
void ProxyServer::LinkedSession::connect() {
|
||||||
// Connect to the remote server. The command handlers will do the login steps
|
// Connect to the remote server. The command handlers will do the login steps
|
||||||
// and set up forwarding
|
// and set up forwarding
|
||||||
struct sockaddr_storage local_ss;
|
const struct sockaddr_in* dest_sin = reinterpret_cast<const sockaddr_in*>(
|
||||||
struct sockaddr_in* local_sin = reinterpret_cast<struct sockaddr_in*>(&local_ss);
|
&this->next_destination);
|
||||||
memset(local_sin, 0, sizeof(*local_sin));
|
|
||||||
local_sin->sin_family = AF_INET;
|
|
||||||
const struct sockaddr_in* dest_sin = reinterpret_cast<const sockaddr_in*>(&this->next_destination);
|
|
||||||
if (dest_sin->sin_family != AF_INET) {
|
if (dest_sin->sin_family != AF_INET) {
|
||||||
throw logic_error("ss not AF_INET");
|
throw runtime_error("destination is not AF_INET");
|
||||||
}
|
}
|
||||||
local_sin->sin_port = dest_sin->sin_port;
|
|
||||||
local_sin->sin_addr.s_addr = dest_sin->sin_addr.s_addr;
|
|
||||||
|
|
||||||
string netloc_str = render_sockaddr_storage(local_ss);
|
string netloc_str = render_sockaddr_storage(this->next_destination);
|
||||||
this->log.info("Connecting to %s", netloc_str.c_str());
|
this->log.info("Connecting to %s", netloc_str.c_str());
|
||||||
|
|
||||||
this->server_channel.set_bufferevent(bufferevent_socket_new(
|
this->server_channel.set_bufferevent(bufferevent_socket_new(
|
||||||
this->server->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS));
|
this->server->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS));
|
||||||
if (bufferevent_socket_connect(this->server_channel.bev.get(),
|
if (bufferevent_socket_connect(this->server_channel.bev.get(),
|
||||||
reinterpret_cast<const sockaddr*>(local_sin), sizeof(*local_sin)) != 0) {
|
reinterpret_cast<const sockaddr*>(dest_sin), sizeof(*dest_sin)) != 0) {
|
||||||
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
throw runtime_error(string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-9
@@ -53,25 +53,23 @@ void send_command(shared_ptr<ServerState> s, uint16_t command, uint32_t flag,
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename HeaderT>
|
template <typename HeaderT>
|
||||||
void send_command_with_header_t(shared_ptr<Client> c, const void* data,
|
void send_command_with_header_t(Channel& ch, const void* data, size_t size) {
|
||||||
size_t size) {
|
|
||||||
const HeaderT* header = reinterpret_cast<const HeaderT*>(data);
|
const HeaderT* header = reinterpret_cast<const HeaderT*>(data);
|
||||||
send_command(c, header->command, header->flag, header + 1, size - sizeof(HeaderT));
|
ch.send(header->command, header->flag, header + 1, size - sizeof(HeaderT));
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_command_with_header(shared_ptr<Client> c, const void* data,
|
void send_command_with_header(Channel& ch, const void* data, size_t size) {
|
||||||
size_t size) {
|
switch (ch.version) {
|
||||||
switch (c->version) {
|
|
||||||
case GameVersion::GC:
|
case GameVersion::GC:
|
||||||
case GameVersion::DC:
|
case GameVersion::DC:
|
||||||
send_command_with_header_t<PSOCommandHeaderDCGC>(c, data, size);
|
send_command_with_header_t<PSOCommandHeaderDCGC>(ch, data, size);
|
||||||
break;
|
break;
|
||||||
case GameVersion::PC:
|
case GameVersion::PC:
|
||||||
case GameVersion::PATCH:
|
case GameVersion::PATCH:
|
||||||
send_command_with_header_t<PSOCommandHeaderPC>(c, data, size);
|
send_command_with_header_t<PSOCommandHeaderPC>(ch, data, size);
|
||||||
break;
|
break;
|
||||||
case GameVersion::BB:
|
case GameVersion::BB:
|
||||||
send_command_with_header_t<PSOCommandHeaderBB>(c, data, size);
|
send_command_with_header_t<PSOCommandHeaderBB>(ch, data, size);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unimplemented game version in send_command_with_header");
|
throw logic_error("unimplemented game version in send_command_with_header");
|
||||||
|
|||||||
+1
-5
@@ -29,9 +29,6 @@
|
|||||||
// pointer is given but size is accidentally not given (e.g. if the type of
|
// pointer is given but size is accidentally not given (e.g. if the type of
|
||||||
// data in the calling function is changed from string to void*).
|
// data in the calling function is changed from string to void*).
|
||||||
|
|
||||||
void send_command(Channel& ch, uint16_t command, uint32_t flag,
|
|
||||||
const void* data, size_t size);
|
|
||||||
|
|
||||||
void send_command(std::shared_ptr<Client> c, uint16_t command,
|
void send_command(std::shared_ptr<Client> c, uint16_t command,
|
||||||
uint32_t flag, const void* data, size_t size);
|
uint32_t flag, const void* data, size_t size);
|
||||||
|
|
||||||
@@ -91,8 +88,7 @@ void send_command_t_vt(std::shared_ptr<TargetT> c, uint16_t command,
|
|||||||
send_command(c, command, flag, all_data.data(), all_data.size());
|
send_command(c, command, flag, all_data.data(), all_data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_command_with_header(std::shared_ptr<Client> c, const void* data,
|
void send_command_with_header(Channel& c, const void* data, size_t size);
|
||||||
size_t size);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -17,7 +17,7 @@ using namespace std;
|
|||||||
ServerShell::ServerShell(
|
ServerShell::ServerShell(
|
||||||
shared_ptr<struct event_base> base,
|
shared_ptr<struct event_base> base,
|
||||||
shared_ptr<ServerState> state)
|
shared_ptr<ServerState> state)
|
||||||
: Shell(base, state) { }
|
: Shell(base), state(state) { }
|
||||||
|
|
||||||
void ServerShell::print_prompt() {
|
void ServerShell::print_prompt() {
|
||||||
fwritex(stdout, Shell::PROMPT);
|
fwritex(stdout, Shell::PROMPT);
|
||||||
@@ -305,7 +305,7 @@ Proxy commands (these will only work when exactly one client is connected):\n\
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto c = this->state->game_server->get_client();
|
auto c = this->state->game_server->get_client();
|
||||||
send_command_with_header(c, data.data(), data.size());
|
send_command_with_header(c->channel, data.data(), data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if ((command_name == "chat") || (command_name == "dchat")) {
|
} else if ((command_name == "chat") || (command_name == "dchat")) {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ public:
|
|||||||
ServerShell& operator=(ServerShell&&) = delete;
|
ServerShell& operator=(ServerShell&&) = delete;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
std::shared_ptr<ServerState> state;
|
||||||
|
|
||||||
std::shared_ptr<ProxyServer::LinkedSession> get_proxy_session();
|
std::shared_ptr<ProxyServer::LinkedSession> get_proxy_session();
|
||||||
|
|
||||||
virtual void print_prompt();
|
virtual void print_prompt();
|
||||||
|
|||||||
+3
-3
@@ -18,15 +18,15 @@ Shell::exit_shell::exit_shell() : runtime_error("shell exited") { }
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
Shell::Shell(std::shared_ptr<struct event_base> base,
|
Shell::Shell(std::shared_ptr<struct event_base> base)
|
||||||
std::shared_ptr<ServerState> state) : base(base), state(state),
|
: base(base),
|
||||||
read_event(event_new(this->base.get(), 0, EV_READ | EV_PERSIST,
|
read_event(event_new(this->base.get(), 0, EV_READ | EV_PERSIST,
|
||||||
&Shell::dispatch_read_stdin, this), event_free),
|
&Shell::dispatch_read_stdin, this), event_free),
|
||||||
prompt_event(event_new(this->base.get(), 0, EV_TIMEOUT,
|
prompt_event(event_new(this->base.get(), 0, EV_TIMEOUT,
|
||||||
&Shell::dispatch_print_prompt, this), event_free) {
|
&Shell::dispatch_print_prompt, this), event_free) {
|
||||||
event_add(this->read_event.get(), nullptr);
|
event_add(this->read_event.get(), nullptr);
|
||||||
|
|
||||||
// schedule an event to print the prompt as soon as the event loop starts
|
// Schedule an event to print the prompt as soon as the event loop starts
|
||||||
// running. we do this so the prompt appears after any initialization
|
// running. we do this so the prompt appears after any initialization
|
||||||
// messages that come after starting the shell
|
// messages that come after starting the shell
|
||||||
struct timeval tv = {0, 0};
|
struct timeval tv = {0, 0};
|
||||||
|
|||||||
+1
-3
@@ -13,8 +13,7 @@
|
|||||||
|
|
||||||
class Shell {
|
class Shell {
|
||||||
public:
|
public:
|
||||||
Shell(std::shared_ptr<struct event_base> base,
|
Shell(std::shared_ptr<struct event_base> base);
|
||||||
std::shared_ptr<ServerState> state);
|
|
||||||
virtual ~Shell() = default;
|
virtual ~Shell() = default;
|
||||||
Shell(const Shell&) = delete;
|
Shell(const Shell&) = delete;
|
||||||
Shell(Shell&&) = delete;
|
Shell(Shell&&) = delete;
|
||||||
@@ -25,7 +24,6 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<struct event_base> base;
|
std::shared_ptr<struct event_base> base;
|
||||||
std::shared_ptr<ServerState> state;
|
|
||||||
std::unique_ptr<struct event, void (*)(struct event*)> read_event;
|
std::unique_ptr<struct event, void (*)(struct event*)> read_event;
|
||||||
std::unique_ptr<struct event, void (*)(struct event*)> prompt_event;
|
std::unique_ptr<struct event, void (*)(struct event*)> prompt_event;
|
||||||
Poll poll;
|
Poll poll;
|
||||||
|
|||||||
Reference in New Issue
Block a user