[Refactor] Implment structured network protocol.

This commit is contained in:
Ritchie Cunningham 2025-10-11 19:05:14 +01:00
parent 3234ef7b3b
commit 9d770ef9b2
11 changed files with 292 additions and 154 deletions

View File

@ -6,6 +6,8 @@
#include "gfx/types.h" #include "gfx/types.h"
#include "net/constants.h" #include "net/constants.h"
#include "net/message_protocol.h"
#include "client_network.h"
#include "network_manager.h" #include "network_manager.h"
#include "game_state.h" #include "game_state.h"
#include "terminal.h" #include "terminal.h"
@ -20,9 +22,9 @@
#include "debug/debug_overlay.h" #include "debug/debug_overlay.h"
void GameState::_init_desktop(void) { void GameState::_init_desktop(void) {
_desktop = std::make_unique<Desktop>(_screen_width, _screen_height, _network.get()); _desktop = std::make_unique<Desktop>(_screen_width, _screen_height, this);
auto term = std::make_unique<Terminal>(_network.get()); auto term = std::make_unique<Terminal>(this);
auto term_window = std::make_unique<UIWindow>("Terminal", 100, 100, 800, 500); auto term_window = std::make_unique<UIWindow>("Terminal", 100, 100, 800, 500);
term_window->set_content(std::move(term)); term_window->set_content(std::move(term));
_desktop->add_window(std::move(term_window)); _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()) { if(_login_screen->is_new_account_mode()) {
std::string hostname = _login_screen->get_hostname(); std::string hostname = _login_screen->get_hostname();
std::string msg = "C_ACC::" + username + "::" + password + "::" + hostname; _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_CREATE_ACCOUNT,
_network->send(msg); {username, password, hostname}));
} else { } else {
std::string msg = "LOGIN::" + username + "::" + password; _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_LOGIN,
_network->send(msg); {username, password}));
} }
_login_screen->clear_login_attempt(); /* Try to spam my server now b.tch! */ _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. */ /* Check for server response to our login attempt. */
std::string server_msg; std::string server_msg;
while(_network->poll_message(server_msg)) { while(_network->poll_message(server_msg)) {
if(server_msg == "LOGIN_SUCCESS" || server_msg == "C_ACC_SUCCESS") { net_protocol::Opcode opcode;
_current_screen = Screen::BOOTING; std::vector<std::string> args;
_login_screen.reset(); /* Free mem. */ net_protocol::parse_message(server_msg, opcode, args);
_boot_sequence = std::make_unique<BootSequence>();
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<BootSequence>();
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<int>(opcode));
break;
}
/* If we successfully changed screen, stop processing messages for this frame. */
if(_current_screen == Screen::BOOTING) {
break; 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: { case Screen::BOOTING: {
if(!_boot_sequence) break; /* Shouldn't happen. */ if(!_boot_sequence) break; /* Shouldn't happen. */
if(_boot_sequence->is_finished()) { 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: { case Screen::DESKTOP: {
std::string server_msg; std::string server_msg;
while(_network->poll_message(server_msg)) { while(_network->poll_message(server_msg)) {
/* Check for 'special', non-terminal messages first. */ net_protocol::Opcode opcode;
if(server_msg.rfind("FILEDATA::", 0) == 0) { std::vector<std::string> args;
std::string payload = server_msg.substr(10); net_protocol::parse_message(server_msg, opcode, args);
size_t separator_pos = payload.find("::");
if(separator_pos != std::string::npos) { switch(opcode) {
std::string filepath = payload.substr(0, separator_pos); case net_protocol::Opcode::S2C_FILE_DATA:
std::string content = payload.substr(separator_pos+2); if(args.size() == 2) {
auto editor = std::make_unique<Editor>(filepath); auto editor = std::make_unique<Editor>(args[0]);
editor->set_buffer_content(content); editor->set_buffer_content(args[1]);
auto editor_window = std::make_unique<UIWindow>(filepath.c_str(), auto editor_window = std::make_unique<UIWindow>(args[0].c_str(),
200, 200, 600, 400); 200, 200, 600, 400);
editor_window->set_content(std::move(editor)); editor_window->set_content(std::move(editor));
_desktop->add_window(std::move(editor_window)); _desktop->add_window(std::move(editor_window));
} }
continue; break;
} case net_protocol::Opcode::S2C_DISCONNECT: {
if(server_msg == "__DISCONNECTED__") {
IWindowContent* content = _desktop->get_focused_window() ? IWindowContent* content = _desktop->get_focused_window() ?
_desktop->get_focused_window()->get_content() : nullptr; _desktop->get_focused_window()->get_content() : nullptr;
Terminal* terminal = dynamic_cast<Terminal*>(content); Terminal* terminal = dynamic_cast<Terminal*>(content);
if(terminal) { if(terminal) {
terminal->add_history("Connection closed."); terminal->add_history("Connection closed.");
_network->send("");
} }
continue;
} }
/* If not 'special' message, assume it's for terminal. */ break;
IWindowContent* content = _desktop->get_focused_window() ? case net_protocol::Opcode::S2C_CLOSE_WINDOW: {
_desktop->get_focused_window()->get_content() : nullptr; if(_desktop) {
Terminal* terminal = dynamic_cast<Terminal*>(content); UIWindow* focused_window = _desktop->get_focused_window();
if(!terminal) if(focused_window) { focused_window->close(); }
continue; }
}
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<Terminal*>(content);
if(!terminal) continue;
/* Server sends "output\nprompt", split them. */ /* Server sends "output\nprompt", split them. */
size_t last_newline = server_msg.find_last_of('\n'); size_t last_newline = args[0].find_last_of('\n');
if(last_newline != std::string::npos) { if(last_newline != std::string::npos) {
std::string prompt = server_msg.substr(last_newline+1); std::string prompt = args[0].substr(last_newline+1);
terminal->set_prompt(prompt); terminal->set_prompt(prompt);
std::string output = server_msg.substr(0, last_newline); std::string output = args[0].substr(0, last_newline);
if(!output.empty()) { if(!output.empty()) {
/* Split multiline output and push each line to history. */ /* Split multiline output and push each line to history. */
std::stringstream ss(output); std::stringstream ss(output);
std::string line; std::string line;
while(std::getline(ss, line, '\n')) { while(std::getline(ss, line, '\n')) {
terminal->add_history(line); 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); _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}));
}

View File

@ -32,6 +32,11 @@ public:
void update(float dt, int draw_calls, int shape_verts, int text_verts); void update(float dt, int draw_calls, int shape_verts, int text_verts);
void render(const RenderContext& context); 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: private:
std::unique_ptr<ClientNetwork> _network; std::unique_ptr<ClientNetwork> _network;
std::unique_ptr<Desktop> _desktop; std::unique_ptr<Desktop> _desktop;
@ -46,5 +51,6 @@ private:
bool _is_single_player; bool _is_single_player;
void _init_desktop(void); void _init_desktop(void);
void _send_network_command(const std::string& command);
void _run_server(void); void _run_server(void);
}; };

View File

@ -6,12 +6,12 @@
#include <SDL3/SDL_events.h> #include <SDL3/SDL_events.h>
#include "terminal.h" #include "terminal.h"
#include "client_network.h" #include "game_state.h"
#include "gfx/types.h" #include "gfx/types.h"
#include "ui/window_action.h" #include "ui/window_action.h"
Terminal::Terminal(ClientNetwork* network) Terminal::Terminal(GameState* game_state)
: _network(network), _should_close(false), _scroll_offset(0), : _game_state(game_state), _should_close(false), _scroll_offset(0),
_prompt(""), _pending_action({ActionType::NONE}) { _prompt(""), _pending_action({ActionType::NONE}) {
_input_view = std::make_unique<TextView>(&_input_buffer, false); _input_view = std::make_unique<TextView>(&_input_buffer, false);
} }
@ -74,7 +74,7 @@ void Terminal::_on_ret_press(void) {
} }
_history.push_back(_prompt + "> " + command); _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) { void Terminal::handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) {

View File

@ -4,16 +4,17 @@
#include <memory> #include <memory>
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include "client_network.h"
#include "gfx/types.h" #include "gfx/types.h"
#include "ui/text_buffer.h" #include "ui/text_buffer.h"
#include "ui/text_view.h" #include "ui/text_view.h"
#include "ui/i_window_content.h" #include "ui/i_window_content.h"
#include "ui/window_action.h" #include "ui/window_action.h"
class GameState;
class Terminal : public IWindowContent { class Terminal : public IWindowContent {
public: public:
Terminal(ClientNetwork* network); Terminal(GameState* game_state);
~Terminal(void); ~Terminal(void);
void update(void) override; void update(void) override;
@ -33,7 +34,7 @@ private:
std::vector<std::string> _history; std::vector<std::string> _history;
int _scroll_offset; int _scroll_offset;
std::string _prompt; std::string _prompt;
ClientNetwork* _network; GameState* _game_state;
TextBuffer _input_buffer; TextBuffer _input_buffer;
WindowAction _pending_action; WindowAction _pending_action;
std::unique_ptr<TextView> _input_view; std::unique_ptr<TextView> _input_view;

View File

@ -8,14 +8,14 @@
#include <SDL3/SDL_video.h> #include <SDL3/SDL_video.h>
#include "gfx/types.h" #include "gfx/types.h"
#include "ui/editor.h" #include "game_state.h"
#include "desktop.h" #include "desktop.h"
#include "client_network.h"
#include "terminal.h" #include "terminal.h"
#include "ui/i_window_content.h" #include "ui/i_window_content.h"
#include "ui/launcher.h" #include "ui/launcher.h"
#include "ui/taskbar.h" #include "ui/taskbar.h"
#include "ui/ui_window.h" #include "ui/ui_window.h"
#include "ui/editor.h"
#include "ui/window_action.h" #include "ui/window_action.h"
static const std::string& get_random_snippet(const std::vector<std::string>& snippets) { static const std::string& get_random_snippet(const std::vector<std::string>& snippets) {
@ -26,9 +26,9 @@ static const std::string& get_random_snippet(const std::vector<std::string>& sni
return snippets[rand() % snippets.size()]; 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<Taskbar>(screen_width, screen_height); _taskbar = std::make_unique<Taskbar>(screen_width, screen_height);
_network = network; _game_state= game_state;
_focused_window = nullptr; _focused_window = nullptr;
_launcher_is_open = false; _launcher_is_open = false;
_launcher = std::make_unique<Launcher>(5, 0, 200); /* Tmp y-coord. */ _launcher = std::make_unique<Launcher>(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) { } else if(_launcher_is_open) {
std::string app_to_launch = _launcher->handle_event(event, screen_height); std::string app_to_launch = _launcher->handle_event(event, screen_height);
if(app_to_launch == "Terminal") { if(app_to_launch == "Terminal") {
auto term = std::make_unique<Terminal>(_network); auto term = std::make_unique<Terminal>(_game_state);
auto term_window = std::make_unique<UIWindow>("Terminal", 150, 150, 800, 500); auto term_window = std::make_unique<UIWindow>("Terminal", 150, 150, 800, 500);
term_window->set_content(std::move(term)); term_window->set_content(std::move(term));
add_window(std::move(term_window)); 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(); WindowAction action = content->get_pending_action();
switch(action.type) { switch(action.type) {
case ActionType::WRITE_FILE: { case ActionType::WRITE_FILE: {
std::string message = "WRITEF::" + action.payload1 + "::" + action.payload2; _game_state->send_file_write_request(action.payload1, action.payload2);
_network->send(message);
break; break;
} }
case ActionType::READ_FILE: { case ActionType::READ_FILE: {
std::string message = "READF::" + action.payload1; _game_state->send_file_read_request(action.payload1);
_network->send(message);
break; break;
} }
default: default:

View File

@ -4,13 +4,14 @@
#include <vector> #include <vector>
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include "client_network.h"
#include "gfx/types.h" #include "gfx/types.h"
#include "ui/ui_window.h" #include "ui/ui_window.h"
#include "ui/taskbar.h" #include "ui/taskbar.h"
#include "ui/launcher.h" #include "ui/launcher.h"
#include "ui/ui_renderer.h" #include "ui/ui_renderer.h"
class GameState;
/* Animated background stuff. */ /* Animated background stuff. */
struct ScrollingText { struct ScrollingText {
std::string text; std::string text;
@ -22,7 +23,7 @@ struct ScrollingText {
class Desktop { class Desktop {
public: public:
Desktop(int screen_width, int screen_height, ClientNetwork* network); Desktop(int screen_width, int screen_height, GameState* game_state);
~Desktop(void); ~Desktop(void);
void add_window(std::unique_ptr<UIWindow> window); void add_window(std::unique_ptr<UIWindow> window);
@ -41,7 +42,7 @@ private:
std::unique_ptr<Taskbar> _taskbar; std::unique_ptr<Taskbar> _taskbar;
std::unique_ptr<Launcher> _launcher; std::unique_ptr<Launcher> _launcher;
UIWindow* _focused_window; UIWindow* _focused_window;
ClientNetwork* _network; GameState* _game_state;
std::vector<ScrollingText> _background_text; std::vector<ScrollingText> _background_text;
std::vector<std::string> _snippets; std::vector<std::string> _snippets;
bool _launcher_is_open; bool _launcher_is_open;

View File

@ -35,6 +35,7 @@ public:
int taskbar_height); int taskbar_height);
void minimize(void); void minimize(void);
void restore(void); void restore(void);
void close(void) { _should_close = true; }
bool is_minimized(void) const; bool is_minimized(void) const;
bool should_close(void) const; bool should_close(void) const;
void set_focused(bool focused); void set_focused(bool focused);

View File

@ -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 = ? " "ON T1.id = T2.parent_id WHERE T1.name = 'bin' AND T2.name = ? "
"AND t1.machine_id = ?;" "AND t1.machine_id = ?;"
<< script_filename << _session_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) { if(script_id > 0) {
bool is_remote = (_session_machine != _home_machine); bool is_remote = (_session_machine != _home_machine);

View File

@ -0,0 +1,37 @@
#include <sstream>
#include "message_protocol.h"
namespace net_protocol {
std::string build_message(Opcode opcode, const std::vector<std::string>& args) {
std::string payload;
payload += static_cast<char>(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<std::string>& out_args) {
out_args.clear();
if(payload.empty()) {
return; /* Might handle this as an error.. */
}
out_opcode = static_cast<Opcode>(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. */

View File

@ -0,0 +1,53 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
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<std::string>& 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<std::string>& out_args);
}; /* namespace net_protocol. */

View File

@ -4,6 +4,7 @@
#include <memory> #include <memory>
#include "network_manager.h" #include "network_manager.h"
#include "net/message_protocol.h"
#include "db/database_manager.h" #include "db/database_manager.h"
#include "asio/error_code.hpp" #include "asio/error_code.hpp"
@ -95,10 +96,6 @@ void NetworkManager::start_accept(void) {
}, },
[this, new_connection]() { this->on_disconnect(new_connection); }); [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); _connections.push_back(new_connection);
} else { } else {
fprintf(stderr, "Accept error: %s\n", ec.message().c_str()); fprintf(stderr, "Accept error: %s\n", ec.message().c_str());
@ -108,17 +105,6 @@ void NetworkManager::start_accept(void) {
}); });
} }
static std::vector<std::string> split_message(const std::string& s, const std::string& delimiter) {
std::vector<std::string> 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<net::TcpConnection> connection, void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
const std::string& message) { const std::string& message) {
Player* player = _players[connection->get_id()].get(); Player* player = _players[connection->get_id()].get();
@ -127,94 +113,103 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
return; return;
} }
net_protocol::Opcode opcode;
std::vector<std::string> args;
net_protocol::parse_message(message, opcode, args);
if(player->state == PlayerState::AUTHENTICATING) { if(player->state == PlayerState::AUTHENTICATING) {
if(message.rfind("C_ACC::", 0) == 0) { switch(opcode) {
std::string payload = message.substr(7); case net_protocol::Opcode::C2S_CREATE_ACCOUNT:
auto parts = split_message(payload, "::"); if(args.size() == 3) {
if(parts.size() == 3) { if(_db_manager->create_player(args[0], args[1], args[2],
if(_db_manager->create_player(parts[0], parts[1], parts[2],
_machine_manager.get_vfs_template())) { _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()); Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get());
delete player->cmd_processor; /* Delete old command processor. */ delete player->cmd_processor; /* Delete old command processor. */
player->cmd_processor = new CommandProcessor(home_machine, _world_machines, player->cmd_processor = new CommandProcessor(home_machine, _world_machines,
_db_manager.get(), _db_manager.get(),
&_machine_manager); &_machine_manager);
player->state = PlayerState::ACTIVE; 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. */ /* send initial prompt. */
std::string prompt = "\n" + get_full_path(player->cmd_processor->get_current_dir()); 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 { } 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) { break;
std::string payload = message.substr(7); case net_protocol::Opcode::C2S_LOGIN:
auto parts = split_message(payload, "::"); if(args.size() == 2) {
if(parts.size() == 2) { if(_db_manager->players().authenticate(args[0], args[1])) {
if(_db_manager->players().authenticate(parts[0], parts[1])) { long long machine_id = _db_manager->players().get_home_machine_id(args[0]);
long long machine_id = _db_manager->players().get_home_machine_id(parts[0]); printf("DEBUG: Loading machine %lld for player %s\n", machine_id, args[0].c_str());
printf("DEBUG: Loading machine %lld for player %s\n", machine_id, parts[0].c_str());
Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get()); Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get());
delete player->cmd_processor; /* Delete old command processor. */ delete player->cmd_processor; /* Delete old command processor. */
player->cmd_processor = new CommandProcessor(home_machine, _world_machines, player->cmd_processor = new CommandProcessor(home_machine, _world_machines,
_db_manager.get(), &_machine_manager); _db_manager.get(), &_machine_manager);
player->state = PlayerState::ACTIVE; player->state = PlayerState::ACTIVE;
connection->send("LOGIN_SUCCESS"); connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_SUCCESS));
/* Send initial prompt. */ /* Send initial prompt. */
std::string prompt = "\n" + get_full_path(player->cmd_processor->get_current_dir()); 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 { } else {
connection->send("LOGIN_FAIL"); connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_FAIL,
{"Invalid username or password."}));
} }
} }
} else { break;
/* Ignore all other messages while authing. */ default:
connection->send("ERR: Not authenticated.\n"); /* ignore all other messages while authing. */
fprintf(stderr, "Received invalid opcode %d during auth.\n", static_cast<int>(opcode));
break;
} }
return; return;
} }
/* === PLAYER BECOMES ACTIVE HERE === */ /* === PLAYER BECOMES ACTIVE HERE === */
/* Check for "special" message prefixes. */ switch(opcode) {
if(message.rfind("WRITEF::", 0) == 0) { case net_protocol::Opcode::C2S_WRITE_FILE:
/* Message format: WRITEF::/path/to/file::content. */ if(args.size() == 2) {
std::string payload = message.substr(8); fprintf(stderr, "[Player %u] Write file: '%s'\n", player->id, args[0].c_str());
size_t separator_pos = payload.find("::"); player->cmd_processor->write_file(args[0], args[1]);
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);
/* Response not required for a file write. */ /* 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<int>(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<net::TcpConnection> connection) { void NetworkManager::on_disconnect(std::shared_ptr<net::TcpConnection> connection) {