Files
sbox-public/engine/Sandbox.Engine/Game/Services/ServerList/ServerList.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

164 lines
3.7 KiB
C#

using Sandbox.Engine;
using System.Net;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Sandbox.Services;
public class ServerList : List<ServerList.Entry>, IDisposable, IWeakInteropHandle
{
CServerList serverlist;
public ServerList()
{
Sandbox.InteropSystem.AllocWeak( this );
serverlist = CServerList.Create( this );
serverlist.AddFilter( "gametagsand", $"protocol:{Protocol.Network}" );
}
~ServerList()
{
Dispose();
}
public bool IsQuerying { get; private set; }
uint IWeakInteropHandle.InteropHandle { get; set; }
public void Query()
{
serverlist.StartQuery();
}
public void AddFilter( string key, string value )
{
serverlist.AddFilter( key, value );
}
internal unsafe void OnServerResponded( IntPtr ptr, ulong steamid )
{
var item = Unsafe.Read<gameserveritem_t>( (void*)ptr );
var tags = Marshal.PtrToStringUTF8( (IntPtr)item.m_szGameTags );
var ipAddress = new IPAddress( BitConverter.GetBytes( IPAddress.HostToNetworkOrder( (int)item.m_unIP ) ) ).ToString();
var entry = new Entry
{
IPAddressAndPort = $"{ipAddress}:{item.m_usConnectionPort}",
SteamId = steamid,
Map = Marshal.PtrToStringUTF8( (IntPtr)item.m_szMap ),
Name = Marshal.PtrToStringUTF8( (IntPtr)item.m_szServerName ),
Players = item.m_nPlayers,
MaxPlayers = item.m_nMaxPlayers - item.m_nBotPlayers,
Ping = item.m_nPing,
Game = Marshal.PtrToStringUTF8( (IntPtr)item.m_szGameDescription ),
Tick = 0
};
var tagParts = tags.Split( "," ).ToList();
foreach ( var tag in tagParts.ToArray() )
{
if ( !tag.Contains( ':' ) ) continue;
var parts = tag.Split( ':' );
if ( parts.Length != 2 ) continue;
switch ( parts[0] )
{
case "game":
entry.Game = parts[1].ToLower();
break;
case "gameversion":
if ( int.TryParse( parts[1], out var x ) )
entry.GameVersion = x;
break;
case "tick":
if ( int.TryParse( parts[1], out var i ) )
entry.Tick = i;
break;
default:
continue;
}
tagParts.Remove( tag );
}
entry.Tags = tagParts.ToArray();
Add( entry );
}
internal void OnFinished()
{
IsQuerying = false;
}
internal void OnStarted()
{
Clear();
IsQuerying = true;
}
public void Dispose()
{
serverlist.Destroy();
serverlist = default;
InteropSystem.Free( this );
GC.SuppressFinalize( this );
}
/// <summary>
/// This is a cleaned up version of gameserveritem_t.
/// </summary>
public struct Entry
{
public string IPAddressAndPort { get; set; }
public ulong SteamId { get; set; }
public string Map { get; set; }
public string Game { get; set; }
public int GameVersion { get; set; }
public string Name { get; set; }
public string[] Tags { get; set; }
public int Players { get; set; }
public int MaxPlayers { get; set; }
public int Ping { get; set; }
public int Tick { get; set; }
}
}
/// <summary>
/// Data from c++
/// We skip the steamid because there's some class padding fuckery going on.
/// We pass that in manually to make life easier.
/// </summary>
internal unsafe struct gameserveritem_t
{
#pragma warning disable 0649
public ushort m_usConnectionPort;
public ushort m_usQueryPort;
public uint m_unIP;
public int m_nPing;
public byte m_bHadSuccessfulResponse;
public byte m_bDoNotRefresh;
public fixed byte m_szGameDir[32];
public fixed byte m_szMap[32];
public fixed byte m_szGameDescription[64];
public uint m_nAppID;
public int m_nPlayers;
public int m_nMaxPlayers;
public int m_nBotPlayers;
public byte m_bPassword;
public byte m_bSecure;
public uint m_ulTimeLastPlayed;
public int m_nServerVersion;
public fixed byte m_szServerName[64];
public fixed byte m_szGameTags[128];
#pragma warning restore 0649
};