[Refactor] Implment structured network protocol.
This commit is contained in:
		
							parent
							
								
									3234ef7b3b
								
							
						
					
					
						commit
						9d770ef9b2
					
				@ -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<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);
 | 
			
		||||
  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<BootSequence>();
 | 
			
		||||
      net_protocol::Opcode opcode;
 | 
			
		||||
      std::vector<std::string> 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<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;
 | 
			
		||||
      } 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<Editor>(filepath);
 | 
			
		||||
              editor->set_buffer_content(content);
 | 
			
		||||
              auto editor_window = std::make_unique<UIWindow>(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<std::string> 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<Editor>(args[0]);
 | 
			
		||||
            editor->set_buffer_content(args[1]);
 | 
			
		||||
            auto editor_window = std::make_unique<UIWindow>(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<Terminal*>(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<Terminal*>(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<Terminal*>(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}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<ClientNetwork>  _network;
 | 
			
		||||
  std::unique_ptr<Desktop>        _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);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -6,12 +6,12 @@
 | 
			
		||||
#include <SDL3/SDL_events.h>
 | 
			
		||||
 | 
			
		||||
#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<TextView>(&_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) {
 | 
			
		||||
 | 
			
		||||
@ -4,16 +4,17 @@
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <SDL3/SDL.h>
 | 
			
		||||
 | 
			
		||||
#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<std::string> _history;
 | 
			
		||||
  int _scroll_offset;
 | 
			
		||||
  std::string _prompt;
 | 
			
		||||
  ClientNetwork* _network;
 | 
			
		||||
  GameState* _game_state;
 | 
			
		||||
  TextBuffer _input_buffer;
 | 
			
		||||
  WindowAction _pending_action;
 | 
			
		||||
  std::unique_ptr<TextView> _input_view;
 | 
			
		||||
 | 
			
		||||
@ -8,14 +8,14 @@
 | 
			
		||||
#include <SDL3/SDL_video.h>
 | 
			
		||||
 | 
			
		||||
#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<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()];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
  _network = network;
 | 
			
		||||
  _game_state= game_state;
 | 
			
		||||
  _focused_window = nullptr;
 | 
			
		||||
  _launcher_is_open = false;
 | 
			
		||||
  _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) {
 | 
			
		||||
      std::string app_to_launch = _launcher->handle_event(event, screen_height);
 | 
			
		||||
      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);
 | 
			
		||||
        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:
 | 
			
		||||
 | 
			
		||||
@ -4,13 +4,14 @@
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <SDL3/SDL.h>
 | 
			
		||||
 | 
			
		||||
#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<UIWindow> window);
 | 
			
		||||
@ -41,7 +42,7 @@ private:
 | 
			
		||||
  std::unique_ptr<Taskbar>   _taskbar;
 | 
			
		||||
  std::unique_ptr<Launcher>  _launcher;
 | 
			
		||||
  UIWindow*                  _focused_window;
 | 
			
		||||
  ClientNetwork*             _network;
 | 
			
		||||
  GameState*                 _game_state;
 | 
			
		||||
  std::vector<ScrollingText> _background_text;
 | 
			
		||||
  std::vector<std::string>   _snippets;
 | 
			
		||||
  bool                       _launcher_is_open;
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										37
									
								
								common/src/net/message_protocol.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								common/src/net/message_protocol.cpp
									
									
									
									
									
										Normal 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. */
 | 
			
		||||
							
								
								
									
										53
									
								
								common/src/net/message_protocol.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								common/src/net/message_protocol.h
									
									
									
									
									
										Normal 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. */
 | 
			
		||||
@ -4,6 +4,7 @@
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
#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<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,
 | 
			
		||||
                                const std::string& message) {
 | 
			
		||||
  Player* player = _players[connection->get_id()].get();
 | 
			
		||||
@ -127,94 +113,103 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  net_protocol::Opcode opcode;
 | 
			
		||||
  std::vector<std::string> 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<int>(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<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) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user