Skip to main content
GTProxy is built with a modular, event-driven architecture that separates concerns into distinct subsystems. This design enables clean packet interception, modification, and extensibility through Lua scripting.

Architectural Overview

The proxy operates as a man-in-the-middle between the Growtopia client and server, managing bidirectional packet flow and providing hooks for inspection and modification.
Growtopia Client ←→ GTProxy Server ←→ GTProxy Client ←→ Growtopia Server
                         ↓                    ↓
                    Event System        Event System
                         ↓                    ↓
                    Packet Handlers    Packet Handlers
                         ↓                    ↓
                    Lua Scripts        Lua Scripts

Core Module

The core::Core class (/home/daytona/workspace/source/src/core/core.hpp:18) serves as the application’s central orchestrator, managing all major subsystems:
class Core final : public utils::types::Immobile {
private:
    Config config_;
    bool running_;

    event::Dispatcher dispatcher_;
    std::shared_ptr<Scheduler> scheduler_;

    std::unique_ptr<network::Server> server_;
    std::unique_ptr<network::Client> client_;
    std::unique_ptr<WebServer> web_server_;

    std::unique_ptr<handlers::ConnectionHandler> connection_handler_;
    std::unique_ptr<handlers::ForwardingHandler> forwarding_handler_;
    std::unique_ptr<handlers::WorldHandler> world_handler_;
    std::unique_ptr<command::CommandHandler> command_handler_;

    std::unique_ptr<scripting::LuaEngine> script_engine_;
    std::unique_ptr<scripting::ScriptScheduler> script_scheduler_;
    std::unique_ptr<scripting::ScriptEventBridge> script_event_bridge_;
    std::unique_ptr<scripting::ScriptLoader> script_loader_;
};

Key Responsibilities

Lifecycle Management

Initializes and coordinates all subsystems, manages the main event loop

Event Dispatching

Owns the central event dispatcher that all components use for communication

Resource Ownership

Manages lifetime of network connections, handlers, and scripting engine

Configuration

Loads and provides access to proxy configuration settings

Network Module

The network layer consists of two main components that handle connections using the ENet library:

Server Component

The network::Server (/home/daytona/workspace/source/src/network/server.hpp:11) listens for incoming connections from the Growtopia client:
class Server final : public ENetWrapper, public IConnection {
public:
    Server(core::Config& config, event::Dispatcher& dispatcher);

    [[nodiscard]] bool write(std::span<const std::byte> data, int channel = 0) const override;
    [[nodiscard]] bool is_connected() const override;

    void disconnect() const;
    void flush() const;

protected:
    void on_connect(ENetPeer* peer) override;
    void on_receive(ENetPeer* peer, std::span<const std::byte> data) override;
    void on_disconnect(ENetPeer* peer) override;

private:
    event::Dispatcher& dispatcher_;
    ENetPeer* peer_;
};

Client Component

The network::Client (/home/daytona/workspace/source/src/network/client.hpp:13) connects to the actual Growtopia server:
class Client final : public ENetWrapper, public IConnection {
public:
    Client(core::Config& config, event::Dispatcher& dispatcher);

    bool connect(const std::string& host, std::uint16_t port);
    [[nodiscard]] bool write(std::span<const std::byte> data, int channel = 0) const override;
    [[nodiscard]] bool is_connected() const override;

protected:
    void on_connect(ENetPeer* peer) override;
    void on_receive(ENetPeer* peer, std::span<const std::byte> data) override;
    void on_disconnect(ENetPeer* peer) override;
};
Both components inherit from IConnection interface, allowing them to be used interchangeably for forwarding packets.

Connection Flow

  1. Client Connects: Growtopia client connects to GTProxy’s server (default port 17091)
  2. Server Forward: GTProxy’s client connects to the real Growtopia server
  3. Bidirectional Flow: Packets flow through both connections with event dispatching
  4. Disconnect Handling: Either side disconnecting triggers cleanup of both connections

Packet Module

The packet system handles encoding, decoding, and routing of Growtopia network messages. See Packet System for details.

Core Components

  • PacketDecoder (/home/daytona/workspace/source/src/packet/packet_decoder.hpp:14): Parses raw bytes into structured packet objects
  • PacketRegistry (/home/daytona/workspace/source/src/packet/packet_registry.hpp:26): Factory pattern for creating packet instances
  • PacketHelper (/home/daytona/workspace/source/src/packet/packet_helper.hpp:90): Serializes packets back to bytes for transmission
  • Payload Types: Variant-based payload system supporting Text, Game, Variant, and Raw formats

Packet Types

GTProxy supports four payload categories:
TypeDescriptionExample Use Case
TextPayloadText-based messages using key-value pairsLogin requests, action commands
GamePayloadBinary game update packetsPlayer movement, tile changes
VariantPayloadFunction calls with typed argumentsOnSpawn, OnRemove events
RawPayloadUnprocessed binary dataPass-through for unknown packets

Event System

GTProxy uses the eventpp library to implement a priority-based event dispatcher. See Event System for complete details.

Event Dispatcher

The event::Dispatcher (/home/daytona/workspace/source/src/event/event.hpp:231) manages all event subscriptions and dispatching:
class PriorityEventDispatcher {
public:
    Handle appendListener(const Type event, const Callback& callback, 
                         const int8_t priority = Priority::Normal);
    Handle prependListener(const Type event, const Callback& callback);
    bool removeListener(Type event, const Handle& handle);
    void dispatch(const Event& e) const;
};

Event Types

Core events defined in /home/daytona/workspace/source/src/event/event.hpp:27:
enum class Type : uint32_t {
    ClientConnect = 0,
    ServerConnect,
    ClientDisconnect,
    ServerDisconnect,
    ClientBoundPacket,
    ServerBoundPacket,
    PacketEventOffset = 0x1000,  // Packet-specific events start here
    Max
};
Packet events use IDs starting at 0x1000 offset to avoid conflicts with core events. The ID is derived as PacketEventOffset + PacketId.

Handler Module

Handlers subscribe to events and implement the proxy’s core logic:

ConnectionHandler

Manages the connection lifecycle, coordinating when the client connects to/disconnects from the server.

ForwardingHandler

The ForwardingHandler (/home/daytona/workspace/source/src/core/handlers/forwarding_handler.hpp:9) routes packets between client and server:
class ForwardingHandler {
public:
    ForwardingHandler(
        event::Dispatcher& dispatcher,
        network::Client& client,
        network::Server& server
    );

private:
    void setup_raw_packet_handlers();
    std::vector<event::ScopedHandle> handles_;
};
This handler listens to ClientBoundPacket and ServerBoundPacket events and forwards them to the appropriate destination unless canceled by another handler.

WorldHandler

Manages world state, tracking tiles, objects, and player position based on packets flowing through the proxy.

Scripting Module

The scripting system uses Lua (via sol2) to expose GTProxy functionality to user scripts.

LuaEngine

The scripting::LuaEngine (/home/daytona/workspace/source/src/scripting/lua_engine.hpp:12) manages the Lua runtime:
class LuaEngine final : public IScriptEngine {
public:
    bool execute(std::string_view script) override;
    bool execute_file(const std::filesystem::path& path) override;
    void register_binding(std::unique_ptr<IBindingModule> binding) override;

    [[nodiscard]] sol::state& state() { return lua_; }
private:
    sol::state lua_;
    std::vector<std::unique_ptr<IBindingModule>> bindings_;
};

Binding Modules

Each binding module exposes specific functionality to Lua:
  • EventBindings: Register event listeners from Lua
  • PacketBindings: Create and modify packets
  • CommandBindings: Register custom commands
  • LoggerBindings: Logging functions
  • PlayerBindings: Access player state
  • WorldBindings: Access world state
  • SchedulerBindings: Schedule delayed/periodic tasks
  • ItemBindings: Query item database

ScriptEventBridge

Bridges C++ events to Lua callbacks, allowing scripts to listen to packet events and connection events.

ScriptScheduler

Provides async task scheduling to Lua scripts, wrapping the core scheduler.

Scheduler Module

The core::Scheduler (/home/daytona/workspace/source/src/core/scheduler.hpp:34) provides thread-safe task scheduling:
class Scheduler {
public:
    TaskId schedule_immediate(std::function<void()> callback, 
                             const std::string& tag = "",
                             TaskPriority priority = TaskPriority::Normal);
    
    TaskId schedule_delayed(std::function<void()> callback,
                           std::chrono::milliseconds delay,
                           const std::string& tag = "",
                           TaskPriority priority = TaskPriority::Normal);
    
    TaskId schedule_periodic(std::function<void()> callback,
                            std::chrono::milliseconds interval,
                            const std::string& tag = "",
                            TaskPriority priority = TaskPriority::Normal);
    
    bool cancel(TaskId id);
    std::size_t cancel_by_tag(const std::string& tag);
};

Task Priorities

enum class TaskPriority : std::int8_t {
    Highest = -128,
    High = -64,
    Normal = 0,
    Low = 64,
    Lowest = 127
};
The scheduler uses a worker thread pool (default: hardware_concurrency) and a separate timer thread for delayed execution.

Utilities Module

Shared utilities used across the codebase:

ByteStream

The utils::ByteStream (/home/daytona/workspace/source/src/utils/byte_stream.hpp:9) provides binary serialization:
template <typename LengthType = std::uint16_t>
class ByteStream {
public:
    void write_data(const void* ptr, const std::size_t size);
    template <typename T> void write(const T& value);
    void write(const std::string& str, const bool write_length_info = true);

    bool read_data(void* ptr, const std::size_t size);
    template <typename T> bool read(T& value);
    bool read(std::string& str, LengthType length = 0);
};

Other Utilities

  • TextParse: Parses key-value text format used by Growtopia
  • Singleton: Thread-safe singleton template
  • Hash: Hashing utilities (Proton hash for Growtopia strings)
  • Random: Random number generation
  • Network: Network utilities (DNS resolution)

Data Flow Example

Here’s how a packet flows through the system:

Initialization Sequence

On startup, the core::Core constructor initializes subsystems in this order:
  1. Load configuration
  2. Create event dispatcher
  3. Initialize scheduler with worker threads
  4. Create network server (listening on proxy port)
  5. Create network client (for outbound connections)
  6. Initialize web server (for HTTP API)
  7. Set up event handlers (connection, forwarding, world)
  8. Initialize Lua engine and load bindings
  9. Load user scripts from scripts/ directory
  10. Start command handler for console input

Thread Safety

GTProxy uses multiple threads:
  • Main Thread: Runs the core event loop and network service
  • Scheduler Worker Threads: Execute scheduled tasks (default: CPU core count)
  • Scheduler Timer Thread: Manages delayed task execution
  • Web Server Threads: Handle HTTP requests (if enabled)
When writing Lua scripts or C++ handlers, be aware that event callbacks may execute on different threads. Use the scheduler for thread-safe deferred execution.

Next Steps

Packet System

Learn how packets are decoded and encoded

Event System

Understand the event-driven architecture

Lua Scripting

Start writing Lua scripts for GTProxy

Building from Source

Build and develop GTProxy