From 9d770ef9b2205771f69fb73ebcfb92d5a2e81083 Mon Sep 17 00:00:00 2001 From: Ritchie Cunningham Date: Sat, 11 Oct 2025 19:05:14 +0100 Subject: [PATCH] [Refactor] Implment structured network protocol. --- client/src/game_state.cpp | 169 +++++++++++++++++----------- client/src/game_state.h | 6 + client/src/terminal.cpp | 8 +- client/src/terminal.h | 7 +- client/src/ui/desktop.cpp | 16 ++- client/src/ui/desktop.h | 7 +- client/src/ui/ui_window.h | 1 + common/src/command_processor.cpp | 5 +- common/src/net/message_protocol.cpp | 37 ++++++ common/src/net/message_protocol.h | 53 +++++++++ server/src/network_manager.cpp | 137 +++++++++++----------- 11 files changed, 292 insertions(+), 154 deletions(-) create mode 100644 common/src/net/message_protocol.cpp create mode 100644 common/src/net/message_protocol.h diff --git a/client/src/game_state.cpp b/client/src/game_state.cpp index 91777f2..0846eff 100644 --- a/client/src/game_state.cpp +++ b/client/src/game_state.cpp @@ -6,6 +6,8 @@ #include "gfx/types.h" #include "net/constants.h" +#include "net/message_protocol.h" +#include "client_network.h" #include "network_manager.h" #include "game_state.h" #include "terminal.h" @@ -20,9 +22,9 @@ #include "debug/debug_overlay.h" void GameState::_init_desktop(void) { - _desktop = std::make_unique(_screen_width, _screen_height, _network.get()); + _desktop = std::make_unique(_screen_width, _screen_height, this); - auto term = std::make_unique(_network.get()); + auto term = std::make_unique(this); auto term_window = std::make_unique("Terminal", 100, 100, 800, 500); term_window->set_content(std::move(term)); _desktop->add_window(std::move(term_window)); @@ -149,11 +151,11 @@ void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts if(_login_screen->is_new_account_mode()) { std::string hostname = _login_screen->get_hostname(); - std::string msg = "C_ACC::" + username + "::" + password + "::" + hostname; - _network->send(msg); + _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_CREATE_ACCOUNT, + {username, password, hostname})); } else { - std::string msg = "LOGIN::" + username + "::" + password; - _network->send(msg); + _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_LOGIN, + {username, password})); } _login_screen->clear_login_attempt(); /* Try to spam my server now b.tch! */ } @@ -161,18 +163,36 @@ void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts /* Check for server response to our login attempt. */ std::string server_msg; while(_network->poll_message(server_msg)) { - if(server_msg == "LOGIN_SUCCESS" || server_msg == "C_ACC_SUCCESS") { - _current_screen = Screen::BOOTING; - _login_screen.reset(); /* Free mem. */ - _boot_sequence = std::make_unique(); + net_protocol::Opcode opcode; + std::vector args; + net_protocol::parse_message(server_msg, opcode, args); + + switch(opcode) { + case net_protocol::Opcode::S2C_LOGIN_SUCCESS: + case net_protocol::Opcode::S2C_CREATE_ACCOUNT_SUCCESS: + _current_screen = Screen::BOOTING; + _login_screen.reset(); /* Free mem. */ + _boot_sequence = std::make_unique(); + break; + case net_protocol::Opcode::S2C_LOGIN_FAIL: + _login_screen->set_error_message(args.empty() ? "Login failed." : args[0]); + break; + case net_protocol::Opcode::S2C_CREATE_ACCOUNT_FAIL: + _login_screen->set_error_message(args.empty() ? "Account creation failed." : + args[0]); + break; + default: + fprintf(stderr, "Recieved unexpected opcode %d during login.\n", + static_cast(opcode)); + break; + } + /* If we successfully changed screen, stop processing messages for this frame. */ + if(_current_screen == Screen::BOOTING) { break; - } else if(server_msg == "LOGIN_FAIL") { - _login_screen->set_error_message("Invalid username or password."); - } else if(server_msg == "C_ACC_FAIL") { - _login_screen->set_error_message("Username already exists."); } } - } break; + break; + } case Screen::BOOTING: { if(!_boot_sequence) break; /* Shouldn't happen. */ if(_boot_sequence->is_finished()) { @@ -184,68 +204,81 @@ void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts } case Screen::DESKTOP: { std::string server_msg; - while(_network->poll_message(server_msg)) { - /* Check for 'special', non-terminal messages first. */ - if(server_msg.rfind("FILEDATA::", 0) == 0) { - std::string payload = server_msg.substr(10); - size_t separator_pos = payload.find("::"); - if(separator_pos != std::string::npos) { - std::string filepath = payload.substr(0, separator_pos); - std::string content = payload.substr(separator_pos+2); - auto editor = std::make_unique(filepath); - editor->set_buffer_content(content); - auto editor_window = std::make_unique(filepath.c_str(), - 200, 200, 600, 400); - editor_window->set_content(std::move(editor)); - _desktop->add_window(std::move(editor_window)); + while(_network->poll_message(server_msg)) { + net_protocol::Opcode opcode; + std::vector args; + net_protocol::parse_message(server_msg, opcode, args); + + switch(opcode) { + case net_protocol::Opcode::S2C_FILE_DATA: + if(args.size() == 2) { + auto editor = std::make_unique(args[0]); + editor->set_buffer_content(args[1]); + auto editor_window = std::make_unique(args[0].c_str(), + 200, 200, 600, 400); + editor_window->set_content(std::move(editor)); + _desktop->add_window(std::move(editor_window)); } - continue; - } - if(server_msg == "__DISCONNECTED__") { + break; + case net_protocol::Opcode::S2C_DISCONNECT: { IWindowContent* content = _desktop->get_focused_window() ? - _desktop->get_focused_window()->get_content() : nullptr; + _desktop->get_focused_window()->get_content() : nullptr; Terminal* terminal = dynamic_cast(content); + if(terminal) { terminal->add_history("Connection closed."); - _network->send(""); } - continue; } - /* If not 'special' message, assume it's for terminal. */ - IWindowContent* content = _desktop->get_focused_window() ? - _desktop->get_focused_window()->get_content() : nullptr; - Terminal* terminal = dynamic_cast(content); - if(!terminal) - continue; + break; + case net_protocol::Opcode::S2C_CLOSE_WINDOW: { + if(_desktop) { + UIWindow* focused_window = _desktop->get_focused_window(); + if(focused_window) { focused_window->close(); } + } + } + break; + case net_protocol::Opcode::S2C_COMMAND_RESPONSE: { + if(!args.empty()) { + IWindowContent* content = _desktop->get_focused_window() ? + _desktop->get_focused_window()->get_content() : nullptr; + Terminal* terminal = dynamic_cast(content); + if(!terminal) continue; - /* Server sends "output\nprompt", split them. */ - size_t last_newline = server_msg.find_last_of('\n'); - if(last_newline != std::string::npos) { - std::string prompt = server_msg.substr(last_newline+1); - terminal->set_prompt(prompt); + /* Server sends "output\nprompt", split them. */ + size_t last_newline = args[0].find_last_of('\n'); + if(last_newline != std::string::npos) { + std::string prompt = args[0].substr(last_newline+1); + terminal->set_prompt(prompt); - std::string output = server_msg.substr(0, last_newline); - if(!output.empty()) { - /* Split multiline output and push each line to history. */ - std::stringstream ss(output); - std::string line; - while(std::getline(ss, line, '\n')) { - terminal->add_history(line); + std::string output = args[0].substr(0, last_newline); + if(!output.empty()) { + /* Split multiline output and push each line to history. */ + std::stringstream ss(output); + std::string line; + while(std::getline(ss, line, '\n')) { + terminal->add_history(line); + } + } + } else { + terminal->add_history(args[0]); } } - } else { - terminal->add_history(server_msg); } + break; } - if(_desktop) { - /* - * TODO: These fuck'in window dimensions just need to be global at this point!! - * Pass GameState by reference ? - */ - _desktop->update(dt, _screen_width, _screen_height); - } - break; } + if(_desktop) { + _desktop->update(dt, _screen_width, _screen_height); + } + break; + } + } +} + +void GameState::send_network_command(const std::string& command) { + if(_network && _network->is_connected()) { + _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_COMMAND, + {command})); } } @@ -277,3 +310,13 @@ void GameState::render(const RenderContext& context) { _debug_overlay->render(context.ui_renderer); } } + +void GameState::send_file_write_request(const std::string& path, const std::string& content) { + _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_WRITE_FILE, + {path, content})); +} + +void GameState::send_file_read_request(const std::string& path) { + _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_READ_FILE, + {path})); +} diff --git a/client/src/game_state.h b/client/src/game_state.h index fdb5a64..b3ac0ff 100644 --- a/client/src/game_state.h +++ b/client/src/game_state.h @@ -32,6 +32,11 @@ public: void update(float dt, int draw_calls, int shape_verts, int text_verts); void render(const RenderContext& context); + /* Public network interface for UI components. */ + void send_network_command(const std::string& command); + void send_file_write_request(const std::string& path, const std::string& content); + void send_file_read_request(const std::string& path); + private: std::unique_ptr _network; std::unique_ptr _desktop; @@ -46,5 +51,6 @@ private: bool _is_single_player; void _init_desktop(void); + void _send_network_command(const std::string& command); void _run_server(void); }; diff --git a/client/src/terminal.cpp b/client/src/terminal.cpp index d55c483..20163d0 100644 --- a/client/src/terminal.cpp +++ b/client/src/terminal.cpp @@ -6,12 +6,12 @@ #include #include "terminal.h" -#include "client_network.h" +#include "game_state.h" #include "gfx/types.h" #include "ui/window_action.h" -Terminal::Terminal(ClientNetwork* network) - : _network(network), _should_close(false), _scroll_offset(0), +Terminal::Terminal(GameState* game_state) + : _game_state(game_state), _should_close(false), _scroll_offset(0), _prompt(""), _pending_action({ActionType::NONE}) { _input_view = std::make_unique(&_input_buffer, false); } @@ -74,7 +74,7 @@ void Terminal::_on_ret_press(void) { } _history.push_back(_prompt + "> " + command); - _network->send(command); + _game_state->send_network_command(command); } void Terminal::handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) { diff --git a/client/src/terminal.h b/client/src/terminal.h index 735aa49..d422b45 100644 --- a/client/src/terminal.h +++ b/client/src/terminal.h @@ -4,16 +4,17 @@ #include #include -#include "client_network.h" #include "gfx/types.h" #include "ui/text_buffer.h" #include "ui/text_view.h" #include "ui/i_window_content.h" #include "ui/window_action.h" +class GameState; + class Terminal : public IWindowContent { public: - Terminal(ClientNetwork* network); + Terminal(GameState* game_state); ~Terminal(void); void update(void) override; @@ -33,7 +34,7 @@ private: std::vector _history; int _scroll_offset; std::string _prompt; - ClientNetwork* _network; + GameState* _game_state; TextBuffer _input_buffer; WindowAction _pending_action; std::unique_ptr _input_view; diff --git a/client/src/ui/desktop.cpp b/client/src/ui/desktop.cpp index 63c316c..888438a 100644 --- a/client/src/ui/desktop.cpp +++ b/client/src/ui/desktop.cpp @@ -8,14 +8,14 @@ #include #include "gfx/types.h" -#include "ui/editor.h" +#include "game_state.h" #include "desktop.h" -#include "client_network.h" #include "terminal.h" #include "ui/i_window_content.h" #include "ui/launcher.h" #include "ui/taskbar.h" #include "ui/ui_window.h" +#include "ui/editor.h" #include "ui/window_action.h" static const std::string& get_random_snippet(const std::vector& snippets) { @@ -26,9 +26,9 @@ static const std::string& get_random_snippet(const std::vector& sni return snippets[rand() % snippets.size()]; } -Desktop::Desktop(int screen_width, int screen_height, ClientNetwork* network) { +Desktop::Desktop(int screen_width, int screen_height, GameState* game_state) { _taskbar = std::make_unique(screen_width, screen_height); - _network = network; + _game_state= game_state; _focused_window = nullptr; _launcher_is_open = false; _launcher = std::make_unique(5, 0, 200); /* Tmp y-coord. */ @@ -133,7 +133,7 @@ void Desktop::handle_event(SDL_Event* event, int screen_width, int screen_height } else if(_launcher_is_open) { std::string app_to_launch = _launcher->handle_event(event, screen_height); if(app_to_launch == "Terminal") { - auto term = std::make_unique(_network); + auto term = std::make_unique(_game_state); auto term_window = std::make_unique("Terminal", 150, 150, 800, 500); term_window->set_content(std::move(term)); add_window(std::move(term_window)); @@ -182,13 +182,11 @@ void Desktop::update(float dt, int screen_width, int screen_height) { WindowAction action = content->get_pending_action(); switch(action.type) { case ActionType::WRITE_FILE: { - std::string message = "WRITEF::" + action.payload1 + "::" + action.payload2; - _network->send(message); + _game_state->send_file_write_request(action.payload1, action.payload2); break; } case ActionType::READ_FILE: { - std::string message = "READF::" + action.payload1; - _network->send(message); + _game_state->send_file_read_request(action.payload1); break; } default: diff --git a/client/src/ui/desktop.h b/client/src/ui/desktop.h index c67f38e..d930abf 100644 --- a/client/src/ui/desktop.h +++ b/client/src/ui/desktop.h @@ -4,13 +4,14 @@ #include #include -#include "client_network.h" #include "gfx/types.h" #include "ui/ui_window.h" #include "ui/taskbar.h" #include "ui/launcher.h" #include "ui/ui_renderer.h" +class GameState; + /* Animated background stuff. */ struct ScrollingText { std::string text; @@ -22,7 +23,7 @@ struct ScrollingText { class Desktop { public: - Desktop(int screen_width, int screen_height, ClientNetwork* network); + Desktop(int screen_width, int screen_height, GameState* game_state); ~Desktop(void); void add_window(std::unique_ptr window); @@ -41,7 +42,7 @@ private: std::unique_ptr _taskbar; std::unique_ptr _launcher; UIWindow* _focused_window; - ClientNetwork* _network; + GameState* _game_state; std::vector _background_text; std::vector _snippets; bool _launcher_is_open; diff --git a/client/src/ui/ui_window.h b/client/src/ui/ui_window.h index 2f00b14..d2e3de7 100644 --- a/client/src/ui/ui_window.h +++ b/client/src/ui/ui_window.h @@ -35,6 +35,7 @@ public: int taskbar_height); void minimize(void); void restore(void); + void close(void) { _should_close = true; } bool is_minimized(void) const; bool should_close(void) const; void set_focused(bool focused); diff --git a/common/src/command_processor.cpp b/common/src/command_processor.cpp index 9c786b7..5e05a64 100644 --- a/common/src/command_processor.cpp +++ b/common/src/command_processor.cpp @@ -97,7 +97,10 @@ std::string CommandProcessor::process_command(const std::string& command) { "ON T1.id = T2.parent_id WHERE T1.name = 'bin' AND T2.name = ? " "AND t1.machine_id = ?;" << script_filename << _session_machine->id - >> std::tie(script_id, script_content); + >> [&](long long id, std::string content) { + script_id = id; + script_content = content; + }; if(script_id > 0) { bool is_remote = (_session_machine != _home_machine); diff --git a/common/src/net/message_protocol.cpp b/common/src/net/message_protocol.cpp new file mode 100644 index 0000000..2abaf4b --- /dev/null +++ b/common/src/net/message_protocol.cpp @@ -0,0 +1,37 @@ +#include +#include "message_protocol.h" + +namespace net_protocol { + +std::string build_message(Opcode opcode, const std::vector& args) { + std::string payload; + payload += static_cast(opcode); + + for(size_t i = 0; i < args.size(); ++i) { + payload.append(args[i]); + if(i < args.size() - 1) { + payload += '\0'; + } + } + return payload; +} + +void parse_message(const std::string& payload, Opcode& out_opcode, + std::vector& out_args) { + out_args.clear(); + if(payload.empty()) { + return; /* Might handle this as an error.. */ + } + + out_opcode = static_cast(payload[0]); + + if(payload.length() > 1) { + std::stringstream ss(payload.substr(1)); + std::string segment; + while(std::getline(ss, segment, '\0')) { + out_args.push_back(segment); + } + } +} + +}; /* namespace net_protocol. */ diff --git a/common/src/net/message_protocol.h b/common/src/net/message_protocol.h new file mode 100644 index 0000000..9cd5aa7 --- /dev/null +++ b/common/src/net/message_protocol.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +namespace net_protocol { + +/** + * @brief Defines the opcodes for client-server communication. + * + * Each message payload begins with one of these single-byte opcodes to identify + * the purpose of the message. + */ +enum class Opcode : uint8_t { + /* Client -> Server messages. */ + C2S_CREATE_ACCOUNT, + C2S_LOGIN, + C2S_COMMAND, + C2S_WRITE_FILE, + C2S_READ_FILE, + + /* Server -> Client messages. */ + S2C_CREATE_ACCOUNT_SUCCESS, + S2C_CREATE_ACCOUNT_FAIL, + S2C_LOGIN_SUCCESS, + S2C_LOGIN_FAIL, + S2C_COMMAND_RESPONSE, + S2C_FILE_DATA, + S2C_DISCONNECT, + S2C_CLOSE_WINDOW, +}; + +/** + * @brief Builds a raw message payload from an opcode and arguments. + * @param opcode The message type. + * @param args Vector of string arguments to be included in the message. + * @return A payload string to be sent over the network. + */ +std::string build_message(Opcode opcode, const std::vector& args = {}); + +/** + * @brief Parse a raw message payload into its opcode and arguments. + * @param payload The raw payload received from the netwrok. + * @param out_opcode The output variable for the parsed opcode. + * @param out_args the output vector for the parsed string arguments. + * + * The format is: [1-byte Opcode][arg1]\0[arg2]\0... + */ +void parse_message(const std::string& payload, Opcode& out_opcode, + std::vector& out_args); + +}; /* namespace net_protocol. */ diff --git a/server/src/network_manager.cpp b/server/src/network_manager.cpp index 71e6944..cd8c200 100644 --- a/server/src/network_manager.cpp +++ b/server/src/network_manager.cpp @@ -4,6 +4,7 @@ #include #include "network_manager.h" +#include "net/message_protocol.h" #include "db/database_manager.h" #include "asio/error_code.hpp" @@ -95,10 +96,6 @@ void NetworkManager::start_accept(void) { }, [this, new_connection]() { this->on_disconnect(new_connection); }); - /* Send initial prompt. */ - std::string prompt = "\n" + get_full_path(new_player_ptr->cmd_processor->get_current_dir()); - new_connection->send(prompt); - _connections.push_back(new_connection); } else { fprintf(stderr, "Accept error: %s\n", ec.message().c_str()); @@ -108,17 +105,6 @@ void NetworkManager::start_accept(void) { }); } -static std::vector split_message(const std::string& s, const std::string& delimiter) { - std::vector tokens; - size_t start = 0, end = 0; - while((end = s.find(delimiter, start)) != std::string::npos) { - tokens.push_back(s.substr(start, end-start)); - start = end + delimiter.length(); - } - tokens.push_back(s.substr(start)); - return tokens; -} - void NetworkManager::on_message(std::shared_ptr connection, const std::string& message) { Player* player = _players[connection->get_id()].get(); @@ -127,94 +113,103 @@ void NetworkManager::on_message(std::shared_ptr connection, return; } + net_protocol::Opcode opcode; + std::vector args; + net_protocol::parse_message(message, opcode, args); + if(player->state == PlayerState::AUTHENTICATING) { - if(message.rfind("C_ACC::", 0) == 0) { - std::string payload = message.substr(7); - auto parts = split_message(payload, "::"); - if(parts.size() == 3) { - if(_db_manager->create_player(parts[0], parts[1], parts[2], + switch(opcode) { + case net_protocol::Opcode::C2S_CREATE_ACCOUNT: + if(args.size() == 3) { + if(_db_manager->create_player(args[0], args[1], args[2], _machine_manager.get_vfs_template())) { - long long machine_id = _db_manager->players().get_home_machine_id(parts[0]); + long long machine_id = _db_manager->players().get_home_machine_id(args[0]); Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get()); delete player->cmd_processor; /* Delete old command processor. */ player->cmd_processor = new CommandProcessor(home_machine, _world_machines, _db_manager.get(), &_machine_manager); player->state = PlayerState::ACTIVE; - connection->send("C_ACC_SUCCESS"); + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CREATE_ACCOUNT_SUCCESS)); /* send initial prompt. */ std::string prompt = "\n" + get_full_path(player->cmd_processor->get_current_dir()); - connection->send(prompt); + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE, + {prompt})); } else { - connection->send("C_ACC_FAIL"); + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CREATE_ACCOUNT_FAIL, + {"Username already exists."})); } } - } else if(message.rfind("LOGIN::", 0) == 0) { - std::string payload = message.substr(7); - auto parts = split_message(payload, "::"); - if(parts.size() == 2) { - if(_db_manager->players().authenticate(parts[0], parts[1])) { - long long machine_id = _db_manager->players().get_home_machine_id(parts[0]); - printf("DEBUG: Loading machine %lld for player %s\n", machine_id, parts[0].c_str()); + break; + case net_protocol::Opcode::C2S_LOGIN: + if(args.size() == 2) { + if(_db_manager->players().authenticate(args[0], args[1])) { + long long machine_id = _db_manager->players().get_home_machine_id(args[0]); + printf("DEBUG: Loading machine %lld for player %s\n", machine_id, args[0].c_str()); Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get()); delete player->cmd_processor; /* Delete old command processor. */ player->cmd_processor = new CommandProcessor(home_machine, _world_machines, _db_manager.get(), &_machine_manager); player->state = PlayerState::ACTIVE; - connection->send("LOGIN_SUCCESS"); + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_SUCCESS)); /* Send initial prompt. */ std::string prompt = "\n" + get_full_path(player->cmd_processor->get_current_dir()); - connection->send(prompt); + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE, + {prompt})); } else { - connection->send("LOGIN_FAIL"); + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_FAIL, + {"Invalid username or password."})); } } - } else { - /* Ignore all other messages while authing. */ - connection->send("ERR: Not authenticated.\n"); + break; + default: + /* ignore all other messages while authing. */ + fprintf(stderr, "Received invalid opcode %d during auth.\n", static_cast(opcode)); + break; } return; } /* === PLAYER BECOMES ACTIVE HERE === */ - /* Check for "special" message prefixes. */ - if(message.rfind("WRITEF::", 0) == 0) { - /* Message format: WRITEF::/path/to/file::content. */ - std::string payload = message.substr(8); - size_t separator_pos = payload.find("::"); - if(separator_pos != std::string::npos) { - std::string filepath = payload.substr(0, separator_pos); - std::string content = payload.substr(separator_pos+2); - - fprintf(stderr, "[Player %u] Write file: \'%s\'\n", player->id, filepath.c_str()); - player->cmd_processor->write_file(filepath, content); + switch(opcode) { + case net_protocol::Opcode::C2S_WRITE_FILE: + if(args.size() == 2) { + fprintf(stderr, "[Player %u] Write file: '%s'\n", player->id, args[0].c_str()); + player->cmd_processor->write_file(args[0], args[1]); /* Response not required for a file write. */ } - return; + break; + case net_protocol::Opcode::C2S_READ_FILE: + if(!args.empty()) { + fprintf(stderr, "[Player %u] Read file: '%s'\n", player->id, args[0].c_str()); + std::string content = player->cmd_processor->read_file(args[0]); + /* Send the content back to the client. */ + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_FILE_DATA, + {args[0], content})); + } + break; + case net_protocol::Opcode::C2S_COMMAND: + if(!args.empty()) { + fprintf(stderr, "[Player %u] Command: '%s'\n", player->id, args[0].c_str()); + std::string response = player->cmd_processor->process_command(args[0]); + + if(response == "__CLOSE_CONNECTION__") { + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CLOSE_WINDOW)); + return; + } + std::string new_prompt = get_full_path(player->cmd_processor->get_current_dir()); + response += "\n" + new_prompt; + + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE, + {response})); + } + break; + default: + fprintf(stderr, "Received invalid opcode %d from active player.\n", + static_cast(opcode)); + break; } - if(message.rfind("READF::", 0) == 0) { - std::string filepath = message.substr(7); - fprintf(stderr, "[Player %u] Read file: '%s'\n", player->id, filepath.c_str()); - std::string content = player->cmd_processor->read_file(filepath); - /* Send the content back to the client. */ - connection->send("FILEDATA::" + filepath + "::" + content); - return; - } - - /* If no prefix, treat as normal terminal command. */ - fprintf(stderr, "[Player %u] Command: '%s'\n", player->id, message.c_str()); - std::string response = player->cmd_processor->process_command(message); - - if(response == "__CLOSE_CONNECTION__") { - connection->send(response); - return; - } - - std::string new_prompt = get_full_path(player->cmd_processor->get_current_dir()); - response += "\n" + new_prompt; - - connection->send(response); } void NetworkManager::on_disconnect(std::shared_ptr connection) {