docs/ENet Connection

ENet & Network Protocol

UDP networking with reliability, compression, and custom packet protocol

ENet Initialization

int init_enet(int port) {
    if (enet_initialize() != 0) {
        printf("[ENET] enet_initialize failed\n");
        return -1;
    }
    
    ENetAddress address;
    memset(&address, 0, sizeof(address));
    address.type = ENET_ADDRESS_TYPE_IPV4;
    address.port = port;
    
    // Retry up to 5 times if port is busy
    for (int attempt = 0; attempt < 5; attempt++) {
        server = enet_host_create(ENET_ADDRESS_TYPE_IPV4, &address, 1024, 2, 0, 0);
        if (server != NULL) break;
        Sleep(2000);
    }
    
    if (server == NULL) return -1;
    
    // Critical settings
    server->checksum = enet_crc32;                    // CRC32 integrity
    enet_host_compress_with_range_coder(server);      // Range Coder compression
    server->usingNewPacketForServer = 1;              // New packet protocol
    
    printf("[ENET] listening on 0.0.0.0:%d (new packet protocol)\n", port);
    return 0;
}

Configuration Parameters

SettingValuePurpose
Max Peers1024Concurrent connections
Channels2Reliable (0) + Unreliable (1)
Incoming Bandwidth0 (unlimited)Downstream throttle
Outgoing Bandwidth0 (unlimited)Upstream throttle
Checksumenet_crc32Packet integrity
CompressionRange CoderPayload compression
New Protocolenabled (1)Growtopia fork format

New Packet Protocol Format

The Growtopia fork uses a custom packet format with integrity checking and compression:

Wire Format (UDP Datagram)

[6 bytes integrity][2 bytes peerID][2 bytes sentTime][4 bytes CRC32][compressed payload]

Total header: 14 bytes minimum

Integrity Header (6 bytes = 3 x uint16)

integrity[0]: 0x16EC (constant)
integrity[1]: 0xD951 (constant) 
integrity[2]: 0x0000 (reserved/flags)

Peer ID Encoding (2 bytes)

Bits:  [15:14] flags    [13:12] sessionID  [11:0] peerID
Flags: 0xC000 = connect, 0x8000 = disconnect, 0x4000 = acknowledge

Decompression Pipeline

  1. Verify CRC32 over [header(10) + zero slot(4) + decompressed payload]
  2. Range Coder decompress payload
  3. Process decompressed ENet commands

Diagnostic Tools

  • enet_decode2 - Decode captured packets
  • enet_test_server - Standard ENet server with intercept callback
  • enet_test2 - Fork-specific test (usingNewPacketForServer=1)

Event Loop

ENetEvent event;
while (true) {
    // 3ms timeout for responsive processing
    while (enet_host_service(server, &event, 3000) > 0) {
        if (f_saving_ == false && auto_save == false) {
            switch (event.type) {
                case ENET_EVENT_TYPE_CONNECT:
                    handle_connect(event.peer);
                    break;
                case ENET_EVENT_TYPE_RECEIVE:
                    handle_receive(event.peer, event.packet);
                    enet_packet_destroy(event.packet);
                    break;
                case ENET_EVENT_TYPE_DISCONNECT:
                    handle_disconnect(event.peer);
                    break;
            }
        }
    }
    // Game tick processing outside event loop
    loop_worlds();
}

Peer Data Management

Each connected peer stores a Player* in peer->data:

// On CONNECT:
peer->data = new Player;
pInfo(peer)->ip = client_ip;
pInfo(peer)->id = peer->connectID;

// Access macro
#define pInfo(peer) ((Player*)(peer->data))

// On DISCONNECT:
delete (Player*)peer->data;
peer->data = NULL;

Packet Types & Message IDs

Message IDNameDirectionDescription
1ENet Connect ACKS->CInitial handshake
2Text PacketC->SLogin, chat, dialogs, commands
3Extended TextC->SBypass-protected actions
4Binary PacketBothMovement, actions, world data

Rate Limiting & Protection

// Per-peer limits
if (pInfo(peer)->all_packets >= 560) {
    enet_peer_disconnect_later(peer, 0);  // Flood protection
}
if (pInfo(peer)->pps >= 16) {              // Packets per second
    enet_peer_disconnect_later(peer, 0);
}

// Login rate limiting
if (Server_Security.login_count > 40 || Server_Security.update_item_data > 15) {
    failed_login(peer, "Too many people logging in");
}