Realm List Protocol Guide
Overview
The realm list protocol allows the client to retrieve the list of available game servers (realms) from the authentication server after successful authentication. This is the second step in the connection flow, following authentication.
Connection Flow
1. Connect to auth server (port 3724)
2. Authenticate (LOGON_CHALLENGE + LOGON_PROOF)
3. Request realm list (REALM_LIST)
4. Select realm
5. Connect to world server (realm's address)
Implementation
Data Structures
Realm
The Realm struct contains all information about a game server:
struct Realm {
uint8_t icon; // Realm icon type
uint8_t lock; // Lock status
uint8_t flags; // Realm flags (bit 0x04 = has version info)
std::string name; // Realm name (e.g., "My Private Server")
std::string address; // Server address (e.g., "localhost:8085")
float population; // Population level (0.0 to 2.0+)
uint8_t characters; // Number of characters player has on this realm
uint8_t timezone; // Timezone ID
uint8_t id; // Realm ID
// Version info (conditional - only if flags & 0x04)
uint8_t majorVersion = 0; // Major version (e.g., 3)
uint8_t minorVersion = 0; // Minor version (e.g., 3)
uint8_t patchVersion = 0; // Patch version (e.g., 5)
uint16_t build = 0; // Build number (e.g., 12340 for 3.3.5a)
[[nodiscard]] bool hasVersionInfo() const { return (flags & 0x04) != 0; }
};
RealmListResponse
Container for the list of realms:
struct RealmListResponse {
std::vector<Realm> realms; // All available realms
};
API Usage
Basic Usage
#include "auth/auth_handler.hpp"
using namespace wowee::auth;
// Create auth handler
AuthHandler auth;
// Connect to auth server
if (!auth.connect("logon.myserver.com", 3724)) {
std::cerr << "Failed to connect" << std::endl;
return;
}
// Set up callbacks
auth.setOnSuccess([&auth](const std::vector<uint8_t>& sessionKey) {
std::cout << "Authentication successful!" << std::endl;
std::cout << "Session key size: " << sessionKey.size() << " bytes" << std::endl;
// Request realm list after successful authentication
auth.requestRealmList();
});
auth.setOnRealmList([](const std::vector<Realm>& realms) {
std::cout << "Received " << realms.size() << " realms:" << std::endl;
for (const auto& realm : realms) {
std::cout << " - " << realm.name << " (" << realm.address << ")" << std::endl;
std::cout << " Population: " << realm.population << std::endl;
std::cout << " Characters: " << (int)realm.characters << std::endl;
}
});
auth.setOnFailure([](const std::string& reason) {
std::cerr << "Authentication failed: " << reason << std::endl;
});
// Start authentication
auth.authenticate("username", "password");
// Main loop
while (auth.getState() != AuthState::REALM_LIST_RECEIVED &&
auth.getState() != AuthState::FAILED) {
auth.update(0.016f); // ~60 FPS
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
// Access realm list
const auto& realms = auth.getRealms();
if (!realms.empty()) {
std::cout << "First realm: " << realms[0].name << std::endl;
}
Complete Example with Realm Selection
#include "auth/auth_handler.hpp"
#include <iostream>
#include <thread>
#include <chrono>
int main() {
using namespace wowee::auth;
AuthHandler auth;
// Connect
std::cout << "Connecting to authentication server..." << std::endl;
if (!auth.connect("localhost", 3724)) {
std::cerr << "Connection failed" << std::endl;
return 1;
}
// Set up success callback to request realms
auth.setOnSuccess([&auth](const std::vector<uint8_t>& sessionKey) {
std::cout << "\n========================================" << std::endl;
std::cout << " AUTHENTICATION SUCCESSFUL!" << std::endl;
std::cout << "========================================" << std::endl;
std::cout << "Session key: " << sessionKey.size() << " bytes" << std::endl;
// Automatically request realm list
std::cout << "\nRequesting realm list..." << std::endl;
auth.requestRealmList();
});
// Set up realm list callback
bool gotRealms = false;
auth.setOnRealmList([&gotRealms](const std::vector<Realm>& realms) {
std::cout << "\n========================================" << std::endl;
std::cout << " AVAILABLE REALMS" << std::endl;
std::cout << "========================================" << std::endl;
for (size_t i = 0; i < realms.size(); ++i) {
const auto& realm = realms[i];
std::cout << "\n[" << (i + 1) << "] " << realm.name << std::endl;
std::cout << " Address: " << realm.address << std::endl;
std::cout << " Population: ";
// Interpret population level
if (realm.population < 0.5f) {
std::cout << "Low (Green)";
} else if (realm.population < 1.0f) {
std::cout << "Medium (Yellow)";
} else if (realm.population < 2.0f) {
std::cout << "High (Red)";
} else {
std::cout << "Full (Red)";
}
std::cout << " (" << realm.population << ")" << std::endl;
std::cout << " Your characters: " << (int)realm.characters << std::endl;
std::cout << " Icon: " << (int)realm.icon << std::endl;
std::cout << " Lock: " << (realm.lock ? "Locked" : "Unlocked") << std::endl;
if (realm.hasVersionInfo()) {
std::cout << " Version: " << (int)realm.majorVersion << "."
<< (int)realm.minorVersion << "."
<< (int)realm.patchVersion << " (build "
<< realm.build << ")" << std::endl;
}
}
gotRealms = true;
});
// Set up failure callback
auth.setOnFailure([](const std::string& reason) {
std::cerr << "\n========================================" << std::endl;
std::cerr << " AUTHENTICATION FAILED" << std::endl;
std::cerr << "========================================" << std::endl;
std::cerr << "Reason: " << reason << std::endl;
});
// Authenticate
std::cout << "Authenticating..." << std::endl;
auth.authenticate("myuser", "mypass");
// Main loop - wait for realm list or failure
while (!gotRealms && auth.getState() != AuthState::FAILED) {
auth.update(0.016f);
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
// Check result
if (gotRealms) {
const auto& realms = auth.getRealms();
// Example: Select first realm
if (!realms.empty()) {
const auto& selectedRealm = realms[0];
std::cout << "\n========================================" << std::endl;
std::cout << " REALM SELECTED" << std::endl;
std::cout << "========================================" << std::endl;
std::cout << "Realm: " << selectedRealm.name << std::endl;
std::cout << "Address: " << selectedRealm.address << std::endl;
// Next: parse the address and connect to the world server
// Example: "localhost:8085" -> host="localhost", port=8085
}
}
// Cleanup
auth.disconnect();
return gotRealms ? 0 : 1;
}
Protocol Details
REALM_LIST Request
Packet Structure:
Opcode: 0x10 (REALM_LIST)
Size: 5 bytes total
Bytes:
0: Opcode (0x10)
1-4: Unknown uint32 (always 0x00000000)
Building the Packet:
network::Packet RealmListPacket::build() {
network::Packet packet(static_cast<uint16_t>(AuthOpcode::REALM_LIST));
packet.writeUInt32(0x00); // Unknown field
return packet;
}
REALM_LIST Response
Packet Structure:
Opcode: 0x10 (REALM_LIST)
Variable length
Header:
Byte 0: Opcode (0x10)
Bytes 1-2: Packet size (uint16, little-endian)
Bytes 3-6: Unknown (uint32)
Bytes 7-8: Realm count (uint16, little-endian)
For each realm:
1 byte: Icon
1 byte: Lock
1 byte: Flags
C-string: Name (null-terminated)
C-string: Address (null-terminated, format: "host:port")
4 bytes: Population (float, little-endian)
1 byte: Characters (character count on this realm)
1 byte: Timezone
1 byte: ID
[Conditional - only if flags & 0x04:]
1 byte: Major version
1 byte: Minor version
1 byte: Patch version
2 bytes: Build (uint16, little-endian)
Vanilla layout:
Vanilla-family servers send a different shape, which differs only at the front:
Header:
Byte 7: Realm count (uint8)
For each realm:
4 bytes: Icon (uint32)
(no Lock byte)
1 byte: Flags
... the rest as above
RealmListResponseParser::parse(packet, response, legacyVanillaLayout) reads
whichever the caller asks for. The login screen asks for the vanilla layout
when the active expansion is classic or turtle, or its protocol version is
3 or lower (ClientInfo::legacyVanillaRealmList), since vmangos-derived 1.12
realms speak auth protocol 8 while still sending the vanilla entries. If a
modern-layout parse comes out with an empty realm name, the entry is re-read as
vanilla and the log warns Realm list entry looked shifted; retrying as vanilla
layout.
Packet Framing:
The TCPSocket automatically handles variable-length REALM_LIST packets:
// In TCPSocket::getExpectedPacketSize()
case 0x10: // REALM_LIST response
if (receiveBuffer.size() >= 3) {
uint16_t size = receiveBuffer[1] | (receiveBuffer[2] << 8);
return 1 + 2 + size; // opcode + size field + payload
}
return 0; // Need more data
Realm Flags
The flags field contains bitwise flags:
- Bit 0x04: Realm has version info (major, minor, patch, build)
- Other bits are for realm type and status (see TrinityCore documentation)
Example:
if (realm.flags & 0x04) {
// Realm includes version information
std::cout << "Version: " << (int)realm.majorVersion << "."
<< (int)realm.minorVersion << "."
<< (int)realm.patchVersion << std::endl;
}
Population Levels
The population field is a float representing server load:
- 0.0 - 0.5: Low (Green) - Server is not crowded
- 0.5 - 1.0: Medium (Yellow) - Moderate population
- 1.0 - 2.0: High (Red) - Server is crowded
- 2.0+: Full (Red) - Server is at capacity
Parsing Address
Realm addresses are in the format "host:port". Example parsing:
std::string parseHost(const std::string& address) {
size_t colonPos = address.find(':');
if (colonPos != std::string::npos) {
return address.substr(0, colonPos);
}
return address;
}
uint16_t parsePort(const std::string& address) {
size_t colonPos = address.find(':');
if (colonPos != std::string::npos) {
std::string portStr = address.substr(colonPos + 1);
return static_cast<uint16_t>(std::stoi(portStr));
}
return 8085; // Default world server port
}
// Usage
const auto& realm = realms[0];
std::string host = parseHost(realm.address);
uint16_t port = parsePort(realm.address);
std::cout << "Connecting to " << host << ":" << port << std::endl;
Authentication States
The AuthState enum now includes realm list states:
enum class AuthState {
DISCONNECTED, // Not connected
CONNECTED, // Connected, ready for auth
CHALLENGE_SENT, // LOGON_CHALLENGE sent
CHALLENGE_RECEIVED, // LOGON_CHALLENGE response received
PIN_REQUIRED, // Server asked for a PIN
AUTHENTICATOR_REQUIRED, // Server asked for an authenticator code
PROOF_SENT, // LOGON_PROOF sent
AUTHENTICATED, // Authentication successful, can request realms
REALM_LIST_REQUESTED, // REALM_LIST request sent
REALM_LIST_RECEIVED, // REALM_LIST response received
FAILED // Authentication or connection failed
};
State Transitions:
DISCONNECTED
↓ connect()
CONNECTED
↓ authenticate()
CHALLENGE_SENT
↓ (server response)
CHALLENGE_RECEIVED
↓ (automatic, or submitSecurityCode() after PIN_REQUIRED / AUTHENTICATOR_REQUIRED)
PROOF_SENT
↓ (server response)
AUTHENTICATED
↓ requestRealmList()
REALM_LIST_REQUESTED
↓ (server response)
REALM_LIST_RECEIVED
Testing
With Live Server
// Test against a WoW 3.3.5a private server
auth.connect("logon.my-wotlk-server.com", 3724);
auth.authenticate("testuser", "testpass");
// Wait for realm list
while (auth.getState() != AuthState::REALM_LIST_RECEIVED) {
auth.update(0.016f);
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
// Verify realms received
const auto& realms = auth.getRealms();
assert(!realms.empty());
assert(!realms[0].name.empty());
assert(!realms[0].address.empty());
With Mock Data
For testing without a live server, you can create mock realms:
Realm mockRealm;
mockRealm.id = 1;
mockRealm.name = "Test Realm";
mockRealm.address = "localhost:8085";
mockRealm.icon = 1;
mockRealm.lock = 0;
mockRealm.flags = 0x04; // Has version info
mockRealm.population = 1.5f; // High population
mockRealm.characters = 3; // 3 characters
mockRealm.timezone = 1;
mockRealm.majorVersion = 3;
mockRealm.minorVersion = 3;
mockRealm.patchVersion = 5;
mockRealm.build = 12340;
// Test parsing address
std::string host = parseHost(mockRealm.address);
assert(host == "localhost");
uint16_t port = parsePort(mockRealm.address);
assert(port == 8085);
Common Issues
1. “Cannot request realm list: not authenticated”
Cause: Tried to request realm list before authentication completed.
Solution: Only call requestRealmList() after authentication succeeds (in onSuccess callback or when state is AUTHENTICATED).
// WRONG
auth.authenticate("user", "pass");
auth.requestRealmList(); // Too soon!
// CORRECT
auth.setOnSuccess([&auth](const std::vector<uint8_t>& sessionKey) {
auth.requestRealmList(); // Call here
});
auth.authenticate("user", "pass");
2. Empty Realm List
Cause: Server has no realms configured.
Solution: Check server configuration. A typical WoW server should have at least one realm in its realmlist table.
3. “Unknown opcode or indeterminate size”
Cause: Server sent unexpected packet or packet framing failed.
Solution: Enable debug logging to see raw packet data, either by running the
client with WOWEE_LOG_LEVEL=debug or in code:
Logger::getInstance().setLogLevel(LogLevel::DEBUG);
4. Realm Address Unreachable on a LAN
Cause: The server advertises a public address in its realmlist table, and
the router does not route that address back inside the LAN.
Solution: Fix the realm’s address on the server, or run the client with
WOWEE_REALM_HOST_OVERRIDE=<local address>. The client then connects to that
host with the realm’s advertised port.
In the Client
The client wires this up in src/ui/auth_screen.cpp and
src/core/ui_screen_callback_handler.cpp:
- The login card’s Server list holds every server logged into before and
ChromieCraft (
logon.chromiecraft.com, port 3724, WotLK), which is added on every start if missing. Somewhere else… opens the Address and Port fields under more options. - After authentication the realm screen shows the realm list. Choosing a realm
splits its address at the colon (port 8085 when there is none), applies
WOWEE_REALM_HOST_OVERRIDEif set, and connects to the world server with the session key and the realm’s ID.
Next Steps
After receiving the realm list:
- Display realms to user - Show realm name, population, character count
- Let user select realm - Prompt for realm selection
- Parse realm address - Extract host and port from
addressfield - Connect to world server - Use the parsed host:port to connect
- Send CMSG_AUTH_SESSION - Authenticate with world server using session key
Example next step:
auth.setOnRealmList([&auth](const std::vector<Realm>& realms) {
if (realms.empty()) {
std::cerr << "No realms available" << std::endl;
return;
}
// Select first realm
const auto& realm = realms[0];
// Parse address
size_t colonPos = realm.address.find(':');
std::string host = realm.address.substr(0, colonPos);
uint16_t port = std::stoi(realm.address.substr(colonPos + 1));
// TODO: Connect to world server
std::cout << "Next: Connect to " << host << ":" << port << std::endl;
std::cout << "Send CMSG_AUTH_SESSION with session key" << std::endl;
// Get session key for world server authentication
const auto& sessionKey = auth.getSessionKey();
std::cout << "Session key: " << sessionKey.size() << " bytes" << std::endl;
});
Summary
The realm list protocol:
- ✅ Automatically handles variable-length packets
- ✅ Parses all realm information including version info
- ✅ Provides easy-to-use callback interface
- ✅ Includes comprehensive logging
- ✅ Ready for live server testing
Status: ✅ Complete and production-ready
Next Protocol: World server connection (CMSG_AUTH_SESSION)