Server Transfer
Hytale implements a native server transfer system that allows players to seamlessly move between servers without proxy software like BungeeCord. This is accomplished through a “referral” mechanism with support for a 4KB custom data payload.
How It Works
Section titled “How It Works”The transfer is entirely client-driven - Server A tells the client where to go and what data to carry, then the client connects to Server B with that data.
Packets Involved
Section titled “Packets Involved”ClientReferral (ID: 18)
Section titled “ClientReferral (ID: 18)”Sent from server to client to initiate a transfer.
| Field | Type | Max Size | Description |
|---|---|---|---|
hostTo | HostAddress | ~1KB | Target server address |
data | byte[] | 4096 bytes | Custom payload for the target server |
The HostAddress structure contains:
public class HostAddress { public String host; // max 256 characters public short port; // 1-65535}Connect (ID: 0)
Section titled “Connect (ID: 0)”The initial connection packet, which includes referral data when the player was transferred.
| Field | Type | Description |
|---|---|---|
referralData | byte[] | The 4KB payload from the source server |
referralSource | HostAddress | Which server sent this player |
Transferring Players
Section titled “Transferring Players”Basic Transfer
Section titled “Basic Transfer”// transfer player to another serverplayerRef.referToServer("minigames.example.com", 25565);Transfer with Data
Section titled “Transfer with Data”Pass custom data (up to 4KB) to the destination server:
// serialize player state, session info, etc.byte[] sessionData = new byte[4096];// ... populate with your data
playerRef.referToServer("minigames.example.com", 25565, sessionData);From PlayerRef
Section titled “From PlayerRef”The PlayerRef class provides the transfer API:
public class PlayerRef { // simple transfer public void referToServer(String host, int port);
// transfer with custom data (max 4096 bytes) public void referToServer(String host, int port, byte[] data);}Receiving Transferred Players
Section titled “Receiving Transferred Players”PlayerSetupConnectEvent
Section titled “PlayerSetupConnectEvent”Listen to this event to handle incoming transferred players:
eventRegistry.register(PlayerSetupConnectEvent.class, event -> { // check if this is a transferred player if (event.isReferralConnection()) { byte[] data = event.getReferralData(); HostAddress source = event.getReferralSource();
// log the transfer System.out.println("Player " + event.getUsername() + " transferred from " + source.host + ":" + source.port + " with " + data.length + " bytes of data");
// validate and process the data if (!validateReferralData(data)) { event.setCancelled(true); event.setReason("Invalid transfer data"); return; }
// restore player state from the data restorePlayerState(event.getUuid(), data); }});Re-routing Players
Section titled “Re-routing Players”You can also redirect incoming players to a different server:
eventRegistry.register(PlayerSetupConnectEvent.class, event -> { // route to a different server based on some logic if (shouldRedirect(event.getUuid())) { event.referToServer("lobby.example.com", 25565, event.getReferralData()); // player will be redirected, won't join this server }});Event API
Section titled “Event API”public class PlayerSetupConnectEvent { // player information UUID getUuid(); String getUsername(); PlayerAuthentication getAuth();
// referral information boolean isReferralConnection(); byte[] getReferralData(); // the 4KB payload (or null) HostAddress getReferralSource(); // source server (or null)
// actions void referToServer(String host, int port); void referToServer(String host, int port, byte[] data);
// cancel connection void setCancelled(boolean cancelled); void setReason(String reason);}Built-in Commands
Section titled “Built-in Commands”Hytale includes a built-in command for testing transfers:
/refer <player> <host> <port>/transfer <player> <host> <port>Permissions:
command.refer.self- Transfer yourselfcommand.refer.other- Transfer other players
Security Considerations
Section titled “Security Considerations”Example: Signed Transfer Data
Section titled “Example: Signed Transfer Data”// on source server - sign the datapublic byte[] createSignedTransferData(UUID playerId, long timestamp) { ByteBuffer buffer = ByteBuffer.allocate(4096);
// write payload buffer.putLong(playerId.getMostSignificantBits()); buffer.putLong(playerId.getLeastSignificantBits()); buffer.putLong(timestamp); // ... more data
// sign with shared secret byte[] signature = hmacSign(buffer.array(), SHARED_SECRET); buffer.put(signature);
return buffer.array();}
// on destination server - verify signaturepublic boolean validateTransferData(byte[] data, UUID expectedPlayer) { ByteBuffer buffer = ByteBuffer.wrap(data);
UUID playerId = new UUID(buffer.getLong(), buffer.getLong()); long timestamp = buffer.getLong();
// check timestamp (prevent replay) if (System.currentTimeMillis() - timestamp > 30000) { return false; // expired }
// verify signature // ... extract and verify HMAC
return playerId.equals(expectedPlayer);}