add some new proxy commands
This commit is contained in:
+113
-15
@@ -16,6 +16,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <phosg/Encoding.hh>
|
#include <phosg/Encoding.hh>
|
||||||
|
#include <phosg/Filesystem.hh>
|
||||||
#include <phosg/Network.hh>
|
#include <phosg/Network.hh>
|
||||||
#include <phosg/Strings.hh>
|
#include <phosg/Strings.hh>
|
||||||
#include <phosg/Time.hh>
|
#include <phosg/Time.hh>
|
||||||
@@ -34,12 +35,17 @@ static void flush_and_free_bufferevent(struct bufferevent* bev) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
ProxyServer::ProxyServer(shared_ptr<struct event_base> base,
|
ProxyServer::ProxyServer(
|
||||||
const struct sockaddr_storage& initial_destination, GameVersion version) :
|
shared_ptr<struct event_base> base,
|
||||||
base(base), client_bev(nullptr, flush_and_free_bufferevent),
|
const struct sockaddr_storage& initial_destination,
|
||||||
|
GameVersion version)
|
||||||
|
: base(base),
|
||||||
|
client_bev(nullptr, flush_and_free_bufferevent),
|
||||||
server_bev(nullptr, flush_and_free_bufferevent),
|
server_bev(nullptr, flush_and_free_bufferevent),
|
||||||
next_destination(initial_destination), version(version),
|
next_destination(initial_destination),
|
||||||
header_size((version == GameVersion::BB) ? 8 : 4) {
|
version(version),
|
||||||
|
header_size((version == GameVersion::BB) ? 8 : 4),
|
||||||
|
save_quests(false) {
|
||||||
memset(&this->client_input_header, 0, sizeof(this->client_input_header));
|
memset(&this->client_input_header, 0, sizeof(this->client_input_header));
|
||||||
memset(&this->server_input_header, 0, sizeof(this->server_input_header));
|
memset(&this->server_input_header, 0, sizeof(this->server_input_header));
|
||||||
}
|
}
|
||||||
@@ -53,6 +59,21 @@ void ProxyServer::listen(int port) {
|
|||||||
this->listeners.emplace(port, move(listener));
|
this->listeners.emplace(port, move(listener));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProxyServer::set_save_quests(bool save_quests) {
|
||||||
|
this->save_quests = save_quests;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ProxyServer::SavingQuestFile::SavingQuestFile(
|
||||||
|
const std::string& basename,
|
||||||
|
const std::string& output_filename,
|
||||||
|
uint32_t remaining_bytes)
|
||||||
|
: basename(basename),
|
||||||
|
output_filename(output_filename),
|
||||||
|
remaining_bytes(remaining_bytes),
|
||||||
|
f(fopen_unique(this->output_filename, "wb")) { }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void ProxyServer::send_to_client(const std::string& data) {
|
void ProxyServer::send_to_client(const std::string& data) {
|
||||||
@@ -274,14 +295,10 @@ void ProxyServer::receive_and_process_commands(bool from_server) {
|
|||||||
if (this->get_size_field(input_header) == 0) {
|
if (this->get_size_field(input_header) == 0) {
|
||||||
ssize_t bytes = evbuffer_copyout(source_buf, input_header,
|
ssize_t bytes = evbuffer_copyout(source_buf, input_header,
|
||||||
this->header_size);
|
this->header_size);
|
||||||
//log(INFO, "[ProxyServer-debug] %zd bytes copied for header", bytes);
|
|
||||||
if (bytes < static_cast<ssize_t>(this->header_size)) {
|
if (bytes < static_cast<ssize_t>(this->header_size)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
//log(INFO, "[ProxyServer-debug] Received encrypted header");
|
|
||||||
//print_data(stderr, input_header, this->header_size);
|
|
||||||
|
|
||||||
if (source_crypt) {
|
if (source_crypt) {
|
||||||
source_crypt->decrypt(input_header, this->header_size);
|
source_crypt->decrypt(input_header, this->header_size);
|
||||||
}
|
}
|
||||||
@@ -289,7 +306,6 @@ void ProxyServer::receive_and_process_commands(bool from_server) {
|
|||||||
|
|
||||||
size_t command_size = this->get_size_field(input_header);
|
size_t command_size = this->get_size_field(input_header);
|
||||||
if (evbuffer_get_length(source_buf) < command_size) {
|
if (evbuffer_get_length(source_buf) < command_size) {
|
||||||
//log(INFO, "[ProxyServer-debug] Insufficient data for command (%zX/%hX bytes)", evbuffer_get_length(source_buf), this->get_size_field(input_header));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,13 +314,8 @@ void ProxyServer::receive_and_process_commands(bool from_server) {
|
|||||||
if (bytes < static_cast<ssize_t>(command_size)) {
|
if (bytes < static_cast<ssize_t>(command_size)) {
|
||||||
throw logic_error("enough bytes available, but could not remove them");
|
throw logic_error("enough bytes available, but could not remove them");
|
||||||
}
|
}
|
||||||
//log(INFO, "[ProxyServer-debug] Read command (%zX bytes)", bytes);
|
|
||||||
// overwrite the header with the already-decrypted header
|
|
||||||
memcpy(command.data(), input_header, this->header_size);
|
memcpy(command.data(), input_header, this->header_size);
|
||||||
|
|
||||||
//log(INFO, "[ProxyServer-debug] Received encrypted command with pre-decrypted header");
|
|
||||||
//print_data(stderr, command);
|
|
||||||
|
|
||||||
if (source_crypt) {
|
if (source_crypt) {
|
||||||
source_crypt->decrypt(command.data() + this->header_size,
|
source_crypt->decrypt(command.data() + this->header_size,
|
||||||
command_size - this->header_size);
|
command_size - this->header_size);
|
||||||
@@ -395,6 +406,93 @@ void ProxyServer::receive_and_process_commands(bool from_server) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 0x44:
|
||||||
|
case 0xA6: { // open quest file
|
||||||
|
if (!this->save_quests) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_download_quest = this->get_command_field(input_header) == 0xA6;
|
||||||
|
|
||||||
|
struct OpenFileCommand {
|
||||||
|
char name[0x20];
|
||||||
|
uint16_t unused;
|
||||||
|
uint16_t flags;
|
||||||
|
char filename[0x10];
|
||||||
|
uint32_t file_size;
|
||||||
|
};
|
||||||
|
if (command.size() < sizeof(OpenFileCommand)) {
|
||||||
|
log(WARNING, "[ProxyServer] Open file command is too small");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto* cmd = reinterpret_cast<const OpenFileCommand*>(command.data() + this->header_size);
|
||||||
|
|
||||||
|
string output_filename = string_printf("%s.%s.%" PRIu64,
|
||||||
|
cmd->filename, is_download_quest ? "download" : "online", now());
|
||||||
|
for (size_t x = 0; x < output_filename.size(); x++) {
|
||||||
|
if (output_filename[x] < 0x20 || output_filename[x] > 0x7E || output_filename[x] == '/') {
|
||||||
|
output_filename[x] = '_';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (output_filename[0] == '.') {
|
||||||
|
output_filename[0] = '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
SavingQuestFile sqf(cmd->filename, output_filename, cmd->file_size);
|
||||||
|
this->saving_quest_files.emplace(cmd->filename, move(sqf));
|
||||||
|
log(INFO, "[ProxyServer] Opened quest file %s", output_filename.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x13:
|
||||||
|
case 0xA7: { // quest data segment
|
||||||
|
if (!this->save_quests) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WriteFileCommand {
|
||||||
|
char filename[0x10];
|
||||||
|
uint8_t data[0x400];
|
||||||
|
uint32_t data_size;
|
||||||
|
};
|
||||||
|
if (command.size() < sizeof(WriteFileCommand)) {
|
||||||
|
log(WARNING, "[ProxyServer] Write file command is too small");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto* cmd = reinterpret_cast<const WriteFileCommand*>(command.data() + this->header_size);
|
||||||
|
|
||||||
|
SavingQuestFile* sqf = nullptr;
|
||||||
|
try {
|
||||||
|
sqf = &this->saving_quest_files.at(cmd->filename);
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
log(WARNING, "[ProxyServer] Can\'t find saving quest file %s",
|
||||||
|
cmd->filename);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t bytes_to_write = cmd->data_size;
|
||||||
|
if (bytes_to_write > 0x400) {
|
||||||
|
log(WARNING, "[ProxyServer] Chunk data size is invalid; truncating to 0x400");
|
||||||
|
bytes_to_write = 0x400;
|
||||||
|
}
|
||||||
|
|
||||||
|
log(INFO, "[ProxyServer] Writing %zu bytes to %s", bytes_to_write,
|
||||||
|
sqf->output_filename.c_str());
|
||||||
|
fwritex(sqf->f.get(), cmd->data, bytes_to_write);
|
||||||
|
if (bytes_to_write > sqf->remaining_bytes) {
|
||||||
|
log(WARNING, "[ProxyServer] Chunk size extends beyond original file size; file may be truncated");
|
||||||
|
sqf->remaining_bytes = 0;
|
||||||
|
} else {
|
||||||
|
sqf->remaining_bytes -= bytes_to_write;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sqf->remaining_bytes == 0) {
|
||||||
|
log(INFO, "[ProxyServer] File %s is complete", sqf->output_filename.c_str());
|
||||||
|
this->saving_quest_files.erase(cmd->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <event2/event.h>
|
#include <event2/event.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -29,6 +30,8 @@ public:
|
|||||||
void send_to_client(const std::string& data);
|
void send_to_client(const std::string& data);
|
||||||
void send_to_server(const std::string& data);
|
void send_to_server(const std::string& data);
|
||||||
|
|
||||||
|
void set_save_quests(bool save_quests);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<struct event_base> base;
|
std::shared_ptr<struct event_base> base;
|
||||||
std::map<int, std::unique_ptr<struct evconnlistener, void(*)(struct evconnlistener*)>> listeners;
|
std::map<int, std::unique_ptr<struct evconnlistener, void(*)(struct evconnlistener*)>> listeners;
|
||||||
@@ -46,6 +49,20 @@ private:
|
|||||||
std::shared_ptr<PSOEncryption> server_input_crypt;
|
std::shared_ptr<PSOEncryption> server_input_crypt;
|
||||||
std::shared_ptr<PSOEncryption> server_output_crypt;
|
std::shared_ptr<PSOEncryption> server_output_crypt;
|
||||||
|
|
||||||
|
struct SavingQuestFile {
|
||||||
|
std::string basename;
|
||||||
|
std::string output_filename;
|
||||||
|
uint32_t remaining_bytes;
|
||||||
|
std::unique_ptr<FILE, std::function<void(FILE*)>> f;
|
||||||
|
|
||||||
|
SavingQuestFile(
|
||||||
|
const std::string& basename,
|
||||||
|
const std::string& output_filename,
|
||||||
|
uint32_t remaining_bytes);
|
||||||
|
};
|
||||||
|
bool save_quests;
|
||||||
|
std::unordered_map<std::string, SavingQuestFile> saving_quest_files;
|
||||||
|
|
||||||
void send_to_end(const std::string& data, bool to_server);
|
void send_to_end(const std::string& data, bool to_server);
|
||||||
|
|
||||||
static void dispatch_on_listen_accept(struct evconnlistener* listener,
|
static void dispatch_on_listen_accept(struct evconnlistener* listener,
|
||||||
|
|||||||
+40
-1
@@ -39,10 +39,16 @@ Commands:\n\
|
|||||||
Send a chat message to the server.\n\
|
Send a chat message to the server.\n\
|
||||||
dchat <data>\n\
|
dchat <data>\n\
|
||||||
Send a chat message to the server with arbitrary data in it.\n\
|
Send a chat message to the server with arbitrary data in it.\n\
|
||||||
|
info-board <text>\n\
|
||||||
|
Set your info board contents.\n\
|
||||||
|
info-board-data <data>\n\
|
||||||
|
Set your info board contents with arbitrary data.\n\
|
||||||
marker <color-id>\n\
|
marker <color-id>\n\
|
||||||
Send a lobby marker message to the server.\n\
|
Send a lobby marker message to the server.\n\
|
||||||
event <event-id>\n\
|
event <event-id>\n\
|
||||||
Send a lobby event update to yourself.\n\
|
Send a lobby event update to yourself.\n\
|
||||||
|
ship\n\
|
||||||
|
Request the ship select menu from the server.\n\
|
||||||
");
|
");
|
||||||
|
|
||||||
} else if ((command_name == "sc") || (command_name == "ss")) {
|
} else if ((command_name == "sc") || (command_name == "ss")) {
|
||||||
@@ -97,10 +103,43 @@ Commands:\n\
|
|||||||
string data("\xDA\x00\x04\x00", 4);
|
string data("\xDA\x00\x04\x00", 4);
|
||||||
data[1] = stod(command_args);
|
data[1] = stod(command_args);
|
||||||
|
|
||||||
log(INFO, "server (from proxy):");
|
log(INFO, "Server (from proxy):");
|
||||||
print_data(stderr, data);
|
print_data(stderr, data);
|
||||||
this->proxy_server->send_to_client(data);
|
this->proxy_server->send_to_client(data);
|
||||||
|
|
||||||
|
} else if (command_name == "ship") {
|
||||||
|
static const string data("\xA0\x00\x04\x00", 4);
|
||||||
|
|
||||||
|
log(INFO, "Server (from proxy):");
|
||||||
|
print_data(stderr, data);
|
||||||
|
this->proxy_server->send_to_server(data);
|
||||||
|
|
||||||
|
} else if ((command_name == "info-board") || (command_name == "info-board-data")) {
|
||||||
|
string data(4, '\0');
|
||||||
|
data[0] = 0xD9;
|
||||||
|
if (command_name == "info-board-data") {
|
||||||
|
data += parse_data_string(command_args);
|
||||||
|
} else {
|
||||||
|
data += command_args;
|
||||||
|
}
|
||||||
|
data.push_back('\0');
|
||||||
|
data.resize((data.size() + 3) & (~3));
|
||||||
|
uint16_t* size_field = reinterpret_cast<uint16_t*>(data.data() + 2);
|
||||||
|
*size_field = data.size();
|
||||||
|
|
||||||
|
log(INFO, "Client (from proxy):");
|
||||||
|
print_data(stderr, data);
|
||||||
|
this->proxy_server->send_to_server(data);
|
||||||
|
|
||||||
|
} else if (command_name == "set-save-quests") {
|
||||||
|
if (command_args == "on") {
|
||||||
|
this->proxy_server->set_save_quests(true);
|
||||||
|
} else if (command_args == "off") {
|
||||||
|
this->proxy_server->set_save_quests(false);
|
||||||
|
} else {
|
||||||
|
throw invalid_argument("argument must be \"on\" or \"off\"");
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
throw invalid_argument("unknown command; try \'help\'");
|
throw invalid_argument("unknown command; try \'help\'");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user