From 7ec3bb0f6f58ed89859a9e2eda9014fa988120e2 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Thu, 17 Mar 2022 19:54:42 -0700 Subject: [PATCH] add some new proxy commands --- src/ProxyServer.cc | 128 +++++++++++++++++++++++++++++++++++++++------ src/ProxyServer.hh | 17 ++++++ src/ProxyShell.cc | 41 ++++++++++++++- 3 files changed, 170 insertions(+), 16 deletions(-) diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 13d8d4ef..9dc32950 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -34,12 +35,17 @@ static void flush_and_free_bufferevent(struct bufferevent* bev) { -ProxyServer::ProxyServer(shared_ptr base, - const struct sockaddr_storage& initial_destination, GameVersion version) : - base(base), client_bev(nullptr, flush_and_free_bufferevent), +ProxyServer::ProxyServer( + shared_ptr base, + const struct sockaddr_storage& initial_destination, + GameVersion version) + : base(base), + client_bev(nullptr, flush_and_free_bufferevent), server_bev(nullptr, flush_and_free_bufferevent), - next_destination(initial_destination), version(version), - header_size((version == GameVersion::BB) ? 8 : 4) { + next_destination(initial_destination), + 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->server_input_header, 0, sizeof(this->server_input_header)); } @@ -53,6 +59,21 @@ void ProxyServer::listen(int port) { 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) { @@ -274,14 +295,10 @@ void ProxyServer::receive_and_process_commands(bool from_server) { if (this->get_size_field(input_header) == 0) { ssize_t bytes = evbuffer_copyout(source_buf, input_header, this->header_size); - //log(INFO, "[ProxyServer-debug] %zd bytes copied for header", bytes); if (bytes < static_cast(this->header_size)) { break; } - //log(INFO, "[ProxyServer-debug] Received encrypted header"); - //print_data(stderr, input_header, this->header_size); - if (source_crypt) { 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); 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; } @@ -298,13 +314,8 @@ void ProxyServer::receive_and_process_commands(bool from_server) { if (bytes < static_cast(command_size)) { 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); - //log(INFO, "[ProxyServer-debug] Received encrypted command with pre-decrypted header"); - //print_data(stderr, command); - if (source_crypt) { source_crypt->decrypt(command.data() + this->header_size, command_size - this->header_size); @@ -395,6 +406,93 @@ void ProxyServer::receive_and_process_commands(bool from_server) { } 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(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(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; + } } } diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index fbad2087..2e505f60 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,8 @@ public: void send_to_client(const std::string& data); void send_to_server(const std::string& data); + void set_save_quests(bool save_quests); + private: std::shared_ptr base; std::map> listeners; @@ -46,6 +49,20 @@ private: std::shared_ptr server_input_crypt; std::shared_ptr server_output_crypt; + struct SavingQuestFile { + std::string basename; + std::string output_filename; + uint32_t remaining_bytes; + std::unique_ptr> f; + + SavingQuestFile( + const std::string& basename, + const std::string& output_filename, + uint32_t remaining_bytes); + }; + bool save_quests; + std::unordered_map saving_quest_files; + void send_to_end(const std::string& data, bool to_server); static void dispatch_on_listen_accept(struct evconnlistener* listener, diff --git a/src/ProxyShell.cc b/src/ProxyShell.cc index 4f10964a..873516c5 100644 --- a/src/ProxyShell.cc +++ b/src/ProxyShell.cc @@ -39,10 +39,16 @@ Commands:\n\ Send a chat message to the server.\n\ dchat \n\ Send a chat message to the server with arbitrary data in it.\n\ + info-board \n\ + Set your info board contents.\n\ + info-board-data \n\ + Set your info board contents with arbitrary data.\n\ marker \n\ Send a lobby marker message to the server.\n\ event \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")) { @@ -97,10 +103,43 @@ Commands:\n\ string data("\xDA\x00\x04\x00", 4); data[1] = stod(command_args); - log(INFO, "server (from proxy):"); + log(INFO, "Server (from proxy):"); print_data(stderr, data); this->proxy_server->send_to_client(data); + } else if (command_name == "ship") { + static const string data("\xA0\x00\x04\x00", 4); + + log(INFO, "Server (from proxy):"); + print_data(stderr, data); + this->proxy_server->send_to_server(data); + + } else if ((command_name == "info-board") || (command_name == "info-board-data")) { + string data(4, '\0'); + data[0] = 0xD9; + if (command_name == "info-board-data") { + data += parse_data_string(command_args); + } else { + data += command_args; + } + data.push_back('\0'); + data.resize((data.size() + 3) & (~3)); + uint16_t* size_field = reinterpret_cast(data.data() + 2); + *size_field = data.size(); + + log(INFO, "Client (from proxy):"); + print_data(stderr, data); + this->proxy_server->send_to_server(data); + + } else if (command_name == "set-save-quests") { + if (command_args == "on") { + this->proxy_server->set_save_quests(true); + } else if (command_args == "off") { + this->proxy_server->set_save_quests(false); + } else { + throw invalid_argument("argument must be \"on\" or \"off\""); + } + } else { throw invalid_argument("unknown command; try \'help\'"); }