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
| Setting | Value | Purpose |
|---|---|---|
| Max Peers | 1024 | Concurrent connections |
| Channels | 2 | Reliable (0) + Unreliable (1) |
| Incoming Bandwidth | 0 (unlimited) | Downstream throttle |
| Outgoing Bandwidth | 0 (unlimited) | Upstream throttle |
| Checksum | enet_crc32 | Packet integrity |
| Compression | Range Coder | Payload compression |
| New Protocol | enabled (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 minimumIntegrity 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 = acknowledgeDecompression Pipeline
- Verify CRC32 over [header(10) + zero slot(4) + decompressed payload]
- Range Coder decompress payload
- Process decompressed ENet commands
Diagnostic Tools
enet_decode2- Decode captured packetsenet_test_server- Standard ENet server with intercept callbackenet_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 ID | Name | Direction | Description |
|---|---|---|---|
| 1 | ENet Connect ACK | S->C | Initial handshake |
| 2 | Text Packet | C->S | Login, chat, dialogs, commands |
| 3 | Extended Text | C->S | Bypass-protected actions |
| 4 | Binary Packet | Both | Movement, 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");
}