Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ZTzTopia/GTProxy/llms.txt

Use this file to discover all available pages before exploring further.

Getting Started

GTProxy is a C++23 network proxy for Growtopia that enables packet debugging and modification. This guide will help you understand the codebase structure and development workflow.

Prerequisites

Before contributing, make sure you have:
  • Built GTProxy from source (see Building from Source)
  • Familiarity with C++23 features
  • Understanding of CMake and Conan package management
  • Knowledge of network protocols and packet structures

Project Structure

GTProxy/
├── src/                  # Main source code
│   ├── core/            # Application core, config, logging, scheduler, web server
│   ├── network/         # ENet client/server connections
│   ├── packet/          # Packet types, encoding/decoding, event handling
│   │   ├── game/        # Game-specific packet handlers
│   │   └── message/     # Message packet types
│   ├── world/           # World entity and state management
│   ├── player/          # Player entity and state management
│   ├── command/         # In-proxy command system
│   │   └── commands/    # Individual command implementations
│   ├── scripting/       # Lua scripting engine (sol2)
│   │   └── bindings/    # Lua API bindings
│   ├── event/           # Event dispatcher system
│   └── utils/           # Utilities (ByteStream, TextParse, hash, random)
├── lib/                 # External libs (enet, cpp-httplib) as Git submodules
├── tests/               # Google Test unit tests
├── scripts/             # Lua scripts for extending proxy
├── resources/           # Runtime resources (SSL certs)
└── cmake/               # CMake modules and configurations

Key Directories

Contains the main application logic, configuration management, logging setup, task scheduler, and built-in web server for metadata access.
Implements ENet-based client and server connections for handling Growtopia’s UDP networking protocol.
Defines packet types, encoding/decoding logic, and event handling. The game/ subdirectory contains game-specific packet handlers, while message/ contains message packet types.
Integrates Lua scripting using sol2. The bindings/ directory contains C++ to Lua API bindings for extending proxy functionality.
Common utilities including ByteStream for binary data manipulation, TextParse for string processing, hash functions, and random number generation.

Code Style Guidelines

Naming Conventions

Consistency is critical. Follow these naming conventions:
ElementStyleExample
Classes/StructsPascalCaseCore, ByteStream, Config
Functions/Methodssnake_caseget_config, is_connected
Member variablessnake_case_config_, running_, peer_
Local variablessnake_casesleep_duration, packet_data
Enums (unscoped)SCREAMING_SNAKE_CASENET_MESSAGE_UNKNOWN
Enum classesPascalCase valuesResult::Success, Result::Failed
Namespaceslowercasecore, network, packet
Template paramsPascalCaseLengthType, Func

Header Guards

Always use #pragma once instead of traditional include guards:
#pragma once

// Header content here

Include Order

Organize includes in this order:
  1. Own header (in .cpp files)
  2. Standard library headers (<vector>, <string>, <memory>)
  3. External library headers (<enet/enet.h>, <spdlog/spdlog.h>)
  4. Project headers with relative paths ("../packet/packet_types.hpp")
#include "my_class.hpp"

#include <memory>
#include <string>
#include <vector>

#include <enet/enet.h>
#include <spdlog/spdlog.h>

#include "../packet/packet_types.hpp"
#include "../utils/byte_stream.hpp"

Formatting

  • Indentation: 4 spaces (no tabs)
  • Braces: Same line for class/function declarations
  • Brace initialization: Prefer Type name{ value } over Type name = value
  • Line length: No strict limit, but keep code readable
// Preferred brace initialization
const auto begin{ static_cast<const std::byte*>(ptr) };
ByteStream bs{};
int port{ 16999 };

// Function declaration
void process_packet(std::span<const std::byte> data) {
    // Implementation
}

Types and Attributes

  • Use [[nodiscard]] on getters and functions where ignoring the return value is likely a bug
  • Use final on classes not intended for inheritance
  • Use override on all virtual method overrides
  • Prefer std::byte over char/unsigned char for binary data
  • Prefer std::span<const std::byte> for non-owning byte ranges
  • Prefer std::string_view for non-owning string parameters
  • Use explicit integer types: std::uint8_t, std::uint16_t, std::uint32_t
class Config final {
public:
    [[nodiscard]] std::uint16_t get_port() const noexcept { return port_; }
    
    void process_data(std::span<const std::byte> data);
    void set_name(std::string_view name);

private:
    std::uint16_t port_{ 16999 };
};

Error Handling

  • Throw std::runtime_error with brace initialization for fatal errors
  • Use early returns for error conditions
  • Use std::optional for fallible operations that may not have a value
  • Return bool for simple success/failure operations
if (!host_) {
    throw std::runtime_error{ "Failed to create host" };
}

if (data.size() < 4) {
    spdlog::warn("Malformed packet (size {})", data.size());
    return false;
}

const auto decoded{ decoder_.decode(data) };
if (!decoded.has_value()) {
    return;
}

Logging

Use spdlog for all logging. Available levels: trace, debug, info, warn, error.
spdlog::info("Connected to server at {}:{}", host, port);
spdlog::warn("Received malformed packet (size {})", data.size());
spdlog::error("Failed to initialize: {}", error_msg);

Memory Management

  • Use std::unique_ptr for exclusive ownership
  • Use std::shared_ptr only when shared ownership is required
  • Use references for non-owning parameters
  • Store references as member variables when lifetime is guaranteed
class NetworkManager {
public:
    explicit NetworkManager(Config& config) : config_{ config } {}
    
private:
    Config& config_;  // Non-owning reference
    std::unique_ptr<Connection> connection_;  // Exclusive ownership
};

Testing

GTProxy uses Google Test for unit testing. Tests are located in the tests/ directory.

Running Tests

# Run all tests with CTest
ctest --test-dir build --output-on-failure

# Run tests directly
./build/tests/GTProxy_tests

# Run a specific test
./build/tests/GTProxy_tests --gtest_filter=ByteStreamTest.WriteAndReadBasicTypes

# Run all tests in a suite
./build/tests/GTProxy_tests --gtest_filter=ByteStreamTest.*

# List all available tests
./build/tests/GTProxy_tests --gtest_list_tests

Writing Tests

Create test files following the pattern test_<module>.cpp:
#include <gtest/gtest.h>
#include "../src/utils/byte_stream.hpp"

TEST(ByteStreamTest, WriteAndReadBasicTypes) {
    ByteStream bs{};
    bs.write<std::uint32_t>(42);
    
    const auto value{ bs.read<std::uint32_t>() };
    EXPECT_EQ(value, 42);
}

Creating New Packet Structures

When implementing new packet types, follow these steps:
1

Define Packet ID

Add a new enum value to PacketId in src/packet/packet_id.hpp:
enum class PacketId : std::uint8_t {
    Unknown,
    // ... existing packets
    YourNewPacket,
};
2

Map Packet ID

Add the mapping to VARIANT_FUNCTION_MAP (for variant strings) or GAME_PACKET_MAP (for game packets) in src/packet/packet_id.hpp:
inline constexpr auto VARIANT_FUNCTION_MAP = std::array{
    std::pair{"OnYourNewPacket", PacketId::YourNewPacket},
    // ... other mappings
};
3

Create Packet Struct

Define a new struct in the appropriate file (e.g., src/packet/game/world.hpp):
struct YourNewPacket : VariantPacket<PacketId::YourNewPacket> {
    std::uint32_t some_value{};
    std::string some_string{};

    void read(const Payload& payload) override {
        // Decode from payload
    }

    [[nodiscard]] Payload write() const override {
        // Encode to payload
    }
};
4

Register Packet

Register the new packet in src/packet/register_packets.hpp:
void register_packets(PacketRegistry& registry) {
    registry.register_packet<packet::game::YourNewPacket>();
    // ... other registrations
}

Lua Scripting

GTProxy supports Lua scripting for extending functionality. Scripts in scripts/ are loaded automatically at startup.

Adding or Updating Bindings

When creating new Lua bindings:
  1. Create Test Script: Add scripts/test_{feature_name}.lua to verify functionality
  2. Document API: Update scripts/AGENTS.md with the new Lua API
  3. Example Usage: Include example usage demonstrating common use cases
For detailed Lua scripting guidelines, see scripts/AGENTS.md in the source repository.

Build Configuration

CMake Options

The project uses CMake 3.24+ with the following key settings:
  • C Standard: C11
  • C++ Standard: C++23 (required)
  • Conan Integration: Automatic via cmake/cmake-conan/conan_provider.cmake

Adding Dependencies

To add a new Conan dependency, edit conanfile.py:
def requirements(self):
    self.requires('your-package/[~version]')
    # ... existing dependencies
For Git submodules:
cd lib
git submodule add https://github.com/user/repo.git

Next Steps

Building from Source

Learn how to build GTProxy from source

Troubleshooting

Find solutions to common development issues

Build docs developers (and LLMs) love