diff --git a/OpenGSQ/Protocols/GameSpy1.cs b/OpenGSQ/Protocols/GameSpy1.cs
index 200b637..717b1f2 100644
--- a/OpenGSQ/Protocols/GameSpy1.cs
+++ b/OpenGSQ/Protocols/GameSpy1.cs
@@ -21,10 +21,10 @@ public class GameSpy1 : ProtocolBase
///
/// Initializes a new instance of the GameSpy1 class.
///
- /// The IP address of the server.
+ /// The IP address of the server.
/// The port number of the server.
/// The timeout for the connection in milliseconds.
- public GameSpy1(string address, int port, int timeout = 5000) : base(address, port, timeout)
+ public GameSpy1(string host, int port, int timeout = 5000) : base(host, port, timeout)
{
}
diff --git a/OpenGSQ/Protocols/GameSpy2.cs b/OpenGSQ/Protocols/GameSpy2.cs
index 7cc86b6..635cdaf 100644
--- a/OpenGSQ/Protocols/GameSpy2.cs
+++ b/OpenGSQ/Protocols/GameSpy2.cs
@@ -20,10 +20,10 @@ public class GameSpy2 : ProtocolBase
///
/// Initializes a new instance of the GameSpy2 class.
///
- /// The IP address of the server.
+ /// The IP address of the server.
/// The port number of the server.
/// The timeout for the connection in milliseconds.
- public GameSpy2(string address, int port, int timeout = 5000) : base(address, port, timeout)
+ public GameSpy2(string host, int port, int timeout = 5000) : base(host, port, timeout)
{
}
diff --git a/OpenGSQ/Protocols/GameSpy3.cs b/OpenGSQ/Protocols/GameSpy3.cs
index db014c8..0e737c4 100644
--- a/OpenGSQ/Protocols/GameSpy3.cs
+++ b/OpenGSQ/Protocols/GameSpy3.cs
@@ -25,10 +25,10 @@ public class GameSpy3 : ProtocolBase
///
/// Initializes a new instance of the GameSpy3 class.
///
- /// The IP address of the server.
+ /// The IP address of the server.
/// The port number of the server.
/// The timeout for the connection in milliseconds.
- public GameSpy3(string address, int port, int timeout = 5000) : base(address, port, timeout)
+ public GameSpy3(string host, int port, int timeout = 5000) : base(host, port, timeout)
{
}
diff --git a/OpenGSQ/Protocols/GameSpy4.cs b/OpenGSQ/Protocols/GameSpy4.cs
index 6c3285f..446fac1 100644
--- a/OpenGSQ/Protocols/GameSpy4.cs
+++ b/OpenGSQ/Protocols/GameSpy4.cs
@@ -11,10 +11,10 @@ public class GameSpy4 : GameSpy3
///
/// Initializes a new instance of the GameSpy4 class.
///
- /// The IP address of the server.
+ /// The IP address of the server.
/// The port number of the server.
/// The timeout for the connection in milliseconds.
- public GameSpy4(string address, int port, int timeout = 5000) : base(address, port, timeout)
+ public GameSpy4(string host, int port, int timeout = 5000) : base(host, port, timeout)
{
_Challenge = true;
}
diff --git a/OpenGSQ/Protocols/Quake1.cs b/OpenGSQ/Protocols/Quake1.cs
index 791dda4..0dab745 100644
--- a/OpenGSQ/Protocols/Quake1.cs
+++ b/OpenGSQ/Protocols/Quake1.cs
@@ -41,10 +41,10 @@ public class Quake1 : ProtocolBase
///
/// Initializes a new instance of the Quake1 class.
///
- /// The IP address of the server.
+ /// The IP address of the server.
/// The port number of the server.
/// The timeout for the connection in milliseconds.
- public Quake1(string address, int port, int timeout = 5000) : base(address, port, timeout)
+ public Quake1(string host, int port, int timeout = 5000) : base(host, port, timeout)
{
_RequestHeader = "status";
_ResponseHeader = "n";
diff --git a/OpenGSQ/Protocols/Quake2.cs b/OpenGSQ/Protocols/Quake2.cs
index 6e66887..6785e2e 100644
--- a/OpenGSQ/Protocols/Quake2.cs
+++ b/OpenGSQ/Protocols/Quake2.cs
@@ -17,10 +17,10 @@ public class Quake2 : Quake1
///
/// Initializes a new instance of the Quake2 class.
///
- /// The IP address of the server.
+ /// The IP address of the server.
/// The port number of the server.
/// The timeout for the connection in milliseconds.
- public Quake2(string address, int port, int timeout = 5000) : base(address, port, timeout)
+ public Quake2(string host, int port, int timeout = 5000) : base(host, port, timeout)
{
_RequestHeader = "status";
_ResponseHeader = "print\n";
diff --git a/OpenGSQ/Protocols/Quake3.cs b/OpenGSQ/Protocols/Quake3.cs
index 45977ca..03d5713 100644
--- a/OpenGSQ/Protocols/Quake3.cs
+++ b/OpenGSQ/Protocols/Quake3.cs
@@ -20,10 +20,10 @@ public class Quake3 : Quake2
///
/// Initializes a new instance of the Quake3 class.
///
- /// The IP address of the server.
+ /// The IP address of the server.
/// The port number of the server.
/// The timeout for the connection in milliseconds.
- public Quake3(string address, int port, int timeout = 5000) : base(address, port, timeout)
+ public Quake3(string host, int port, int timeout = 5000) : base(host, port, timeout)
{
_RequestHeader = "getstatus";
_ResponseHeader = "statusResponse\n";
diff --git a/OpenGSQ/Protocols/Scum.cs b/OpenGSQ/Protocols/Scum.cs
new file mode 100644
index 0000000..8fde9b2
--- /dev/null
+++ b/OpenGSQ/Protocols/Scum.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using System.Linq;
+using System.IO;
+using System.Net.Sockets;
+using OpenGSQ.Responses.Scum;
+
+namespace OpenGSQ.Protocols
+{
+ ///
+ /// Scum Protocol
+ ///
+ public class Scum : ProtocolBase
+ {
+ ///
+ public override string FullName => "Scum Protocol";
+
+ private static readonly List<(string, int)> _masterServers = new List<(string, int)>
+ {
+ ("176.57.138.2", 1040),
+ ("172.107.16.215", 1040),
+ ("206.189.248.133", 1040)
+ };
+
+ ///
+ /// Initializes a new instance of the Scum class.
+ ///
+ /// The host of the server.
+ /// The port of the server.
+ /// The timeout for server requests.
+ public Scum(string host, int port, int timeout = 5000) : base(host, port, timeout)
+ {
+ }
+
+ ///
+ /// Gets the status of the server.
+ ///
+ /// The list of master servers to query.
+ /// The status of the server.
+ /// Thrown when the server is not found in the list of master servers.
+ public async Task GetStatus(List masterServers = null)
+ {
+ var ip = (await GetIPEndPoint()).Address.ToString();
+
+ masterServers ??= await QueryMasterServers();
+
+ foreach (var server in masterServers)
+ {
+ if (server.Ip == ip && server.Port == Port)
+ {
+ return server;
+ }
+ }
+
+ throw new ServerNotFoundException($"The server with IP address {ip} and port {Port} was not found in the list of master servers.");
+ }
+
+ ///
+ /// Queries the master servers for a list of servers.
+ ///
+ /// A list of servers from the master servers.
+ /// Thrown when failed to connect to any of the master servers.
+ public static async Task> QueryMasterServers()
+ {
+ foreach (var (host, port) in _masterServers)
+ {
+ try
+ {
+ using var tcpClient = new TcpClient();
+ tcpClient.ReceiveTimeout = 5000;
+ await tcpClient.ConnectAsync(host, port);
+ await tcpClient.SendAsync(new byte[] { 0x04, 0x03, 0x00, 0x00 });
+
+ var total = -1;
+ var response = new byte[0];
+ var servers = new List();
+
+ while (total == -1 || servers.Count < total)
+ {
+ response = response.Concat(await tcpClient.ReceiveAsync()).ToArray();
+ using var br = new BinaryReader(new MemoryStream(response));
+
+ // first packet return the total number of servers
+ if (total == -1)
+ {
+ total = br.ReadInt16();
+ }
+
+ // server bytes length always 127
+ while (br.BaseStream.Length - br.BaseStream.Position >= 127)
+ {
+ var statusResponse = new StatusResponse
+ {
+ Ip = string.Join(".", br.ReadBytes(4).Reverse().Select(b => b.ToString())),
+ Port = br.ReadInt16(),
+ Name = Encoding.UTF8.GetString(br.ReadBytes(100).TakeWhile(b => b != 0).ToArray())
+ };
+ br.ReadByte(); // skip
+ statusResponse.NumPlayers = br.ReadByte();
+ statusResponse.MaxPlayers = br.ReadByte();
+ statusResponse.Time = br.ReadByte();
+ br.ReadByte(); // skip
+ statusResponse.Password = ((br.ReadByte() >> 1) & 1) == 1;
+ br.ReadBytes(7); // skip
+ var v = br.ReadBytes(8).Reverse().Select(b => Convert.ToInt32(b).ToString("X").PadLeft(2, '0')).ToList();
+ statusResponse.Version = $"{Convert.ToInt32(v[0], 16)}.{Convert.ToInt32(v[1], 16)}.{Convert.ToInt32(v[2] + v[3], 16)}.{Convert.ToInt32(v[4] + v[5] + v[6] + v[7], 16)}";
+ servers.Add(statusResponse);
+ }
+
+ // if the length is less than 127, save the unused bytes for next loop
+ response = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
+ }
+
+ return servers;
+ }
+ catch (SocketException)
+ {
+ // Ignore exceptions
+ }
+ }
+
+ throw new Exception("Failed to connect to any of the master servers.");
+ }
+ }
+}
diff --git a/OpenGSQ/Responses/Scum/StatusResponse.cs b/OpenGSQ/Responses/Scum/StatusResponse.cs
new file mode 100644
index 0000000..076c1cd
--- /dev/null
+++ b/OpenGSQ/Responses/Scum/StatusResponse.cs
@@ -0,0 +1,48 @@
+namespace OpenGSQ.Responses.Scum
+{
+ ///
+ /// Represents the response status of a server.
+ ///
+ public class StatusResponse
+ {
+ ///
+ /// Gets or sets the IP address of the server.
+ ///
+ public string Ip { get; set; }
+
+ ///
+ /// Gets or sets the port number of the server.
+ ///
+ public int Port { get; set; }
+
+ ///
+ /// Gets or sets the name of the server.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Gets or sets the number of players currently connected to the server.
+ ///
+ public int NumPlayers { get; set; }
+
+ ///
+ /// Gets or sets the maximum number of players that can connect to the server.
+ ///
+ public int MaxPlayers { get; set; }
+
+ ///
+ /// Gets or sets the server time.
+ ///
+ public int Time { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether a password is required to connect to the server.
+ ///
+ public bool Password { get; set; }
+
+ ///
+ /// Gets or sets the version of the server.
+ ///
+ public string Version { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/OpenGSQTests/Protocols/ScumTests.cs b/OpenGSQTests/Protocols/ScumTests.cs
new file mode 100644
index 0000000..588ffaf
--- /dev/null
+++ b/OpenGSQTests/Protocols/ScumTests.cs
@@ -0,0 +1,23 @@
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using OpenGSQTests;
+
+namespace OpenGSQ.Protocols.Tests
+{
+ [TestClass()]
+ public class ScumTests : TestBase
+ {
+ public Scum scum = new("15.235.181.19", 7042);
+
+ public ScumTests() : base(nameof(ScumTests))
+ {
+ _EnableSave = false;
+ }
+
+ [TestMethod()]
+ public async Task GetStatusTest()
+ {
+ SaveResult(nameof(GetStatusTest), await scum.GetStatus());
+ }
+ }
+}
\ No newline at end of file
diff --git a/OpenGSQTests/Results/ScumTests/GetStatusTest.json b/OpenGSQTests/Results/ScumTests/GetStatusTest.json
new file mode 100644
index 0000000..e73f9e5
--- /dev/null
+++ b/OpenGSQTests/Results/ScumTests/GetStatusTest.json
@@ -0,0 +1,10 @@
+{
+ "Ip": "15.235.181.19",
+ "Port": 7042,
+ "Name": "★6868★PVP打架爽服/小队扶持/满人送V10/10倍魔改/高性能服务器",
+ "NumPlayers": 2,
+ "MaxPlayers": 100,
+ "Time": 21,
+ "Password": false,
+ "Version": "0.9.511.80646"
+}
\ No newline at end of file