Permissions, Whitelist, and Admin System
Advanced20-30 minutesWindowsVisual Studio
Unturned's server access control is divided into three independent systems: the admin list (SteamAdminlist), the whitelist (SteamWhitelist), and the ban list (SteamBlacklist). Each is a static class that manages a typed ID list with file persistence through River binary serialization. The CommandWhitelisted toggle controls whether the whitelist is enforced.
Source code location: Unturned/Provider/SteamAdminlist.cs, SteamAdminID.cs, SteamWhitelist.cs, SteamWhitelistID.cs, SteamBlacklist.cs, SteamBlacklistID.cs, Command/CommandWhitelisted.cs
SteamAdminlist — Admin ID Management
SteamAdminlist maintains a List<SteamAdminID> of players with elevated privileges. Each entry couples the admin's SteamID with the judge ID who granted admin status:
csharp
public class SteamAdminID
{
public CSteamID playerID { get; private set; }
public CSteamID judgeID;
}Core Methods
admin(CSteamID playerID, CSteamID judgeID) — Adds or updates an admin entry. If the player is already in the list, only the judge ID is updated. If new, a SteamAdminID is appended. When the player is currently connected, client.isAdmin is set to true and an Admined network message is broadcast. The broadcast respects Provider.hideAdmins — the admined player always receives the notification, but other clients only see it if admin visibility is enabled.
unadmin(CSteamID playerID) — Sets client.isAdmin = false for the connected player, broadcasts an Unadmined message, and removes the entry from the list.
checkAdmin(CSteamID playerID) — Returns true if the player is the server owner (ownerID) or appears in the admin list. This is the primary authorization check used by commands and protected operations.
checkAC(CSteamID playerID) — An anti-cheat stub that currently logs the SteamID and its SHA1 hash, then returns false. This method exists for future anti-cheat integration.
Persistence
The admin list is serialized to /Server/Adminlist.dat using the River binary format. The save format (version 2) writes a byte version header, a ushort count, then a sequence of SteamID pairs (playerID, judgeID). Load reverses this process:
csharp
public static void load()
{
_list = new List<SteamAdminID>();
ownerID = CSteamID.Nil;
if (ServerSavedata.fileExists("/Server/Adminlist.dat"))
{
River river = ServerSavedata.openRiver("/Server/Adminlist.dat", true);
byte version = river.readByte();
if (version > 1)
{
ushort count = river.readUInt16();
for (ushort i = 0; i < count; i++)
list.Add(new SteamAdminID(river.readSteamID(), river.readSteamID()));
}
river.closeRiver();
}
}The ownerID is set externally by Dedicator during server initialization and identifies the server's owner, who bypasses admin checks entirely.
SteamWhitelist — Access Restriction
SteamWhitelist manages a List<SteamWhitelistID> of players permitted to join when the whitelist is active:
csharp
public class SteamWhitelistID
{
public CSteamID steamID { get; private set; }
public string tag; // Arbitrary label for the whitelist entry
public CSteamID judgeID; // Who added this entry
}Core Methods
whitelist(CSteamID steamID, string tag, CSteamID judgeID) — Adds or updates a whitelist entry. If the SteamID already exists, its tag and judgeID are updated in place.
unwhitelist(CSteamID steamID) — Removes the entry. If the whitelist is currently enforced (Provider.isWhitelisted is true), the player is kicked with reason "Removed from whitelist.".
checkWhitelisted(CSteamID steamID) — Linear scan for the SteamID. Returns true if found. This is called during the player connection pipeline to accept or reject incoming players.
The Whitelist Toggle
CommandWhitelisted sets Provider.isWhitelisted = true. This command is only available on dedicated servers before the server starts (it checks Provider.isServer is false). When whitelist is enabled, the Provider rejects any connection whose SteamID is not in SteamWhitelist.list.
Persistence
Serialized to /Server/Whitelist.dat (version 2). The format is: byte version, ushort count, then per-entry: SteamID, string tag, SteamID judgeID.
SteamBlacklist — Ban Management
SteamBlacklist maintains a List<SteamBlacklistID> of banned players with the most sophisticated persistence of the three systems. Each entry records the player's SteamID, IPv4 address, HWID hashes, judge ID, reason, duration, and ban timestamp:
csharp
public class SteamBlacklistID
{
public CSteamID playerID { get; private set; }
public uint ip { get; private set; }
internal byte[][] hwids;
public CSteamID judgeID;
public string reason;
public uint duration;
public uint banned;
public bool isExpired => Provider.time > banned + duration;
public uint getTime() => duration - (Provider.time - banned);
}Multi-Factor Ban Detection
The checkBanned method performs a three-factor matching scan:
- SteamID match — exact match on
playerID - IP match — matches the IPv4 address (only if
ip != 0) - HWID match — compares each stored HWID hash against the client's HWIDs using
Hash.verifyHash
Any single match triggers a ban block. Expired entries are automatically removed during scan:
csharp
if (list[index].isExpired)
{
list.RemoveAt(index);
return false;
}The DoesAnyHwidMatch method iterates over all stored HWIDs and all client HWIDs, performing pairwise hash verification.
Ban Constants
PERMANENT = 60 * 60 * 24 * 365— 1 year (functionally permanent)TEMPORARY = 60 * 3— 3 minutes (used for test bans)
Persistence
Serialized to /Server/Blacklist.dat (version 4, which added HWID support). The format evolution shows the system's growth: version 1 (basic SteamID), version 2 (added ip field), version 3, version 4 (added HWID array). The deserialization handles backward compatibility by checking the version byte before reading each field:
csharp
byte version = river.readByte();
// ...
if (version > 2) { ip = river.readUInt32(); }
if (version >= SAVEDATA_VERSION_ADDED_HWID) { /* read HWID array */ }When saving, expired entries are not written — the save method writes all entries from list, but the load method filters out expired entries during deserialization:
csharp
if (!blacklistID.isExpired)
list.Add(blacklistID);Integration with Provider
The three systems integrate with Provider at specific lifecycle points:
- Player connection —
Provider.checkBanverifies the incoming player againstSteamBlacklist, thenProvider.checkWhitelistedchecks againstSteamWhitelistif whitelist mode is active. - Permission checks — Commands call
SteamAdminlist.checkAdminfor authorization. The server owner (SteamAdminlist.ownerID) bypasses all checks. - Server save —
SaveManager.save()callsSteamWhitelist.save(),SteamBlacklist.save(), andSteamAdminlist.save()on dedicated servers.
