mirror of
https://github.com/tribufu/ServerManagers
synced 2026-05-06 15:17:34 +00:00
source code checkin
This commit is contained in:
parent
5f8fb2c825
commit
7e57b72e35
675 changed files with 168433 additions and 0 deletions
263
src/QueryMaster/DataObjects.cs
Normal file
263
src/QueryMaster/DataObjects.cs
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about the server
|
||||
/// </summary>
|
||||
|
||||
[Serializable]
|
||||
public class ServerInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true if server replies with Obsolete response format.
|
||||
/// </summary>
|
||||
public bool IsObsolete { get; internal set; }
|
||||
/// <summary>
|
||||
/// Socket address of server.
|
||||
/// </summary>
|
||||
public string Address { get; internal set; }
|
||||
/// <summary>
|
||||
/// Protocol version used by the server.
|
||||
/// </summary>
|
||||
public byte Protocol { get; internal set; }
|
||||
/// <summary>
|
||||
/// Name of the server.
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
/// <summary>
|
||||
/// Map the server has currently loaded.
|
||||
/// </summary>
|
||||
public string Map { get; internal set; }
|
||||
/// <summary>
|
||||
/// Name of the folder containing the game files.
|
||||
/// </summary>
|
||||
public string Directory { get; internal set; }
|
||||
/// <summary>
|
||||
/// Full name of the game.
|
||||
/// </summary>
|
||||
public string Description { get; internal set; }
|
||||
/// <summary>
|
||||
/// Steam Application ID of game.
|
||||
/// </summary>
|
||||
public short Id { get; internal set; }
|
||||
/// <summary>
|
||||
/// Number of players on the server.
|
||||
/// </summary>
|
||||
public int Players { get; internal set; }
|
||||
/// <summary>
|
||||
/// Maximum number of players the server reports it can hold.
|
||||
/// </summary>
|
||||
public byte MaxPlayers { get; internal set; }
|
||||
/// <summary>
|
||||
/// Number of bots on the server.
|
||||
/// </summary>
|
||||
public byte Bots { get; internal set; }
|
||||
/// <summary>
|
||||
/// Indicates the type of server.(Dedicated/Non-dedicated/Proxy)
|
||||
/// </summary>
|
||||
public string ServerType { get; internal set; }
|
||||
/// <summary>
|
||||
/// Indicates the operating system of the server.(Linux/Windows/Mac)
|
||||
/// </summary>
|
||||
public string Environment { get; internal set; }
|
||||
/// <summary>
|
||||
/// Indicates whether the server requires a password
|
||||
/// </summary>
|
||||
public bool IsPrivate { get; internal set; }
|
||||
/// <summary>
|
||||
/// Specifies whether the server uses VAC.
|
||||
/// </summary>
|
||||
public bool IsSecure { get; internal set; }
|
||||
/// <summary>
|
||||
/// Version of the game installed on the server.
|
||||
/// </summary>
|
||||
public string GameVersion { get; internal set; }
|
||||
/// <summary>
|
||||
/// Round-trip delay time.
|
||||
/// </summary>
|
||||
public long Ping { get; internal set; }
|
||||
/// <summary>
|
||||
/// Additional information provided by server.
|
||||
/// </summary>
|
||||
public ExtraInfo Extra { get; internal set; }
|
||||
/// <summary>
|
||||
/// Valid only if the server is running The Ship.
|
||||
/// </summary>
|
||||
public TheShip ShipInfo { get; internal set; }
|
||||
/// <summary>
|
||||
/// Indicates whether the game is a mod(Halflofe/HalfLifeMod)
|
||||
/// </summary>
|
||||
/// <remarks>Present only in Obsolete server responses.</remarks>
|
||||
public bool IsModded { get; internal set; }
|
||||
/// <summary>
|
||||
/// Valid only if IsModded =true
|
||||
/// </summary>
|
||||
/// <remarks>Present only in Obsolete server responses.</remarks>
|
||||
public Mod ModInfo { get; internal set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains extra information about the Ship server
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TheShip
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the game mode.(Hunt/Elimination/Duel/Deathmatch/VIP Team/Team Elimination)
|
||||
/// </summary>
|
||||
public string Mode { get; internal set; }
|
||||
/// <summary>
|
||||
/// The number of witnesses necessary to have a player arrested.
|
||||
/// </summary>
|
||||
public byte Witnesses { get; internal set; }
|
||||
/// <summary>
|
||||
/// Time (in seconds) before a player is arrested while being witnessed.
|
||||
/// </summary>
|
||||
public byte Duration { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about the Mod.
|
||||
/// </summary>
|
||||
/// <remarks>Present only in Obsolete server responses.</remarks>
|
||||
[Serializable]
|
||||
public class Mod
|
||||
{
|
||||
/// <summary>
|
||||
/// URL to mod website.
|
||||
/// </summary>
|
||||
public string Link { get; internal set; }
|
||||
/// <summary>
|
||||
/// URL to download the mod.
|
||||
/// </summary>
|
||||
public string DownloadLink { get; internal set; }
|
||||
/// <summary>
|
||||
/// Version of mod installed on server.
|
||||
/// </summary>
|
||||
public long Version { get; internal set; }
|
||||
/// <summary>
|
||||
/// Space (in bytes) the mod takes up.
|
||||
/// </summary>
|
||||
public long Size { get; internal set; }
|
||||
/// <summary>
|
||||
/// Indicates the type of mod.
|
||||
/// </summary>
|
||||
public bool IsOnlyMultiPlayer { get; internal set; }
|
||||
/// <summary>
|
||||
/// Indicates whether mod uses its own DLL
|
||||
/// </summary>
|
||||
public bool IsHalfLifeDll { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains information of a player currently in server
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the player.
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
/// <summary>
|
||||
/// Player's score (usually "frags" or "kills".)
|
||||
/// </summary>
|
||||
public long Score { get; internal set; }
|
||||
/// <summary>
|
||||
/// Time player has been connected to the server.(returns TimeSpan instance)
|
||||
/// </summary>
|
||||
public TimeSpan Time { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains information of a server rule
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class Rule
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the rule.
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
/// <summary>
|
||||
/// Value of the rule.
|
||||
/// </summary>
|
||||
public string Value { get; internal set; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Contains information of a player
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PlayerInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of player
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
/// <summary>
|
||||
/// UId of player(Steam ID)
|
||||
/// </summary>
|
||||
public string Uid { get; internal set; }
|
||||
/// <summary>
|
||||
/// Won Id
|
||||
/// </summary>
|
||||
public string WonId { get; internal set; }
|
||||
/// <summary>
|
||||
/// Player's Team Name
|
||||
/// </summary>
|
||||
public string Team { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains extra information about server
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ExtraInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The server's game port number.
|
||||
/// </summary>
|
||||
public short Port { get; internal set; }
|
||||
/// <summary>
|
||||
/// Server's SteamID.
|
||||
/// </summary>
|
||||
public int SteamID { get; internal set; }
|
||||
/// <summary>
|
||||
/// Contains information on Source TV.(if it is Source TV)
|
||||
/// </summary>
|
||||
public SourceTVInfo SpecInfo { get; internal set; }
|
||||
/// <summary>
|
||||
/// Tags that describe the game according to the server.
|
||||
/// </summary>
|
||||
public string Keywords { get; internal set; }
|
||||
/// <summary>
|
||||
/// The server's 64-bit GameID.
|
||||
/// </summary>
|
||||
public int GameId { get; internal set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Contains information on SourceTV
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SourceTVInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Spectator port number for SourceTV.
|
||||
/// </summary>
|
||||
public short Port { get; internal set; }
|
||||
/// <summary>
|
||||
/// Name of the spectator server for SourceTV.
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
293
src/QueryMaster/Enums.cs
Normal file
293
src/QueryMaster/Enums.cs
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the type of engine used by server
|
||||
/// </summary>
|
||||
public enum EngineType
|
||||
{
|
||||
/// <summary>
|
||||
/// Source Engine
|
||||
/// </summary>
|
||||
Source,
|
||||
/// <summary>
|
||||
/// Gold Source Engine
|
||||
/// </summary>
|
||||
GoldSource
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the game
|
||||
/// </summary>
|
||||
public enum Game
|
||||
{
|
||||
//Gold Source Games
|
||||
/// <summary>
|
||||
/// Counter-Strike
|
||||
/// </summary>
|
||||
CounterStrike = 10,
|
||||
/// <summary>
|
||||
/// Team Fortress Classic
|
||||
/// </summary>
|
||||
Team_Fortress_Classic = 20,
|
||||
/// <summary>
|
||||
/// Day Of Defeat
|
||||
/// </summary>
|
||||
Day_Of_Defeat = 30,
|
||||
/// <summary>
|
||||
/// Deathmatch Classic
|
||||
/// </summary>
|
||||
Deathmatch_Classic = 40,
|
||||
/// <summary>
|
||||
/// Opposing Force
|
||||
/// </summary>
|
||||
Opposing_Force = 50,
|
||||
/// <summary>
|
||||
/// Ricochet
|
||||
/// </summary>
|
||||
Ricochet = 60,
|
||||
/// <summary>
|
||||
/// Half-Life
|
||||
/// </summary>
|
||||
Half_Life = 70,
|
||||
/// <summary>
|
||||
/// Condition Zero
|
||||
/// </summary>
|
||||
Condition_Zero = 80,
|
||||
/// <summary>
|
||||
/// CounterStrike 1.6 dedicated server
|
||||
/// </summary>
|
||||
CounterStrike_1_6_dedicated_server = 90,
|
||||
/// <summary>
|
||||
/// Condition Zero Deleted Scenes
|
||||
/// </summary>
|
||||
Condition_Zero_Deleted_Scenes = 100,
|
||||
/// <summary>
|
||||
/// Half-Life:Blue Shift
|
||||
/// </summary>
|
||||
Half_Life_Blue_Shift = 130,
|
||||
//Source Games
|
||||
/// <summary>
|
||||
/// Half-Life 2
|
||||
/// </summary>
|
||||
Half_Life_2 = 220,
|
||||
/// <summary>
|
||||
/// Counter-Strike: Source
|
||||
/// </summary>
|
||||
CounterStrike_Source = 240,
|
||||
/// <summary>
|
||||
/// Half-Life: Source
|
||||
/// </summary>
|
||||
Half_Life_Source = 280,
|
||||
/// <summary>
|
||||
/// Day of Defeat: Source
|
||||
/// </summary>
|
||||
Day_Of_Defeat_Source = 300,
|
||||
/// <summary>
|
||||
/// Half-Life 2: Deathmatch
|
||||
/// </summary>
|
||||
Half_Life_2_Deathmatch = 320,
|
||||
/// <summary>
|
||||
/// Half-Life 2: Lost Coast
|
||||
/// </summary>
|
||||
Half_Life_2_Lost_Coast = 340,
|
||||
/// <summary>
|
||||
/// Half-Life Deathmatch: Source
|
||||
/// </summary>
|
||||
Half_Life_Deathmatch_Source = 360,
|
||||
/// <summary>
|
||||
/// Half-Life 2: Episode One
|
||||
/// </summary>
|
||||
Half_Life_2_Episode_One = 380,
|
||||
/// <summary>
|
||||
/// Portal
|
||||
/// </summary>
|
||||
Portal = 400,
|
||||
/// <summary>
|
||||
/// Half-Life 2: Episode Two
|
||||
/// </summary>
|
||||
Half_Life_2_Episode_Two = 420,
|
||||
/// <summary>
|
||||
/// Team Fortress 2
|
||||
/// </summary>
|
||||
Team_Fortress_2 = 440,
|
||||
/// <summary>
|
||||
/// Left 4 Dead
|
||||
/// </summary>
|
||||
Left_4_Dead = 500,
|
||||
/// <summary>
|
||||
/// Left 4 Dead 2
|
||||
/// </summary>
|
||||
Left_4_Dead_2 = 550,
|
||||
/// <summary>
|
||||
/// Dota 2
|
||||
/// </summary>
|
||||
Dota_2 = 570,
|
||||
/// <summary>
|
||||
/// Portal 2
|
||||
/// </summary>
|
||||
Portal_2 = 620,
|
||||
/// <summary>
|
||||
/// Alien Swarm
|
||||
/// </summary>
|
||||
Alien_Swarm = 630,
|
||||
/// <summary>
|
||||
/// Counter-Strike: Global Offensive
|
||||
/// </summary>
|
||||
CounterStrike_Global_Offensive = 1800,
|
||||
/// <summary>
|
||||
/// SiN Episodes: Emergence
|
||||
/// </summary>
|
||||
SiN_Episodes_Emergence = 1300,
|
||||
/// <summary>
|
||||
/// Dark Messiah of Might and Magic
|
||||
/// </summary>
|
||||
Dark_Messiah_Of_Might_And_Magic = 2100,
|
||||
/// <summary>
|
||||
/// Dark Messiah Might and Magic Multi-Player
|
||||
/// </summary>
|
||||
Dark_Messiah_Might_And_Magic_MultiPlayer = 2130,
|
||||
/// <summary>
|
||||
/// The Ship
|
||||
/// </summary>
|
||||
The_Ship = 2400,
|
||||
/// <summary>
|
||||
/// Bloody Good Time
|
||||
/// </summary>
|
||||
Bloody_Good_Time = 2450,
|
||||
/// <summary>
|
||||
/// Vampire The Masquerade - Bloodlines
|
||||
/// </summary>
|
||||
Vampire_The_Masquerade_Bloodlines = 2600,
|
||||
/// <summary>
|
||||
/// Garry's Mod
|
||||
/// </summary>
|
||||
Garrys_Mod = 4000,
|
||||
/// <summary>
|
||||
/// Zombie Panic! Source
|
||||
/// </summary>
|
||||
Zombie_Panic_Source = 17500,
|
||||
/// <summary>
|
||||
/// Age of Chivalry
|
||||
/// </summary>
|
||||
Age_of_Chivalry = 17510,
|
||||
/// <summary>
|
||||
/// Synergy
|
||||
/// </summary>
|
||||
Synergy = 17520,
|
||||
/// <summary>
|
||||
/// D.I.P.R.I.P.
|
||||
/// </summary>
|
||||
D_I_P_R_I_P = 17530,
|
||||
/// <summary>
|
||||
/// Eternal Silence
|
||||
/// </summary>
|
||||
Eternal_Silence = 17550,
|
||||
/// <summary>
|
||||
/// Pirates, Vikings, and Knights II
|
||||
/// </summary>
|
||||
Pirates_Vikings_And_Knights_II = 17570,
|
||||
/// <summary>
|
||||
/// Dystopia
|
||||
/// </summary>
|
||||
Dystopia = 17580,
|
||||
/// <summary>
|
||||
/// Insurgency
|
||||
/// </summary>
|
||||
Insurgency = 17700,
|
||||
/// <summary>
|
||||
/// Nuclear Dawn
|
||||
/// </summary>
|
||||
Nuclear_Dawn = 17710,
|
||||
/// <summary>
|
||||
/// Smashball
|
||||
/// </summary>
|
||||
Smashball = 17730,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the Region
|
||||
/// </summary>
|
||||
public enum Region : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// US East coast
|
||||
/// </summary>
|
||||
US_East_coast,
|
||||
/// <summary>
|
||||
/// US West coast
|
||||
/// </summary>
|
||||
US_West_coast,
|
||||
/// <summary>
|
||||
/// South America
|
||||
/// </summary>
|
||||
South_America,
|
||||
/// <summary>
|
||||
/// Europe
|
||||
/// </summary>
|
||||
Europe,
|
||||
/// <summary>
|
||||
/// Asia
|
||||
/// </summary>
|
||||
Asia,
|
||||
/// <summary>
|
||||
/// Australia
|
||||
/// </summary>
|
||||
Australia,
|
||||
/// <summary>
|
||||
/// Middle East
|
||||
/// </summary>
|
||||
Middle_East,
|
||||
/// <summary>
|
||||
/// Africa
|
||||
/// </summary>
|
||||
Africa,
|
||||
/// <summary>
|
||||
/// Rest of the world
|
||||
/// </summary>
|
||||
Rest_of_the_world = 0xFF
|
||||
}
|
||||
|
||||
enum SocketType
|
||||
{
|
||||
Udp,
|
||||
Tcp
|
||||
}
|
||||
|
||||
enum ResponseMsgHeader : byte
|
||||
{
|
||||
A2S_INFO = 0x49,
|
||||
A2S_INFO_Obsolete = 0x6D,
|
||||
A2S_PLAYER = 0x44,
|
||||
A2S_RULES = 0x45,
|
||||
A2S_SERVERQUERY_GETCHALLENGE = 0x41,
|
||||
}
|
||||
|
||||
//Used in Source Rcon
|
||||
enum PacketId
|
||||
{
|
||||
Empty = 10,
|
||||
ExecCmd = 11
|
||||
}
|
||||
|
||||
enum PacketType
|
||||
{
|
||||
Auth = 3,
|
||||
AuthResponse = 2,
|
||||
Exec = 2,
|
||||
ExecResponse = 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
16
src/QueryMaster/EventsUtil.cs
Normal file
16
src/QueryMaster/EventsUtil.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
internal static class EventsUtil
|
||||
{
|
||||
internal static void Fire<T>(this EventHandler<T> handler, object sender, T eventArgs) where T : EventArgs
|
||||
{
|
||||
if (handler != null)
|
||||
handler(sender, eventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/QueryMaster/Exceptions.cs
Normal file
55
src/QueryMaster/Exceptions.cs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
/// <summary>
|
||||
/// The exception that is thrown by the QueryMaster library
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class QueryMasterException : Exception
|
||||
{
|
||||
public QueryMasterException() : base() { }
|
||||
public QueryMasterException(string message) : base(message) { }
|
||||
public QueryMasterException(string message, Exception innerException) : base(message, innerException) { }
|
||||
protected QueryMasterException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// The exception that is thrown when an invalid message header is received
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class InvalidHeaderException : QueryMasterException
|
||||
{
|
||||
public InvalidHeaderException() : base() { }
|
||||
public InvalidHeaderException(string message) : base(message) { }
|
||||
public InvalidHeaderException(string message, Exception innerException) : base(message, innerException) { }
|
||||
protected InvalidHeaderException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The exception that is thrown when an invalid packet is received
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class InvalidPacketException : QueryMasterException
|
||||
{
|
||||
public InvalidPacketException() : base() { }
|
||||
public InvalidPacketException(string message) : base(message) { }
|
||||
public InvalidPacketException(string message, Exception innerException) : base(message, innerException) { }
|
||||
protected InvalidPacketException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
/// <summary>
|
||||
/// The exception that is thrown when there is an error while parsing received packets
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ParseException : QueryMasterException
|
||||
{
|
||||
public ParseException() : base() { }
|
||||
public ParseException(string message) : base(message) { }
|
||||
public ParseException(string message, Exception innerException) : base(message, innerException) { }
|
||||
protected ParseException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
}
|
||||
18
src/QueryMaster/GoldSource.cs
Normal file
18
src/QueryMaster/GoldSource.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
class GoldSource : Server
|
||||
{
|
||||
internal GoldSource(IPEndPoint address, bool? isObsolete, int sendTimeOut, int receiveTimeOut) : base(address, EngineType.GoldSource, isObsolete, sendTimeOut, receiveTimeOut) { }
|
||||
public override Rcon GetControl(string pass)
|
||||
{
|
||||
RConObj = RconGoldSource.Authorize(socket.Address, pass);
|
||||
return RConObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
76
src/QueryMaster/IPFilter.cs
Normal file
76
src/QueryMaster/IPFilter.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows you to restrict the results to servers running a certain game.
|
||||
/// </summary>
|
||||
public class IpFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Servers running dedicated
|
||||
/// </summary>
|
||||
public bool IsDedicated { get; set; }
|
||||
/// <summary>
|
||||
/// Servers using anti-cheat technology.(eg:-VAC)
|
||||
/// </summary>
|
||||
public bool IsSecure { get; set; }
|
||||
/// <summary>
|
||||
/// Servers running the specified modification(ex. cstrike)
|
||||
/// </summary>
|
||||
public string GameDirectory { get; set; }
|
||||
/// <summary>
|
||||
/// Servers running the specified map
|
||||
/// </summary>
|
||||
public string Map { get; set; }
|
||||
/// <summary>
|
||||
/// Servers running on a Linux platform
|
||||
/// </summary>
|
||||
public bool IsLinux { get; set; }
|
||||
/// <summary>
|
||||
/// Servers that are not empty
|
||||
/// </summary>
|
||||
public bool IsNotEmpty { get; set; }
|
||||
/// <summary>
|
||||
/// Servers that are not full
|
||||
/// </summary>
|
||||
public bool IsNotFull { get; set; }
|
||||
/// <summary>
|
||||
/// Servers that are spectator proxies
|
||||
/// </summary>
|
||||
public bool IsProxy { get; set; }
|
||||
/// <summary>
|
||||
/// Servers running the specified app
|
||||
/// </summary>
|
||||
public int App { get; set; }
|
||||
/// <summary>
|
||||
/// Servers that are NOT running a game(AppId)(This was introduced to block Left 4 Dead games from the Steam Server Browser)
|
||||
/// </summary>
|
||||
public int NApp { get; set; }
|
||||
/// <summary>
|
||||
/// Servers that are empty
|
||||
/// </summary>
|
||||
public bool IsNoPlayers { get; set; }
|
||||
/// <summary>
|
||||
/// Servers that are whitelisted
|
||||
/// </summary>
|
||||
public bool IsWhiteListed { get; set; }
|
||||
/// <summary>
|
||||
/// Servers with all of the given tag(s) in sv_tags
|
||||
/// </summary>
|
||||
public string Sv_Tags { get; set; }
|
||||
/// <summary>
|
||||
/// Servers with all of the given tag(s) in their 'hidden' tags (L4D2)
|
||||
/// </summary>
|
||||
public string GameData { get; set; }
|
||||
/// <summary>
|
||||
/// Servers with any of the given tag(s) in their 'hidden' tags (L4D2)
|
||||
/// </summary>
|
||||
public string GameDataOr { get; set; }
|
||||
|
||||
public string[] IpAddr { get; set; }
|
||||
}
|
||||
}
|
||||
440
src/QueryMaster/LogEventArgs.cs
Normal file
440
src/QueryMaster/LogEventArgs.cs
Normal file
|
|
@ -0,0 +1,440 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
/// <summary>
|
||||
/// Serves as base class for all EventArgs.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class LogEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Timestamp.
|
||||
/// </summary>
|
||||
public DateTime Timestamp { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for Exception event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ExceptionEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Log line.
|
||||
/// </summary>
|
||||
public string LogLine { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for Server cvar event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class CvarEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Cvar name.
|
||||
/// </summary>
|
||||
public string Cvar { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets Cvar Value.
|
||||
/// </summary>
|
||||
public string Value { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data log start event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class LogStartEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Filename.
|
||||
/// </summary>
|
||||
public string FileName { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets Game name.
|
||||
/// </summary>
|
||||
public string Game { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets Protocol version.
|
||||
/// </summary>
|
||||
public string Protocol { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets Release version.
|
||||
/// </summary>
|
||||
public string Release { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets Build version.
|
||||
/// </summary>
|
||||
public string Build { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for map loaded event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class MapLoadEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Map name.
|
||||
/// </summary>
|
||||
public string MapName { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for map started event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class MapStartEventArgs : MapLoadEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Get map CRC value.
|
||||
/// </summary>
|
||||
public string MapCRC { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for rcon event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class RconEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Challenge Id of remote client.
|
||||
/// </summary>
|
||||
public string Challenge { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets Password.
|
||||
/// </summary>
|
||||
public string Password { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets command sent by remote client.
|
||||
/// </summary>
|
||||
public string Command { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets IP-Address of client.
|
||||
/// </summary>
|
||||
public string Ip { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets Port number of client
|
||||
/// </summary>
|
||||
public ushort Port { get; internal set; }
|
||||
/// <summary>
|
||||
/// Returns true if password sent is valid.
|
||||
/// </summary>
|
||||
public bool IsValid { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for servername event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ServerNameEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets name of server.
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for server say event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ServerSayEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the message said by server.
|
||||
/// </summary>
|
||||
public string Message { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for Playervalidate,playerenteredgame and player disconnected event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PlayerEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Player information.
|
||||
/// </summary>
|
||||
public PlayerInfo Player { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for player connect event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ConnectEventArgs : PlayerEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets IP-Address of client.
|
||||
/// </summary>
|
||||
public string Ip { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets Port number of client.
|
||||
/// </summary>
|
||||
public ushort Port { get; internal set; }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for playerkicked event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class KickEventArgs : PlayerEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the admin who kicked the player.
|
||||
/// </summary>
|
||||
public string Kicker { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets the message sent as a reason for the kick.
|
||||
/// </summary>
|
||||
public string Message { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for suicide event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SuicideEventArgs : PlayerEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the weapon name.
|
||||
/// </summary>
|
||||
public string Weapon { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for team selection event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TeamSelectionEventArgs : PlayerEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the team name.
|
||||
/// </summary>
|
||||
public string Team { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for role selection event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class RoleSelectionEventArgs : PlayerEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the role name.
|
||||
/// </summary>
|
||||
public string Role { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for player name change event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NameChangeEventArgs : PlayerEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets player's new name.
|
||||
/// </summary>
|
||||
public string NewName { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for player killed event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class KillEventArgs : PlayerEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Victim player's info.
|
||||
/// </summary>
|
||||
public PlayerInfo Victim { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets the name of the weapon used.
|
||||
/// </summary>
|
||||
public string Weapon { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for player injured event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class InjureEventArgs : KillEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets damage.
|
||||
/// </summary>
|
||||
public string Damage { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for PlayerOnPLayerTriggered event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PlayerOnPlayerEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets info about the player who triggered an action.
|
||||
/// </summary>
|
||||
public PlayerInfo Source { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets info about the player on whom the ation was triggered.
|
||||
/// </summary>
|
||||
public PlayerInfo Target { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets the name of the action performed.
|
||||
/// </summary>
|
||||
public string Action { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for Player action event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PlayerActionEventArgs : PlayerEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the action performed.
|
||||
/// </summary>
|
||||
public string Action { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets additional data present in the message.
|
||||
/// </summary>
|
||||
public string ExtraInfo { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for team action event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TeamActionEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the team who triggered an action.
|
||||
/// </summary>
|
||||
public string Team { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets the name of the action performed.
|
||||
/// </summary>
|
||||
public string Action { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for WorldAction event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class WorldActionEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the action performed.
|
||||
/// </summary>
|
||||
public string Action { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for Say and TeamSay events.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ChatEventArgs : PlayerEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the message said by player.
|
||||
/// </summary>
|
||||
public string Message { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for TeamAlliance event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TeamAllianceEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of 1st team.
|
||||
/// </summary>
|
||||
public string Team1 { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets the name of 2nd team.
|
||||
/// </summary>
|
||||
public string Team2 { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for TeamScoreReport event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class TeamScoreReportEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of team.
|
||||
/// </summary>
|
||||
public string Team { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets the score of team.
|
||||
/// </summary>
|
||||
public string Score { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets the player count.
|
||||
/// </summary>
|
||||
public string PlayerCount { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets the additional data present in the message.
|
||||
/// </summary>
|
||||
public string ExtraInfo { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for PrivateChat event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PrivateChatEventArgs : LogEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Sender Player's info.
|
||||
/// </summary>
|
||||
public PlayerInfo Sender { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets Receiver Player's info.
|
||||
/// </summary>
|
||||
public PlayerInfo Receiver { get; internal set; }
|
||||
/// <summary>
|
||||
/// Get the message sent by sender.
|
||||
/// </summary>
|
||||
public string Message { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for PlayerScoreReport event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PlayerScoreReportEventArgs : PlayerEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets player score.
|
||||
/// </summary>
|
||||
public string Score { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets the additional data present in the message.
|
||||
/// </summary>
|
||||
public string ExtraInfo { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides data for WeaponSelect and WeaponAcquired event.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class WeaponEventArgs : PlayerEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets name of weapon.
|
||||
/// </summary>
|
||||
public string Weapon { get; internal set; }
|
||||
}
|
||||
}
|
||||
796
src/QueryMaster/Logs.cs
Normal file
796
src/QueryMaster/Logs.cs
Normal file
|
|
@ -0,0 +1,796 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Net;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates a method that has a parameter of type string which is the log message received from server.
|
||||
/// Invoked when a log message is received from server.
|
||||
/// </summary>
|
||||
/// <param name="log">Received log message.</param>
|
||||
public delegate void LogCallback(string log);
|
||||
/// <summary>
|
||||
/// Provides methods to listen to logs and to set up events on desired type of log message.
|
||||
/// </summary>
|
||||
public class Logs : IDisposable
|
||||
{
|
||||
Socket UdpSocket;
|
||||
private readonly int BufferSize = 1400;
|
||||
private LogCallback Callback;
|
||||
private byte[] recvData;
|
||||
private int Port;
|
||||
private int HeaderSize = 0;
|
||||
private IPEndPoint ServerEndPoint;
|
||||
private Regex LineSplit;
|
||||
private Regex RegPlayer = new Regex("^([^\"]+)<([^\"]+)><([^\"]+)><([^\"]*)>$");
|
||||
private char[] QuoteSplitPattern = { '\"' };
|
||||
/// <summary>
|
||||
/// Occurs when Server cvar starts(In TFC, if tfc_clanbattle is 1, this doesn't happen.).
|
||||
/// </summary>
|
||||
public event EventHandler<LogEventArgs> CvarStartMsg; //001.1
|
||||
/// <summary>
|
||||
/// Occurs when someone changes a cvar over rcon.
|
||||
/// </summary>
|
||||
public event EventHandler<CvarEventArgs> ServerCvar; //001.2
|
||||
/// <summary>
|
||||
/// Occurs when Server cvar ends(In TFC, if tfc_clanbattle is 0, this doesn't happen.).
|
||||
/// </summary>
|
||||
public event EventHandler<LogEventArgs> CvarEndMsg; //001.3
|
||||
/// <summary>
|
||||
/// Occurs when Logging to file is started.
|
||||
/// </summary>
|
||||
public event EventHandler<LogStartEventArgs> LogFileStarted; //002.1
|
||||
/// <summary>
|
||||
/// Occurs when Log file is closed.
|
||||
/// </summary>
|
||||
public event EventHandler<LogEventArgs> LogFileClosed; //002.2
|
||||
/// <summary>
|
||||
/// Occurs when map is loaded.
|
||||
/// </summary>
|
||||
public event EventHandler<MapLoadEventArgs> MapLoaded; //003.1
|
||||
/// <summary>
|
||||
/// Occurs when Map starts.
|
||||
/// </summary>
|
||||
public event EventHandler<MapStartEventArgs> MapStarted; //003.2
|
||||
/// <summary>
|
||||
/// Occurs when an rcon message is sent to server.
|
||||
/// </summary>
|
||||
public event EventHandler<RconEventArgs> RconMsg; //004(1,2)
|
||||
/// <summary>
|
||||
/// Occurs when server name is displayed.
|
||||
/// </summary>
|
||||
public event EventHandler<ServerNameEventArgs> ServerName; //005
|
||||
/// <summary>
|
||||
/// Occurs when Server says.
|
||||
/// </summary>
|
||||
public event EventHandler<ServerSayEventArgs> ServerSay; //006
|
||||
/// <summary>
|
||||
/// Occurs when a player is connected.
|
||||
/// </summary>
|
||||
public event EventHandler<ConnectEventArgs> PlayerConnected; //050
|
||||
/// <summary>
|
||||
/// Occurs when a player is validated.
|
||||
/// </summary>
|
||||
public event EventHandler<PlayerEventArgs> PlayerValidated; //050b
|
||||
/// <summary>
|
||||
/// Occurs when a player is enters game.
|
||||
/// </summary>
|
||||
public event EventHandler<PlayerEventArgs> PlayerEnteredGame; //51
|
||||
/// <summary>
|
||||
/// Occurs when a player is disconnected.
|
||||
/// </summary>
|
||||
public event EventHandler<PlayerEventArgs> PlayerDisConnected; //52
|
||||
/// <summary>
|
||||
/// Occurs when a player is kicked.
|
||||
/// </summary>
|
||||
public event EventHandler<KickEventArgs> PlayerKicked; //052b
|
||||
/// <summary>
|
||||
/// Occurs when a player commit suicide.
|
||||
/// </summary>
|
||||
public event EventHandler<SuicideEventArgs> PlayerSuicided; //053
|
||||
/// <summary>
|
||||
/// Occurs when a player Join team.
|
||||
/// </summary>
|
||||
public event EventHandler<TeamSelectionEventArgs> PlayerJoinedTeam; //054
|
||||
/// <summary>
|
||||
/// Occurs when a player change role.
|
||||
/// </summary>
|
||||
public event EventHandler<RoleSelectionEventArgs> PlayerChangedRole; //055
|
||||
/// <summary>
|
||||
/// Occurs when a player changes name.
|
||||
/// </summary>
|
||||
public event EventHandler<NameChangeEventArgs> PlayerChangedName; //056
|
||||
/// <summary>
|
||||
/// Occurs when a player is killed.
|
||||
/// </summary>
|
||||
public event EventHandler<KillEventArgs> PlayerKilled; //057
|
||||
/// <summary>
|
||||
/// Occurs when a player is injured.
|
||||
/// </summary>
|
||||
public event EventHandler<InjureEventArgs> PlayerInjured; //058
|
||||
/// <summary>
|
||||
/// Occurs when a player triggers something on another player(in TFC this event may cover medic healings and infections, sentry gun destruction, spy uncovering.etc).
|
||||
/// </summary>
|
||||
public event EventHandler<PlayerOnPlayerEventArgs> PlayerOnPLayerTriggered; //059
|
||||
/// <summary>
|
||||
/// Occurs when a player triggers an action.
|
||||
/// </summary>
|
||||
public event EventHandler<PlayerActionEventArgs> PlayerTriggered; //060
|
||||
/// <summary>
|
||||
/// Occurs when a team triggers an action(eg:team winning).
|
||||
/// </summary>
|
||||
public event EventHandler<TeamActionEventArgs> TeamTriggered; //061
|
||||
/// <summary>
|
||||
/// Occurs when server triggers an action(eg:roundstart,game events).
|
||||
/// </summary>
|
||||
public event EventHandler<WorldActionEventArgs> WorldTriggered; //062
|
||||
/// <summary>
|
||||
/// Occurs when a player says.
|
||||
/// </summary>
|
||||
public event EventHandler<ChatEventArgs> Say; //063.1
|
||||
/// <summary>
|
||||
/// Occurs when a player uses teamsay.
|
||||
/// </summary>
|
||||
public event EventHandler<ChatEventArgs> TeamSay; //063.2
|
||||
/// <summary>
|
||||
/// Occurs when a team forms alliance with another team.
|
||||
/// </summary>
|
||||
public event EventHandler<TeamAllianceEventArgs> TeamAlliance; //064
|
||||
/// <summary>
|
||||
/// Occurs when Team Score Report is displayed at round end.
|
||||
/// </summary>
|
||||
public event EventHandler<TeamScoreReportEventArgs> TeamScoreReport; //065
|
||||
/// <summary>
|
||||
/// Occurs when a private message is sent.
|
||||
/// </summary>
|
||||
public event EventHandler<PrivateChatEventArgs> PrivateChat; //066
|
||||
/// <summary>
|
||||
/// Occurs when Player Score Report is displayed at round end.
|
||||
/// </summary>
|
||||
public event EventHandler<PlayerScoreReportEventArgs> PlayerScoreReport; //067
|
||||
/// <summary>
|
||||
/// Occurs when Player selects a weapon.
|
||||
/// </summary>
|
||||
public event EventHandler<WeaponEventArgs> PlayerSelectedWeapon; //068
|
||||
/// <summary>
|
||||
/// Occurs when Player acquires a weapon.
|
||||
/// </summary>
|
||||
public event EventHandler<WeaponEventArgs> PlayerAcquiredWeapon; //069
|
||||
/// <summary>
|
||||
/// Occurs when server shuts down.
|
||||
/// </summary>
|
||||
public event EventHandler<LogEventArgs> ShutDown; //new
|
||||
/// <summary>
|
||||
/// Occurs when a log message cannot be parsed.
|
||||
/// </summary>
|
||||
public event EventHandler<ExceptionEventArgs> Exception;
|
||||
|
||||
internal Logs(EngineType type, int port, IPEndPoint serverEndPoint)
|
||||
{
|
||||
ServerEndPoint = serverEndPoint;
|
||||
Port = port;
|
||||
recvData = new byte[BufferSize];
|
||||
LineSplit = new Regex(": ");
|
||||
switch (type)
|
||||
{
|
||||
case EngineType.GoldSource: HeaderSize = 10; break;
|
||||
case EngineType.Source: HeaderSize = 7; break;
|
||||
}
|
||||
UdpSocket = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, ProtocolType.Udp);
|
||||
UdpSocket.Bind(new IPEndPoint(IPAddress.Any, Port));
|
||||
|
||||
UdpSocket.BeginReceive(recvData, 0, recvData.Length, SocketFlags.None, Recv, null);
|
||||
}
|
||||
/// <summary>
|
||||
/// Listen to logs sent by the server
|
||||
/// </summary>
|
||||
/// <param name="callback">Called when a log message is received</param>
|
||||
public void Listen(LogCallback callback)
|
||||
{
|
||||
Callback = callback;
|
||||
}
|
||||
|
||||
private void Recv(IAsyncResult res)
|
||||
{
|
||||
int bytesRecv = 0;
|
||||
try
|
||||
{
|
||||
bytesRecv = UdpSocket.EndReceive(res);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(bytesRecv>HeaderSize)
|
||||
ThreadPool.QueueUserWorkItem(ProcessLog, Encoding.UTF8.GetString(recvData, HeaderSize, bytesRecv - HeaderSize));
|
||||
UdpSocket.BeginReceive(recvData, 0, recvData.Length, SocketFlags.None, Recv, null);
|
||||
}
|
||||
|
||||
private void ProcessLog(object line)
|
||||
{
|
||||
string logLine = (string)line;
|
||||
DateTime Timestamp;
|
||||
string info;
|
||||
try
|
||||
{
|
||||
string[] data = LineSplit.Split(logLine, 2);
|
||||
Timestamp = DateTime.ParseExact(data[0], "MM/dd/yyyy - HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
info = data[1].Remove(data[1].Length - 2);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", Util.StringToBytes(logLine));
|
||||
throw;
|
||||
}
|
||||
if (info.StartsWith("//"))
|
||||
return;
|
||||
if (Callback != null)
|
||||
Callback(logLine);
|
||||
string[] result = info.Split(QuoteSplitPattern, StringSplitOptions.RemoveEmptyEntries);
|
||||
try
|
||||
{
|
||||
if (info[0] == '\"')
|
||||
{
|
||||
switch (result[1])
|
||||
{
|
||||
case " connected, address ": OnConnection(Timestamp, result); break; // 50
|
||||
case " STEAM USERID validated": OnValidation(Timestamp, result); break; //50b
|
||||
case " entered the game": OnEnterGame(Timestamp, result); break; //51
|
||||
case " disconnected": OnDisconnection(Timestamp, result); break; //52
|
||||
case " committed suicide with ": OnSuicide(Timestamp, result); break; //53
|
||||
case " joined team ": OnTeamSelection(Timestamp, result); break; //54
|
||||
case " changed role to ": OnRoleSelection(Timestamp, result); break; //55
|
||||
case " changed name to ": OnNameChange(Timestamp, result); break; //56
|
||||
case " killed ": OnKill(Timestamp, result); break; //57
|
||||
case " attacked ": OnInjure(Timestamp, result); break; //58
|
||||
case " triggered ": //59 ,60
|
||||
{
|
||||
if (result.Length > 3 && result[3] == " against ")
|
||||
OnPlayer_PlayerAction(Timestamp, result);
|
||||
else
|
||||
OnPlayerAction(Timestamp, result);
|
||||
break;
|
||||
}
|
||||
case " say ": OnSay(Timestamp, result); break; //63a
|
||||
case " say_team ": OnTeamSay(Timestamp, result); break; //63b
|
||||
case " tell ": OnPrivateChat(Timestamp, result); break; //66
|
||||
case " selected weapon ": OnWeaponSelection(Timestamp, result); break; //68
|
||||
case " acquired weapon ": OnWeaponPickup(Timestamp, result); break; //69
|
||||
default: OnException(Timestamp, info); break;
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (result[0])
|
||||
{
|
||||
case "Server cvars start": OnCvarStart(Timestamp); break; //001.1
|
||||
case "Server cvar ": OnServerCvar(Timestamp, result); break; //001.2
|
||||
case "Server cvars end": OnCvarEnd(Timestamp); break; //001.3
|
||||
case "Log file started (file ": OnLogFileStart(Timestamp, result); break; //002.1
|
||||
case "Log file closed": OnLogFileClose(Timestamp); break; //002.2
|
||||
case "Loading map ": OnMapLoading(Timestamp, result); break; //003.1
|
||||
case "Started map ": OnMapStart(Timestamp, result); break; //003.2
|
||||
case "Rcon: ": OnRconMsg(Timestamp, result); break; //004.1
|
||||
case "Bad Rcon: ": OnRconMsg(Timestamp, result); break; //004.2
|
||||
case "Server name is ": OnserverName(Timestamp, result); break; //005
|
||||
case "Server say ": OnServerSay(Timestamp, result); break; //006
|
||||
case "Kick: ": OnKick(Timestamp, result); break; //0052b
|
||||
case "Team ":
|
||||
{
|
||||
switch (result[2])
|
||||
{
|
||||
case " triggered ": OnTeamAction(Timestamp, result); break; //061
|
||||
case " formed alliance with team ": OnTeamAlliance(Timestamp, result); break; //064
|
||||
case " scored ": OnTeamScoreReport(Timestamp, result); break; //065
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
case "World triggered ": OnWorldAction(Timestamp, result); break; //062
|
||||
case "Player ": OnPlayerAction(Timestamp, result); break; //60
|
||||
case "Server shutdown": OnShutDown(Timestamp); break; //new
|
||||
default: OnException(Timestamp, info); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Exception.Fire(ServerEndPoint, new ExceptionEventArgs() { Timestamp = Timestamp, LogLine = info });
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Disposes the resources used by log instance
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (UdpSocket != null)
|
||||
UdpSocket.Close();
|
||||
CvarStartMsg = null;
|
||||
ServerCvar = null;
|
||||
CvarEndMsg = null;
|
||||
LogFileStarted = null;
|
||||
LogFileClosed = null;
|
||||
MapLoaded = null;
|
||||
MapStarted = null;
|
||||
RconMsg = null;
|
||||
ServerName = null;
|
||||
ServerSay = null;
|
||||
PlayerConnected = null;
|
||||
PlayerValidated = null;
|
||||
PlayerEnteredGame = null;
|
||||
PlayerDisConnected = null;
|
||||
PlayerKicked = null;
|
||||
PlayerSuicided = null;
|
||||
PlayerJoinedTeam = null;
|
||||
PlayerChangedRole = null;
|
||||
PlayerChangedName = null;
|
||||
PlayerKilled = null;
|
||||
PlayerInjured = null;
|
||||
PlayerOnPLayerTriggered = null;
|
||||
PlayerTriggered = null;
|
||||
TeamTriggered = null;
|
||||
WorldTriggered = null;
|
||||
Say = null;
|
||||
TeamSay = null;
|
||||
TeamAlliance = null;
|
||||
TeamScoreReport = null;
|
||||
PrivateChat = null;
|
||||
PlayerScoreReport = null;
|
||||
PlayerSelectedWeapon = null;
|
||||
PlayerAcquiredWeapon = null;
|
||||
ShutDown = null;
|
||||
Exception = null;
|
||||
}
|
||||
|
||||
|
||||
//Server cvars start [001.1]
|
||||
protected virtual void OnCvarStart(DateTime Timestamp)
|
||||
{
|
||||
CvarStartMsg.Fire(ServerEndPoint, new LogEventArgs() { Timestamp = Timestamp });
|
||||
}
|
||||
|
||||
//Server cvar "var" = "value" [001.2]
|
||||
protected virtual void OnServerCvar(DateTime Timestamp, string[] info)
|
||||
{
|
||||
CvarEventArgs eventArgs = new CvarEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Cvar = info[1],
|
||||
Value = info[3]
|
||||
};
|
||||
ServerCvar.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
|
||||
//Server cvars end [001.3]
|
||||
protected virtual void OnCvarEnd(DateTime Timestamp)
|
||||
{
|
||||
CvarEndMsg.Fire(ServerEndPoint, new LogEventArgs() { Timestamp = Timestamp });
|
||||
}
|
||||
|
||||
//Log file started (file "filename") (game "game") (version "protocol/release/build") [002.1]
|
||||
protected virtual void OnLogFileStart(DateTime Timestamp, string[] info)
|
||||
{
|
||||
string[] tmp = info[5].Split('/');
|
||||
LogStartEventArgs eventArgs = new LogStartEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
FileName = info[1],
|
||||
Game = info[3],
|
||||
Protocol = tmp[0],
|
||||
Release = tmp[1],
|
||||
Build = tmp[2]
|
||||
};
|
||||
LogFileStarted.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//Log file closed [002.2]
|
||||
protected virtual void OnLogFileClose(DateTime Timestamp)
|
||||
{
|
||||
LogFileClosed.Fire(ServerEndPoint, new LogEventArgs() { Timestamp = Timestamp });
|
||||
}
|
||||
|
||||
//Loading map "map" [003.1]
|
||||
protected virtual void OnMapLoading(DateTime Timestamp, string[] info)
|
||||
{
|
||||
MapLoadEventArgs eventArgs = new MapLoadEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
MapName = info[1]
|
||||
};
|
||||
MapLoaded.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
//Started map "map" (CRC "crc") [003.2]
|
||||
protected virtual void OnMapStart(DateTime Timestamp, string[] info)
|
||||
{
|
||||
MapStartEventArgs eventArgs = new MapStartEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
MapName = info[1],
|
||||
MapCRC = info[3]
|
||||
};
|
||||
MapStarted.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
//Rcon: "rcon challenge "password" command" from "ip:port" [004.1]
|
||||
//Bad Rcon: "rcon challenge "password" command" from "ip:port" [004.2]
|
||||
protected virtual void OnRconMsg(DateTime Timestamp, string[] info)
|
||||
{
|
||||
string[] s = info[5].Split(':');
|
||||
RconEventArgs eventArgs = new RconEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
IsValid = info[0] == "Rcon: " ? true : false,
|
||||
Challenge = info[1].Split(' ')[1],
|
||||
Password = info[2],
|
||||
Command = info[3],
|
||||
Ip = s[0],
|
||||
Port = ushort.Parse(s[1])
|
||||
|
||||
};
|
||||
RconMsg.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//Server name is "hostname" [005]
|
||||
protected virtual void OnserverName(DateTime Timestamp, string[] info)
|
||||
{
|
||||
ServerNameEventArgs eventArgs = new ServerNameEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Name = info[1]
|
||||
};
|
||||
ServerName.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
//Server say "message" [006]
|
||||
protected virtual void OnServerSay(DateTime Timestamp, string[] info)
|
||||
{
|
||||
ServerSayEventArgs eventArgs = new ServerSayEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Message = info[1]
|
||||
};
|
||||
ServerSay.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><>" connected, address "ip:port" [50]
|
||||
protected virtual void OnConnection(DateTime Timestamp, string[] info)
|
||||
{
|
||||
string[] s = info[2].Split(':');
|
||||
ConnectEventArgs eventArgs = new ConnectEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
Ip = s[0],
|
||||
Port = ushort.Parse(s[1])
|
||||
};
|
||||
PlayerConnected.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><>" STEAM USERID validated [50b]
|
||||
protected virtual void OnValidation(DateTime Timestamp, string[] info)
|
||||
{
|
||||
PlayerEventArgs eventArgs = new PlayerEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
};
|
||||
PlayerValidated.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><>" entered the game [51]
|
||||
protected virtual void OnEnterGame(DateTime Timestamp, string[] info)
|
||||
{
|
||||
PlayerEventArgs eventArgs = new PlayerEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
};
|
||||
PlayerEnteredGame.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" disconnected [52]
|
||||
protected virtual void OnDisconnection(DateTime Timestamp, string[] info)
|
||||
{
|
||||
PlayerEventArgs eventArgs = new PlayerEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
};
|
||||
PlayerDisConnected.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
//Kick: "Name<uid><wonid><>" was kicked by "Console" (message "") [52b]
|
||||
protected virtual void OnKick(DateTime Timestamp, string[] info)
|
||||
{
|
||||
KickEventArgs eventArgs = new KickEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[1]),
|
||||
Kicker = info[3],
|
||||
Message = info.Length == 7 ? info[5] : string.Empty
|
||||
};
|
||||
PlayerKicked.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" committed suicide with "weapon" [53]
|
||||
protected virtual void OnSuicide(DateTime Timestamp, string[] info)
|
||||
{
|
||||
SuicideEventArgs eventArgs = new SuicideEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
Weapon = info[2]
|
||||
};
|
||||
PlayerSuicided.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" joined team "team" [54]
|
||||
protected virtual void OnTeamSelection(DateTime Timestamp, string[] info)
|
||||
{
|
||||
TeamSelectionEventArgs eventArgs = new TeamSelectionEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
Team = info[2]
|
||||
};
|
||||
PlayerJoinedTeam.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" changed role to "role" [55]
|
||||
protected virtual void OnRoleSelection(DateTime Timestamp, string[] info)
|
||||
{
|
||||
RoleSelectionEventArgs eventArgs = new RoleSelectionEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
Role = info[2]
|
||||
};
|
||||
PlayerChangedRole.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" changed name to "Name" [56]
|
||||
protected virtual void OnNameChange(DateTime Timestamp, string[] info)
|
||||
{
|
||||
NameChangeEventArgs eventArgs = new NameChangeEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
NewName = info[2]
|
||||
};
|
||||
PlayerChangedName.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" killed "Name<uid><wonid><team>" with "weapon" [57]
|
||||
protected virtual void OnKill(DateTime Timestamp, string[] info)
|
||||
{
|
||||
KillEventArgs eventArgs = new KillEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
Victim = GetPlayerInfo(info[2]),
|
||||
Weapon = info[4]
|
||||
};
|
||||
PlayerKilled.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" attacked "Name<uid><wonid><team>" with "weapon" (damage "damage") [58]
|
||||
protected virtual void OnInjure(DateTime Timestamp, string[] info)
|
||||
{
|
||||
InjureEventArgs eventArgs = new InjureEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
Victim = GetPlayerInfo(info[2]),
|
||||
Weapon = info[4],
|
||||
Damage = info[6]
|
||||
};
|
||||
PlayerInjured.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" triggered "action" against "Name<uid><wonid><team>" [59]
|
||||
protected virtual void OnPlayer_PlayerAction(DateTime Timestamp, string[] info)
|
||||
{
|
||||
PlayerOnPlayerEventArgs eventArgs = new PlayerOnPlayerEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Source = GetPlayerInfo(info[0]),
|
||||
Action = info[2],
|
||||
Target = GetPlayerInfo(info[4])
|
||||
};
|
||||
PlayerOnPLayerTriggered.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" triggered "action" [60]
|
||||
protected virtual void OnPlayerAction(DateTime Timestamp, string[] info)
|
||||
{
|
||||
string s = string.Empty;
|
||||
if (info.Length > 3)
|
||||
{
|
||||
for (int i = 3; i < info.Length; i++)
|
||||
s += info[i];
|
||||
}
|
||||
PlayerActionEventArgs eventArgs = new PlayerActionEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
Action = info[2],
|
||||
ExtraInfo = s
|
||||
};
|
||||
PlayerTriggered.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//Team "team" triggered "action" [61]
|
||||
protected virtual void OnTeamAction(DateTime Timestamp, string[] info)
|
||||
{
|
||||
TeamActionEventArgs eventArgs = new TeamActionEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Team = info[1],
|
||||
Action = info[3]
|
||||
};
|
||||
TeamTriggered.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//World triggered "action" [62]
|
||||
protected virtual void OnWorldAction(DateTime Timestamp, string[] info)
|
||||
{
|
||||
WorldActionEventArgs eventArgs = new WorldActionEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Action = info[1]
|
||||
};
|
||||
WorldTriggered.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
//"Name<uid><wonid><team>" say "message" [63.1]
|
||||
protected virtual void OnSay(DateTime Timestamp, string[] info)
|
||||
{
|
||||
ChatEventArgs eventArgs = new ChatEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
Message = info.Length == 3 ? info[2] : string.Empty
|
||||
};
|
||||
Say.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
// "Name<uid><wonid><team>" say_team "message" [63.2]
|
||||
protected virtual void OnTeamSay(DateTime Timestamp, string[] info)
|
||||
{
|
||||
ChatEventArgs eventArgs = new ChatEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
Message = info.Length == 3 ? info[2] : string.Empty
|
||||
};
|
||||
TeamSay.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//Team "team" formed alliance with team "team" [64]
|
||||
protected virtual void OnTeamAlliance(DateTime Timestamp, string[] info)
|
||||
{
|
||||
TeamAllianceEventArgs eventArgs = new TeamAllianceEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Team1 = info[1],
|
||||
Team2 = info[3]
|
||||
};
|
||||
TeamAlliance.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//Team "team" scored "score" with "numplayers" players + extra info [65]
|
||||
protected virtual void OnTeamScoreReport(DateTime Timestamp, string[] info)
|
||||
{
|
||||
string details = string.Empty;
|
||||
if (info.Length > 6)
|
||||
{
|
||||
for (int i = 6; i < info.Length; i++)
|
||||
details += info[i];
|
||||
}
|
||||
TeamScoreReportEventArgs eventArgs = new TeamScoreReportEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Team = info[1],
|
||||
Score = info[3],
|
||||
PlayerCount = info[5],
|
||||
ExtraInfo = details
|
||||
};
|
||||
TeamScoreReport.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" tell "Name<uid><wonid><team>" message "message" [66]
|
||||
protected virtual void OnPrivateChat(DateTime Timestamp, string[] info)
|
||||
{
|
||||
PrivateChatEventArgs eventArgs = new PrivateChatEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Sender = GetPlayerInfo(info[0]),
|
||||
Receiver = GetPlayerInfo(info[2]),
|
||||
Message = info.Length == 5 ? info[4] : string.Empty
|
||||
};
|
||||
PrivateChat.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//Player "Name<uid><wonid><team>" scored "score" + extra info [67]
|
||||
protected virtual void OnPlayerScoreReport(DateTime Timestamp, string[] info)
|
||||
{
|
||||
string details = string.Empty;
|
||||
if (info.Length > 4)
|
||||
{
|
||||
for (int i = 4; i < info.Length; i++)
|
||||
details += info[i];
|
||||
}
|
||||
PlayerScoreReportEventArgs eventArgs = new PlayerScoreReportEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[1]),
|
||||
Score = info[3],
|
||||
ExtraInfo = details
|
||||
|
||||
};
|
||||
PlayerScoreReport.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" selected weapon "weapon" [68]
|
||||
protected virtual void OnWeaponSelection(DateTime Timestamp, string[] info)
|
||||
{
|
||||
WeaponEventArgs eventArgs = new WeaponEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
Weapon = info[2]
|
||||
};
|
||||
PlayerSelectedWeapon.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
//"Name<uid><wonid><team>" acquired weapon "weapon" [69]
|
||||
protected virtual void OnWeaponPickup(DateTime Timestamp, string[] info)
|
||||
{
|
||||
WeaponEventArgs eventArgs = new WeaponEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
Player = GetPlayerInfo(info[0]),
|
||||
Weapon = info[2]
|
||||
};
|
||||
PlayerAcquiredWeapon.Fire(ServerEndPoint, eventArgs);
|
||||
}
|
||||
|
||||
protected virtual void OnShutDown(DateTime Timestamp)
|
||||
{
|
||||
ShutDown.Fire(ServerEndPoint, new LogEventArgs() { Timestamp = Timestamp });
|
||||
}
|
||||
protected virtual void OnException(DateTime Timestamp, string info)
|
||||
{
|
||||
ExceptionEventArgs eventArgs = new ExceptionEventArgs()
|
||||
{
|
||||
Timestamp = Timestamp,
|
||||
LogLine = info
|
||||
};
|
||||
Exception.Fire(ServerEndPoint, eventArgs);
|
||||
|
||||
}
|
||||
|
||||
//Name<uid><wonid><team>
|
||||
private PlayerInfo GetPlayerInfo(string s)
|
||||
{
|
||||
Match match = RegPlayer.Match(s);
|
||||
PlayerInfo info = new PlayerInfo()
|
||||
{
|
||||
Name = match.Groups[1].Value,
|
||||
Uid = match.Groups[2].Value,
|
||||
WonId = match.Groups[3].Value,
|
||||
Team = match.Groups[4].Value,
|
||||
};
|
||||
return info;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
39
src/QueryMaster/MasterQuery.cs
Normal file
39
src/QueryMaster/MasterQuery.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to create MasterServer instance
|
||||
/// </summary>
|
||||
public class MasterQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Master server for Gold Source games
|
||||
/// </summary>
|
||||
public static IPEndPoint GoldSrcServer = new IPEndPoint(Dns.GetHostAddresses("hl1master.steampowered.com")[0], 27011);
|
||||
/// <summary>
|
||||
/// Master server for Source games
|
||||
/// </summary>
|
||||
public static IPEndPoint SourceServer = new IPEndPoint(Dns.GetHostAddresses("hl2master.steampowered.com")[1], 27011);
|
||||
/// <summary>
|
||||
/// Gets the appropriate masterserver query instance
|
||||
/// </summary>
|
||||
/// <param name="type">Engine used by server</param>
|
||||
/// <returns>Master server instance</returns>
|
||||
public static MasterServer GetMasterServerInstance(EngineType type)
|
||||
{
|
||||
MasterServer server = null;
|
||||
switch (type)
|
||||
{
|
||||
case EngineType.GoldSource: server = new MasterServer(GoldSrcServer); break;
|
||||
case EngineType.Source: server = new MasterServer(SourceServer); break;
|
||||
default: throw new FormatException("An invalid EngineType was specified.");
|
||||
}
|
||||
return server;
|
||||
}
|
||||
}
|
||||
}
|
||||
96
src/QueryMaster/MasterServer.cs
Normal file
96
src/QueryMaster/MasterServer.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
/// <summary>
|
||||
/// Encapsulates a method that has a parameter of type ReadOnlyCollection which accepts IPEndPoint instances.
|
||||
/// Invoked when a reply from Master Server is received.
|
||||
/// </summary>
|
||||
/// <param name="endPoints">Server Sockets</param>
|
||||
public delegate void MasterIpCallback(ReadOnlyCollection<IPEndPoint> endPoints);
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods to query master server.
|
||||
/// </summary>
|
||||
public class MasterServer : IDisposable
|
||||
{
|
||||
public readonly IPEndPoint SeedEndpoint = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 0);
|
||||
Socket UdpSocket;
|
||||
IPEndPoint RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
|
||||
private bool IsListening = false;
|
||||
private MasterIpCallback Callback;
|
||||
private Region RegionCode;
|
||||
private IpFilter Filter;
|
||||
private byte[] Msg;
|
||||
private byte[] recvData;
|
||||
internal MasterServer(IPEndPoint endPoint)
|
||||
{
|
||||
UdpSocket = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, ProtocolType.Udp);
|
||||
UdpSocket.Connect(endPoint);
|
||||
}
|
||||
/// <summary>
|
||||
/// Starts receiving socket addresses of servers.
|
||||
/// </summary>
|
||||
/// <param name="region">The region of the world that you wish to find servers in.</param>
|
||||
/// <param name="callback">Called when a batch of Socket addresses are received.</param>
|
||||
/// <param name="filter">Used to set filter on the type of server required.</param>
|
||||
public void GetAddresses(Region region, MasterIpCallback callback, IpFilter filter = null)
|
||||
{
|
||||
if (IsListening) return;
|
||||
|
||||
RegionCode = region;
|
||||
Callback = callback;
|
||||
Filter = filter;
|
||||
IsListening = true;
|
||||
IPEndPoint endPoint = SeedEndpoint;
|
||||
Msg = MasterUtil.BuildPacket(endPoint.ToString(), RegionCode, Filter);
|
||||
UdpSocket.Send(Msg);
|
||||
recvData = new byte[1400];
|
||||
UdpSocket.BeginReceive(recvData, 0, recvData.Length, SocketFlags.None, recv, null);
|
||||
|
||||
}
|
||||
|
||||
private void recv(IAsyncResult res)
|
||||
{
|
||||
int bytesRev = 0;
|
||||
try
|
||||
{
|
||||
bytesRev = UdpSocket.EndReceive(res);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var endpoints = MasterUtil.ProcessPacket(recvData.Take(bytesRev).ToArray());
|
||||
//ThreadPool.QueueUserWorkItem(x => Callback(endpoints));
|
||||
Callback(endpoints);
|
||||
if (!endpoints.Last().Equals(SeedEndpoint))
|
||||
{
|
||||
Msg = MasterUtil.BuildPacket(endpoints.Last().ToString(), RegionCode, Filter);
|
||||
UdpSocket.Send(Msg);
|
||||
UdpSocket.BeginReceive(recvData, 0, recvData.Length, SocketFlags.None, recv, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
IsListening = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes all the resources used MasterServer instance
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (UdpSocket != null && IsListening == true)
|
||||
UdpSocket.Close();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
100
src/QueryMaster/MasterUtil.cs
Normal file
100
src/QueryMaster/MasterUtil.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
static class MasterUtil
|
||||
{
|
||||
private static readonly byte Header = 0x31;
|
||||
internal static byte[] BuildPacket(string endPoint, Region region, IpFilter filter)
|
||||
{
|
||||
List<byte> msg = new List<byte>();
|
||||
msg.Add(Header);
|
||||
msg.Add((byte)region);
|
||||
msg.AddRange(Util.StringToBytes(endPoint));
|
||||
msg.Add(0x00);
|
||||
if (filter != null)
|
||||
msg.AddRange(Util.StringToBytes(ProcessFilter(filter)));
|
||||
msg.Add(0x00);
|
||||
return msg.ToArray();
|
||||
}
|
||||
internal static ReadOnlyCollection<IPEndPoint> ProcessPacket(byte[] packet)
|
||||
{
|
||||
Parser parser = new Parser(packet);
|
||||
List<IPEndPoint> endPoints = new List<IPEndPoint>();
|
||||
parser.Skip(6);
|
||||
int counter = 6;
|
||||
string ip = string.Empty; ;
|
||||
int port = 0;
|
||||
while (counter != packet.Length)
|
||||
{
|
||||
ip = parser.ReadByte() + "." + parser.ReadByte() + "." + parser.ReadByte() + "." + parser.ReadByte();
|
||||
byte portByte1 = parser.ReadByte();
|
||||
byte portByte2 = parser.ReadByte();
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
port = BitConverter.ToUInt16(new byte[] { portByte2, portByte1 }, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
port = BitConverter.ToUInt16(new byte[] { portByte1, portByte2 }, 0);
|
||||
}
|
||||
endPoints.Add(new IPEndPoint(IPAddress.Parse(ip), port));
|
||||
counter += 6;
|
||||
}
|
||||
return endPoints.AsReadOnly();
|
||||
|
||||
}
|
||||
internal static string ProcessFilter(IpFilter filter)
|
||||
{
|
||||
StringBuilder filterStr = new StringBuilder();
|
||||
if(filter.IsDedicated)
|
||||
filterStr.Append(@"\type\d");
|
||||
if (filter.IsSecure)
|
||||
filterStr.Append(@"\secure\1");
|
||||
if (!string.IsNullOrEmpty(filter.GameDirectory))
|
||||
filterStr.Append(@"\gamedir\" + filter.GameDirectory);
|
||||
if (!string.IsNullOrEmpty(filter.Map))
|
||||
filterStr.Append(@"\map\" + filter.Map);
|
||||
if (filter.IsLinux)
|
||||
filterStr.Append(@"\linux\1");
|
||||
if (filter.IsNotEmpty)
|
||||
filterStr.Append(@"\empty\1");
|
||||
if (filter.IsNotFull)
|
||||
filterStr.Append(@"\full\1");
|
||||
if (filter.IsProxy)
|
||||
filterStr.Append(@"\proxy\1");
|
||||
if (filter.App != 0)
|
||||
filterStr.Append(@"\appid\" + filter.App);
|
||||
if (filter.NApp != 0)
|
||||
filterStr.Append(@"\napp\" + filter.NApp);
|
||||
if (filter.IsNoPlayers)
|
||||
filterStr.Append(@"\noplayers\1");
|
||||
if (filter.IsWhiteListed)
|
||||
filterStr.Append(@"\white\1");
|
||||
if (!string.IsNullOrEmpty(filter.Sv_Tags))
|
||||
filterStr.Append(@"\gametype\" + filter.Sv_Tags);
|
||||
if (!string.IsNullOrEmpty(filter.GameData))
|
||||
filterStr.Append(@"\gamedata\" + filter.GameData);
|
||||
if (!string.IsNullOrEmpty(filter.GameDataOr))
|
||||
filterStr.Append(@"\gamedataor\" + filter.GameDataOr);
|
||||
|
||||
if (filter.IpAddr != null && filter.IpAddr.Length > 0)
|
||||
{
|
||||
foreach (var ipaddr in filter.IpAddr)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ipaddr))
|
||||
{
|
||||
filterStr.Append(@"\gameaddr\").Append(ipaddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
//filterStr.Append('\0');
|
||||
return filterStr.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
98
src/QueryMaster/Parser.cs
Normal file
98
src/QueryMaster/Parser.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
class Parser
|
||||
{
|
||||
private byte[] Data = null;
|
||||
private int CurrentPosition = -1;
|
||||
private int LastPosition;
|
||||
|
||||
internal bool HasUnParsedBytes
|
||||
{
|
||||
get { return CurrentPosition < LastPosition; }
|
||||
}
|
||||
|
||||
internal Parser(byte[] data)
|
||||
{
|
||||
Data = data;
|
||||
CurrentPosition = -1;
|
||||
LastPosition = Data.Length - 1;
|
||||
}
|
||||
|
||||
internal byte ReadByte()
|
||||
{
|
||||
CurrentPosition++;
|
||||
if (CurrentPosition > LastPosition)
|
||||
throw new ParseException("Index was outside the bounds of the byte array.");
|
||||
return Data[CurrentPosition];
|
||||
|
||||
}
|
||||
|
||||
internal short ReadShort()
|
||||
{
|
||||
CurrentPosition++;
|
||||
if (CurrentPosition + 3 > LastPosition)
|
||||
throw new ParseException("Unable to parse bytes to short.");
|
||||
short num;
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
Array.Reverse(Data, CurrentPosition, 2);
|
||||
num = BitConverter.ToInt16(Data, CurrentPosition);
|
||||
CurrentPosition++;
|
||||
return num;
|
||||
}
|
||||
|
||||
internal int ReadInt()
|
||||
{
|
||||
CurrentPosition++;
|
||||
if (CurrentPosition + 3 > LastPosition)
|
||||
throw new ParseException("Unable to parse bytes to int.");
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
Array.Reverse(Data, CurrentPosition, 4);
|
||||
int num = BitConverter.ToInt32(Data, CurrentPosition);
|
||||
CurrentPosition += 3;
|
||||
return num;
|
||||
}
|
||||
|
||||
internal float ReadFloat()
|
||||
{
|
||||
CurrentPosition++;
|
||||
if (CurrentPosition + 3 > LastPosition)
|
||||
throw new ParseException("Unable to parse bytes to float.");
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
Array.Reverse(Data, CurrentPosition, 4);
|
||||
float Num = BitConverter.ToSingle(Data, CurrentPosition);
|
||||
CurrentPosition += 3;
|
||||
return Num;
|
||||
|
||||
}
|
||||
|
||||
internal string ReadString()
|
||||
{
|
||||
CurrentPosition++;
|
||||
int temp = CurrentPosition;
|
||||
while (Data[CurrentPosition] != 0x00)
|
||||
{
|
||||
CurrentPosition++;
|
||||
if (CurrentPosition > LastPosition)
|
||||
throw new ParseException("Unable to parse bytes to string.");
|
||||
}
|
||||
return Encoding.UTF8.GetString(Data, temp, CurrentPosition - temp);
|
||||
}
|
||||
|
||||
internal void Skip(byte count)
|
||||
{
|
||||
CurrentPosition += count;
|
||||
if (CurrentPosition > LastPosition)
|
||||
throw new ParseException("skip count was outside the bounds of the byte array.");
|
||||
}
|
||||
|
||||
internal byte[] GetUnParsedData()
|
||||
{
|
||||
return Data.Skip(CurrentPosition + 1).ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
19
src/QueryMaster/Properties/AssemblyInfo.cs
Normal file
19
src/QueryMaster/Properties/AssemblyInfo.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
[assembly: AssemblyTitle("QueryMaster")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("QueryMaster")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
[assembly: Guid("f8b1937a-2d2e-48a8-9364-3f0719461309")]
|
||||
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
22
src/QueryMaster/QueryMaster.csproj
Normal file
22
src/QueryMaster/QueryMaster.csproj
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Globals">
|
||||
<SccProjectName>%24/Development/ServerManagers/Main/QueryMaster</SccProjectName>
|
||||
<SccProvider>{4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}</SccProvider>
|
||||
<SccAuxPath>https://dev.azure.com/bretthewitson</SccAuxPath>
|
||||
<SccLocalPath>.</SccLocalPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net462</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNetZip" Version="1.13.8" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
27
src/QueryMaster/QueryMsg.cs
Normal file
27
src/QueryMaster/QueryMsg.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
static class QueryMsg
|
||||
{
|
||||
|
||||
internal static readonly byte[] InfoQuery = { 0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00 };
|
||||
internal static readonly byte[] ObsoleteInfoQuery = { 0xFF, 0xFF, 0xFF, 0xFF, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6C, 0x73 };
|
||||
|
||||
internal static readonly byte[] RuleChallengeQuery = { 0xFF, 0xFF, 0xFF, 0xFF, 0x56, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
internal static readonly byte[] PlayerChallengeQuery = { 0xFF, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
|
||||
internal static readonly byte[] PlayerQuery = { 0xFF, 0xFF, 0xFF, 0xFF, 0x55 };
|
||||
internal static readonly byte[] ObsoletePlayerQuery = { 0xFF, 0xFF, 0xFF, 0xFF, 0x70, 0x6C, 0x61, 0x79, 0x65, 0x72, 0x73 };
|
||||
|
||||
internal static readonly byte[] RuleQuery = { 0xFF, 0xFF, 0xFF, 0xFF, 0x56 };
|
||||
internal static readonly byte[] ObsoleteRuleQuery = { 0xFF, 0xFF, 0xFF, 0xFF, 0x72, 0x75, 0x6C, 0x65, 0x73 };
|
||||
|
||||
internal static readonly byte[] ObsoletePingQuery = { 0xFF, 0xFF, 0xFF, 0xFF, 0x69 };
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
37
src/QueryMaster/Rcon.cs
Normal file
37
src/QueryMaster/Rcon.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to access server using rcon password.
|
||||
/// </summary>
|
||||
public abstract class Rcon : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Send a Command to server.
|
||||
/// </summary>
|
||||
/// <param name="cmd">Server command.</param>
|
||||
/// <returns>Reply from server(string).</returns>
|
||||
public abstract string SendCommand(string cmd);
|
||||
/// <summary>
|
||||
/// Add a client socket to server's logaddress list.
|
||||
/// </summary>
|
||||
/// <param name="ip">IP-Address of client.</param>
|
||||
/// <param name="port">Port number of client.</param>
|
||||
public abstract void AddlogAddress(string ip, ushort port);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a client socket to server's logaddress list.
|
||||
/// </summary>
|
||||
/// <param name="ip">IP-Address of client.</param>
|
||||
/// <param name="port">Port number of client.</param>
|
||||
public abstract void RemovelogAddress(string ip, ushort port);
|
||||
/// <summary>
|
||||
/// Disposes rcon Object
|
||||
/// </summary>
|
||||
public abstract void Dispose();
|
||||
}
|
||||
}
|
||||
88
src/QueryMaster/RconGoldSource.cs
Normal file
88
src/QueryMaster/RconGoldSource.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
class RconGoldSource : Rcon
|
||||
{
|
||||
internal static readonly byte[] RconChIdQuery = { 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x20, 0x72, 0x63, 0x6f, 0x6e };
|
||||
|
||||
internal static readonly byte[] RconQuery = { 0xFF, 0xFF, 0xFF, 0xFF, 0x72, 0x63, 0x6f, 0x6e, 0x20 };//+<challenge id>+"<rcon password>"+<value>
|
||||
internal string RConPass = string.Empty;
|
||||
internal UdpQuery socket;
|
||||
private RconGoldSource(IPEndPoint address)
|
||||
{
|
||||
socket = new UdpQuery(address, 3000, 3000);
|
||||
}
|
||||
|
||||
internal string ChallengeId = string.Empty;
|
||||
internal static Rcon Authorize(IPEndPoint address, string pass)
|
||||
{
|
||||
RconGoldSource Obj = new RconGoldSource(address);
|
||||
Obj.GetChallengeId();
|
||||
Obj.RConPass = pass;
|
||||
if (!Obj.SendCommand("").Contains("Bad rcon_password"))
|
||||
{
|
||||
return Obj;
|
||||
}
|
||||
Obj.socket.Dispose();
|
||||
return null;
|
||||
}
|
||||
|
||||
public override string SendCommand(string command)
|
||||
{
|
||||
byte[] rconMsg = Util.MergeByteArrays(RconQuery, Util.StringToBytes(ChallengeId), Util.StringToBytes(" \"" + RConPass + "\" " + command));
|
||||
byte[] recvData = new byte[2000];
|
||||
string s;
|
||||
recvData = socket.GetResponse(rconMsg, EngineType.GoldSource);
|
||||
try
|
||||
{
|
||||
s= Util.BytesToString(recvData).Remove(0, 1);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", recvData);
|
||||
throw;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private void GetChallengeId()
|
||||
{
|
||||
byte[] recvData=null;
|
||||
try
|
||||
{
|
||||
recvData = socket.GetResponse(RconChIdQuery, EngineType.GoldSource);
|
||||
Parser parser = new Parser(recvData);
|
||||
ChallengeId = parser.ReadString().Split(' ')[2].Trim();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", recvData);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (socket != null)
|
||||
socket.Dispose();
|
||||
}
|
||||
|
||||
|
||||
public override void AddlogAddress(string ip, ushort port)
|
||||
{
|
||||
SendCommand("logaddress_add " + ip + " " + port);
|
||||
}
|
||||
|
||||
public override void RemovelogAddress(string ip, ushort port)
|
||||
{
|
||||
SendCommand("logaddress_del " + ip + " " + port);
|
||||
}
|
||||
}
|
||||
}
|
||||
86
src/QueryMaster/RconSource.cs
Normal file
86
src/QueryMaster/RconSource.cs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
class RconSource : Rcon
|
||||
{
|
||||
internal TcpQuery socket;
|
||||
private RconSource(IPEndPoint address)
|
||||
{
|
||||
socket = new TcpQuery(address, 3000, 3000);
|
||||
}
|
||||
|
||||
internal static Rcon Authorize(IPEndPoint address, string msg)
|
||||
{
|
||||
RconSource obj = new RconSource(address);
|
||||
byte[] recvData = new byte[50];
|
||||
RconSrcPacket packet = new RconSrcPacket() { Body = msg, Id = (int)PacketId.ExecCmd, Type = (int)PacketType.Auth };
|
||||
recvData = obj.socket.GetResponse(RconUtil.GetBytes(packet));
|
||||
int header;
|
||||
try
|
||||
{
|
||||
header = BitConverter.ToInt32(recvData, 4);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", recvData);
|
||||
throw;
|
||||
}
|
||||
if (header != -1)
|
||||
{
|
||||
return obj;
|
||||
}
|
||||
obj.socket.Dispose();
|
||||
return obj;
|
||||
}
|
||||
|
||||
public override string SendCommand(string command)
|
||||
{
|
||||
RconSrcPacket senPacket = new RconSrcPacket() { Body = command, Id = (int)PacketId.ExecCmd, Type = (int)PacketType.Exec };
|
||||
List<byte[]> recvData = socket.GetMultiPacketResponse(RconUtil.GetBytes(senPacket));
|
||||
StringBuilder str = new StringBuilder();
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < recvData.Count; i++)
|
||||
{
|
||||
//consecutive rcon command replies start with an empty packet
|
||||
if (BitConverter.ToInt32(recvData[i], 4) == (int)PacketId.Empty)
|
||||
continue;
|
||||
//if (recvData[i].Length - BitConverter.ToInt32(recvData[i], 0) == 4)
|
||||
//{
|
||||
str.Append(RconUtil.ProcessPacket(recvData[i]).Body);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// str.Append(RconUtil.ProcessPacket(recvData[i]).Body + Util.BytesToString(recvData[++i].Take(recvData[i].Length - 2).ToArray()));
|
||||
//}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", recvData.SelectMany(x => x).ToArray());
|
||||
throw;
|
||||
}
|
||||
return str.ToString();
|
||||
}
|
||||
|
||||
public override void AddlogAddress(string ip, ushort port)
|
||||
{
|
||||
SendCommand("logaddress_add " + ip + ":" + port);
|
||||
}
|
||||
|
||||
public override void RemovelogAddress(string ip, ushort port)
|
||||
{
|
||||
SendCommand("logaddress_del " + ip + ":" + port);
|
||||
}
|
||||
public override void Dispose()
|
||||
{
|
||||
if (socket != null)
|
||||
socket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/QueryMaster/RconSrcPacket.cs
Normal file
15
src/QueryMaster/RconSrcPacket.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
class RconSrcPacket
|
||||
{
|
||||
internal int Size { get; set; }
|
||||
internal int Id { get; set; }
|
||||
internal int Type { get; set; }
|
||||
internal string Body { get; set; }
|
||||
}
|
||||
}
|
||||
51
src/QueryMaster/RconUtil.cs
Normal file
51
src/QueryMaster/RconUtil.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
static class RconUtil
|
||||
{
|
||||
internal static byte[] GetBytes(RconSrcPacket packet)
|
||||
{
|
||||
byte[] command = Util.StringToBytes(packet.Body);
|
||||
packet.Size = 10 + command.Length;
|
||||
List<byte> y = new List<byte>(packet.Size + 4);
|
||||
y.AddRange(BitConverter.GetBytes(packet.Size));
|
||||
y.AddRange(BitConverter.GetBytes(packet.Id));
|
||||
y.AddRange(BitConverter.GetBytes(packet.Type));
|
||||
y.AddRange(command);
|
||||
//part of string
|
||||
y.Add(0x00);
|
||||
//end terminater
|
||||
y.Add(0x00);
|
||||
return y.ToArray();
|
||||
}
|
||||
|
||||
internal static RconSrcPacket ProcessPacket(byte[] data)
|
||||
{
|
||||
RconSrcPacket packet = new RconSrcPacket();
|
||||
try
|
||||
{
|
||||
Parser parser = new Parser(data);
|
||||
packet.Size = parser.ReadInt();
|
||||
packet.Id = parser.ReadInt();
|
||||
packet.Type = parser.ReadInt();
|
||||
byte[] body = parser.GetUnParsedData();
|
||||
if (body.Length == 2)
|
||||
packet.Body = string.Empty;
|
||||
else
|
||||
{
|
||||
packet.Body = Util.BytesToString(body).TrimEnd('\0', ' ');
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", data);
|
||||
throw;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
}
|
||||
386
src/QueryMaster/Server.cs
Normal file
386
src/QueryMaster/Server.cs
Normal file
|
|
@ -0,0 +1,386 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net.Sockets;
|
||||
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the connected server.Provides methods to query,listen to server logs and control the server
|
||||
/// </summary>
|
||||
public abstract class Server : IDisposable
|
||||
{
|
||||
private bool IsDisposed = false;
|
||||
private EngineType Type;
|
||||
private long Latency;
|
||||
private Logs logs;
|
||||
private IPEndPoint ServerEndPoint;
|
||||
internal UdpQuery socket;
|
||||
internal Rcon RConObj;
|
||||
/// <summary>
|
||||
/// Returns true if server replies only to half life protocol messages.
|
||||
/// </summary>
|
||||
public bool IsObsolete { get; private set; }
|
||||
private byte[] PlayerChallengeId = null;
|
||||
private byte[] RuleChallengeId = null;
|
||||
private bool IsPlayerChallengeId;
|
||||
private bool IsRuleChallengeId;
|
||||
|
||||
internal Server(IPEndPoint address, EngineType type, bool? isObsolete, int sendTimeOut, int receiveTimeOut)
|
||||
{
|
||||
ServerEndPoint = address;
|
||||
socket = new UdpQuery(address, sendTimeOut, receiveTimeOut);
|
||||
IsDisposed = false;
|
||||
Type = type;
|
||||
if (isObsolete == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (socket.GetResponse(QueryMsg.ObsoleteInfoQuery, Type)[0] == 0x6D)
|
||||
IsObsolete = true;
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
if (e.ErrorCode == 10060)
|
||||
IsObsolete = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
IsObsolete = isObsolete == true ? true : false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves information about the server
|
||||
/// </summary>
|
||||
/// <returns>Instance of ServerInfo class</returns>
|
||||
public virtual ServerInfo GetInfo()
|
||||
{
|
||||
byte[] Query = QueryMsg.InfoQuery;
|
||||
if (IsObsolete)
|
||||
Query = QueryMsg.ObsoleteInfoQuery;
|
||||
|
||||
byte[] recvData = new byte[socket.BufferSize];
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
recvData = socket.GetResponse(Query, Type);
|
||||
sw.Stop();
|
||||
Latency = sw.ElapsedMilliseconds;
|
||||
try
|
||||
{
|
||||
switch (recvData[0])
|
||||
{
|
||||
case 0x49: return Current(recvData);
|
||||
case 0x6D: return Obsolete(recvData);
|
||||
default: throw new InvalidHeaderException("packet header is not valid");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", recvData);
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ServerInfo Current(byte[] data)
|
||||
{
|
||||
Parser parser = new Parser(data);
|
||||
if (parser.ReadByte() != (byte)ResponseMsgHeader.A2S_INFO)
|
||||
throw new InvalidHeaderException("A2S_INFO message header is not valid");
|
||||
ServerInfo server = new ServerInfo();
|
||||
server.IsObsolete = false;
|
||||
server.Protocol = parser.ReadByte();
|
||||
server.Name = parser.ReadString();
|
||||
server.Map = parser.ReadString();
|
||||
server.Directory = parser.ReadString();
|
||||
server.Description = parser.ReadString();
|
||||
server.Id = parser.ReadShort();
|
||||
server.Players = parser.ReadByte();
|
||||
server.MaxPlayers = parser.ReadByte();
|
||||
server.Bots = parser.ReadByte();
|
||||
server.ServerType = (new Func<string>(() => { switch ((char)parser.ReadByte()) { case 'l':return "Listen"; case 'd':return "Dedicated"; case 'p':return "SourceTV"; } return ""; }))();
|
||||
server.Environment = (new Func<string>(() => { switch ((char)parser.ReadByte()) { case 'l':return "Linux"; case 'w':return "Windows"; case 'm':return "Mac"; } return ""; }))();
|
||||
server.IsPrivate = Convert.ToBoolean(parser.ReadByte());
|
||||
server.IsSecure = Convert.ToBoolean(parser.ReadByte());
|
||||
if (server.Id >= 2400 && server.Id <= 2412)
|
||||
{
|
||||
TheShip ship = new TheShip();
|
||||
switch (parser.ReadByte())
|
||||
{
|
||||
case 0: ship.Mode = "Hunt"; break;
|
||||
case 1: ship.Mode = "Elimination"; break;
|
||||
case 2: ship.Mode = "Duel"; break;
|
||||
case 3: ship.Mode = "Deathmatch"; break;
|
||||
case 4: ship.Mode = "VIP Team"; break;
|
||||
case 5: ship.Mode = "Team Elimination"; break;
|
||||
default: ship.Mode = ""; break;
|
||||
}
|
||||
ship.Witnesses = parser.ReadByte();
|
||||
ship.Duration = parser.ReadByte();
|
||||
server.ShipInfo = ship;
|
||||
}
|
||||
server.GameVersion = parser.ReadString();
|
||||
|
||||
if (parser.HasUnParsedBytes)
|
||||
{
|
||||
byte edf = parser.ReadByte();
|
||||
ExtraInfo info = new ExtraInfo();
|
||||
info.Port = (edf & 0x80) > 0 ? parser.ReadShort() : (short)0;
|
||||
info.SteamID = (edf & 0x10) > 0 ? parser.ReadInt() : 0;
|
||||
if ((edf & 0x40) > 0)
|
||||
info.SpecInfo = new SourceTVInfo() { Port = parser.ReadShort(), Name = parser.ReadString() };
|
||||
info.Keywords = (edf & 0x20) > 0 ? parser.ReadString() : string.Empty;
|
||||
info.GameId = (edf & 0x10) > 0 ? parser.ReadInt() : 0;
|
||||
server.Extra = info;
|
||||
|
||||
}
|
||||
server.Address = socket.Address.Address + ":" + socket.Address.Port;
|
||||
server.Ping = Latency;
|
||||
return server;
|
||||
}
|
||||
private ServerInfo Obsolete(byte[] data)
|
||||
{
|
||||
Parser parser = new Parser(data);
|
||||
if (parser.ReadByte() != (byte)ResponseMsgHeader.A2S_INFO_Obsolete)
|
||||
throw new InvalidHeaderException("A2S_INFO(obsolete) message header is not valid");
|
||||
ServerInfo server = new ServerInfo();
|
||||
server.IsObsolete = true;
|
||||
server.Address = parser.ReadString();
|
||||
server.Name = parser.ReadString();
|
||||
server.Map = parser.ReadString();
|
||||
server.Directory = parser.ReadString();
|
||||
server.Id = Util.GetGameId(parser.ReadString());
|
||||
server.Players = parser.ReadByte();
|
||||
server.MaxPlayers = parser.ReadByte();
|
||||
server.Protocol = parser.ReadByte();
|
||||
server.ServerType = (new Func<string>(() => { switch ((char)parser.ReadByte()) { case 'L':return "non-dedicated server"; case 'D':return "dedicated"; case 'P':return "HLTV server"; } return ""; }))();
|
||||
server.Environment = (new Func<string>(() => { switch ((char)parser.ReadByte()) { case 'L':return "Linux"; case 'W':return "Windows"; } return ""; }))();
|
||||
server.IsPrivate = Convert.ToBoolean(parser.ReadByte());
|
||||
byte mod = parser.ReadByte();
|
||||
server.IsModded = mod > 0 ? true : false;
|
||||
if (server.IsModded)
|
||||
{
|
||||
Mod modinfo = new Mod();
|
||||
modinfo.Link = parser.ReadString();
|
||||
modinfo.DownloadLink = parser.ReadString();
|
||||
parser.ReadByte();//0x00
|
||||
modinfo.Version = parser.ReadInt();
|
||||
modinfo.Size = parser.ReadInt();
|
||||
modinfo.IsOnlyMultiPlayer = parser.ReadByte() > 0 ? true : false;
|
||||
modinfo.IsHalfLifeDll = parser.ReadByte() < 0 ? true : false;
|
||||
server.ModInfo = modinfo;
|
||||
}
|
||||
server.IsSecure = Convert.ToBoolean(parser.ReadByte());
|
||||
server.Bots = parser.ReadByte();
|
||||
server.GameVersion = "server is obsolete,does not provide this information";
|
||||
server.Ping = Latency;
|
||||
return server;
|
||||
}
|
||||
/// <summary>
|
||||
/// Retrieves information about the players currently on the server
|
||||
/// </summary>
|
||||
/// <returns>ReadOnlyCollection of Player instances</returns>
|
||||
public virtual ReadOnlyCollection<Player> GetPlayers()
|
||||
{
|
||||
byte[] recvData = null;
|
||||
|
||||
if (IsObsolete)
|
||||
{
|
||||
recvData = socket.GetResponse(QueryMsg.ObsoletePlayerQuery, Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PlayerChallengeId == null)
|
||||
{
|
||||
recvData = GetPlayerChallengeId();
|
||||
if (IsPlayerChallengeId)
|
||||
PlayerChallengeId = recvData;
|
||||
}
|
||||
if (IsPlayerChallengeId)
|
||||
recvData = socket.GetResponse(Util.MergeByteArrays(QueryMsg.PlayerQuery, PlayerChallengeId), Type);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Parser parser = new Parser(recvData);
|
||||
if (parser.ReadByte() != (byte)ResponseMsgHeader.A2S_PLAYER)
|
||||
throw new InvalidHeaderException("A2S_PLAYER message header is not valid");
|
||||
int playerCount = parser.ReadByte();
|
||||
List<Player> players = new List<Player>(playerCount);
|
||||
for (int i = 0; i < playerCount; i++)
|
||||
{
|
||||
parser.ReadByte();//index,always equal to 0
|
||||
players.Add(new Player()
|
||||
{
|
||||
Name = parser.ReadString(),
|
||||
Score = parser.ReadInt(),
|
||||
Time = TimeSpan.FromSeconds(parser.ReadFloat())
|
||||
});
|
||||
}
|
||||
if (playerCount == 1 && players[0].Name == "Max Players")
|
||||
return null;
|
||||
return players.AsReadOnly();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", recvData);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Retrieves server rules
|
||||
/// </summary>
|
||||
/// <returns>ReadOnlyCollection of Rule instances</returns>
|
||||
public ReadOnlyCollection<Rule> GetRules()
|
||||
{
|
||||
byte[] recvData = null;
|
||||
if (IsObsolete)
|
||||
{
|
||||
recvData = socket.GetResponse(QueryMsg.ObsoleteRuleQuery, Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RuleChallengeId == null)
|
||||
{
|
||||
recvData = GetRuleChallengeId();
|
||||
if (IsRuleChallengeId)
|
||||
RuleChallengeId = recvData;
|
||||
}
|
||||
if (IsRuleChallengeId)
|
||||
recvData = socket.GetResponse(Util.MergeByteArrays(QueryMsg.RuleQuery, RuleChallengeId), Type);
|
||||
}
|
||||
try
|
||||
{
|
||||
Parser parser = new Parser(recvData);
|
||||
if (parser.ReadByte() != (byte)ResponseMsgHeader.A2S_RULES)
|
||||
throw new InvalidHeaderException("A2S_RULES message header is not valid");
|
||||
|
||||
int count = parser.ReadShort();//number of rules
|
||||
List<Rule> rules = new List<Rule>(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
rules.Add(new Rule() { Name = parser.ReadString(), Value = parser.ReadString() });
|
||||
}
|
||||
return rules.AsReadOnly();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", recvData);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GetPlayerChallengeId()
|
||||
{
|
||||
byte[] recvBytes = null;
|
||||
byte header = 0;
|
||||
Parser parser = null;
|
||||
recvBytes = socket.GetResponse(QueryMsg.PlayerChallengeQuery, Type);
|
||||
try
|
||||
{
|
||||
parser = new Parser(recvBytes);
|
||||
header = parser.ReadByte();
|
||||
switch (header)
|
||||
{
|
||||
case (byte)ResponseMsgHeader.A2S_SERVERQUERY_GETCHALLENGE: IsPlayerChallengeId = true; return parser.GetUnParsedData();
|
||||
case (byte)ResponseMsgHeader.A2S_PLAYER: IsPlayerChallengeId = false; return recvBytes;
|
||||
default: throw new InvalidHeaderException("A2S_SERVERQUERY_GETCHALLENGE message header is not valid");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", recvBytes);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GetRuleChallengeId()
|
||||
{
|
||||
byte[] recvBytes = null;
|
||||
byte header = 0;
|
||||
Parser parser = null;
|
||||
recvBytes = socket.GetResponse(QueryMsg.RuleChallengeQuery, Type);
|
||||
try
|
||||
{
|
||||
parser = new Parser(recvBytes);
|
||||
header = parser.ReadByte();
|
||||
|
||||
switch (header)
|
||||
{
|
||||
case (byte)ResponseMsgHeader.A2S_SERVERQUERY_GETCHALLENGE: IsRuleChallengeId = true; return BitConverter.GetBytes(parser.ReadInt());
|
||||
case (byte)ResponseMsgHeader.A2S_RULES: IsRuleChallengeId = false; return recvBytes;
|
||||
default: throw new InvalidHeaderException("A2S_SERVERQUERY_GETCHALLENGE message header is not valid");
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", recvBytes);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Listen to server logs.
|
||||
/// </summary>
|
||||
/// <param name="port">Local port</param>
|
||||
/// <returns>Instance of Log class</returns>
|
||||
/// <remarks>Receiver's socket address must be added to server's logaddress list before listening</remarks>
|
||||
public Logs GetLogs(int port)
|
||||
{
|
||||
logs = new Logs(Type, port, ServerEndPoint);
|
||||
return logs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets valid rcon object that can be used to send rcon commands to server
|
||||
/// </summary>
|
||||
/// <param name="pass">Rcon password of server</param>
|
||||
/// <returns>Instance of Rcon class</returns>
|
||||
public abstract Rcon GetControl(string pass);
|
||||
|
||||
/// <summary>
|
||||
/// Gets Round-trip delay time
|
||||
/// </summary>
|
||||
/// <returns>Elapsed milliseconds</returns>
|
||||
///
|
||||
public long Ping()
|
||||
{
|
||||
Stopwatch sw = null;
|
||||
if (IsObsolete)
|
||||
{
|
||||
sw = Stopwatch.StartNew();
|
||||
byte[] msg = socket.GetResponse(QueryMsg.ObsoletePingQuery, Type);
|
||||
sw.Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
sw = Stopwatch.StartNew();
|
||||
byte[] msg = socket.GetResponse(QueryMsg.InfoQuery, Type);
|
||||
sw.Stop();
|
||||
}
|
||||
return sw.ElapsedMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes all the resources used by the server object
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
if (socket != null)
|
||||
socket.Dispose();
|
||||
if (RConObj != null)
|
||||
RConObj.Dispose();
|
||||
if (logs != null)
|
||||
logs.Dispose();
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/QueryMaster/ServerQuery.cs
Normal file
88
src/QueryMaster/ServerQuery.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods to create Server instance
|
||||
/// </summary>
|
||||
public static class ServerQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns an object that represents the server
|
||||
/// </summary>
|
||||
/// <param name="type">Base engine which game uses</param>
|
||||
/// <param name="ip">IP-Address of server</param>
|
||||
/// <param name="port">Port number of server</param>
|
||||
/// <param name="isObsolete">Obsolete Gold Source servers reply only to half life protocol.if set to true then it would use half life protocol.If set to null,then protocol is identified at runtime[Default : false]</param>
|
||||
/// <param name="sendTimeOut">Sets Socket's SendTimeout Property.</param>
|
||||
/// <param name="receiveTimeOut">Sets Socket's ReceiveTimeout.</param>
|
||||
/// <returns>Instance of server class that represents the connected server.</returns>
|
||||
public static Server GetServerInstance(EngineType type, string ip, ushort port, bool? isObsolete = false, int sendTimeOut = 3000, int receiveTimeOut = 3000)
|
||||
{
|
||||
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(ip), (int)port);
|
||||
return GetServerInstance(type, endPoint, isObsolete, sendTimeOut, receiveTimeOut);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an object that represents the server
|
||||
/// </summary>
|
||||
/// <param name="type">Base engine which game uses</param>
|
||||
/// <param name="endPoint">Socket address of server</param>
|
||||
/// <param name="isObsolete">Obsolete Gold Source servers reply only to half life protocol.if set to true then it would use half life protocol.If set to null,then protocol is identified at runtime.</param>
|
||||
/// <param name="sendTimeOut">Sets Socket's SendTimeout Property.</param>
|
||||
/// <param name="receiveTimeOut">Sets Socket's ReceiveTimeout.</param>
|
||||
/// <returns>Instance of server class that represents the connected server</returns>
|
||||
public static Server GetServerInstance(EngineType type, IPEndPoint endPoint, bool? isObsolete = false, int sendTimeOut = 3000, int receiveTimeOut = 3000)
|
||||
{
|
||||
Server server = null;
|
||||
switch (type)
|
||||
{
|
||||
case EngineType.GoldSource: server = new GoldSource(endPoint, isObsolete, sendTimeOut, receiveTimeOut); break;
|
||||
case EngineType.Source: server = new Source(endPoint, sendTimeOut, receiveTimeOut); break;
|
||||
default: throw new ArgumentException("An invalid EngineType was specified.");
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an object that represents the server
|
||||
/// </summary>
|
||||
/// <param name="game">Name of game</param>
|
||||
/// <param name="endPoint">Socket address of server</param>
|
||||
/// <param name="isObsolete">Obsolete Gold Source servers reply only to half life protocol.if set to true then it would use half life protocol.If set to null,then protocol is identified at runtime.</param>
|
||||
/// <param name="sendTimeOut">Sets Socket's SendTimeout Property.</param>
|
||||
/// <param name="receiveTimeOut">Sets Socket's ReceiveTimeout.</param>
|
||||
/// <returns>Instance of server class that represents the connected server</returns>
|
||||
public static Server GetServerInstance(Game game, IPEndPoint endPoint, bool? isObsolete = false, int sendTimeOut = 3000, int receiveTimeOut = 3000)
|
||||
{
|
||||
if ((int)game <= 130)
|
||||
return GetServerInstance(EngineType.GoldSource, endPoint, isObsolete, sendTimeOut, receiveTimeOut);
|
||||
else
|
||||
return GetServerInstance(EngineType.Source, endPoint, isObsolete, sendTimeOut, receiveTimeOut);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an object that represents the server
|
||||
/// </summary>
|
||||
/// <param name="game">Name of game</param>
|
||||
/// <param name="ip">IP-Address of server</param>
|
||||
/// <param name="port">Port number of server</param>
|
||||
/// <param name="isObsolete">Obsolete Gold Source servers reply only to half life protocol.if set to true then it would use half life protocol.If set to null,then protocol is identified at runtime.</param>
|
||||
/// <param name="sendTimeOut">Sets Socket's SendTimeout Property.</param>
|
||||
/// <param name="receiveTimeOut">Sets Socket's ReceiveTimeout.</param>
|
||||
/// <returns>Instance of server class that represents the connected server</returns>
|
||||
public static Server GetServerInstance(Game game, string ip, ushort port, bool? isObsolete = false, int sendTimeOut = 3000, int receiveTimeOut = 3000)
|
||||
{
|
||||
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(ip), (int)port);
|
||||
if ((int)game <= 130)
|
||||
return GetServerInstance(EngineType.GoldSource, endPoint, isObsolete, sendTimeOut, receiveTimeOut);
|
||||
else
|
||||
return GetServerInstance(EngineType.Source, endPoint, isObsolete, sendTimeOut, receiveTimeOut);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
60
src/QueryMaster/ServerSocket.cs
Normal file
60
src/QueryMaster/ServerSocket.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
internal class ServerSocket : IDisposable
|
||||
{
|
||||
internal static readonly int UdpBufferSize = 1400;
|
||||
internal static readonly int TcpBufferSize = 4110;
|
||||
internal Socket socket { set; get; }
|
||||
protected internal int BufferSize = 0;
|
||||
internal IPEndPoint Address = null;
|
||||
protected bool IsDisposed;
|
||||
internal ServerSocket(SocketType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case SocketType.Tcp: socket = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, ProtocolType.Tcp); BufferSize = TcpBufferSize; break;
|
||||
case SocketType.Udp: socket = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, ProtocolType.Udp); BufferSize = UdpBufferSize; break;
|
||||
default: throw new ArgumentException("An invalid SocketType was specified.");
|
||||
}
|
||||
socket.SendTimeout = 3000;
|
||||
socket.ReceiveTimeout = 3000;
|
||||
|
||||
IsDisposed = false;
|
||||
}
|
||||
|
||||
internal void Connect(IPEndPoint address)
|
||||
{
|
||||
Address = address;
|
||||
socket.Connect(Address);
|
||||
}
|
||||
|
||||
internal int SendData(byte[] data)
|
||||
{
|
||||
return socket.Send(data);
|
||||
}
|
||||
|
||||
internal byte[] ReceiveData()
|
||||
{
|
||||
byte[] recvData = new byte[BufferSize];
|
||||
int recv = 0;
|
||||
recv = socket.Receive(recvData);
|
||||
return recvData.Take(recv).ToArray();
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
if (socket != null)
|
||||
socket.Close();
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/QueryMaster/Source.cs
Normal file
18
src/QueryMaster/Source.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
class Source : Server
|
||||
{
|
||||
internal Source(IPEndPoint address, int sendTimeOut, int receiveTimeOut) : base(address, EngineType.Source, false, sendTimeOut, receiveTimeOut) { }
|
||||
public override Rcon GetControl(string pass)
|
||||
{
|
||||
RConObj = RconSource.Authorize(socket.Address, pass);
|
||||
return RConObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/QueryMaster/TcpQuery.cs
Normal file
52
src/QueryMaster/TcpQuery.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
internal class TcpQuery : ServerSocket
|
||||
{
|
||||
|
||||
private byte[] EmptyPkt = new byte[] { 0x0a, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
internal TcpQuery(IPEndPoint address, int sendTimeOut, int receiveTimeOut)
|
||||
: base(SocketType.Tcp)
|
||||
{
|
||||
Connect(address);
|
||||
socket.SendTimeout = sendTimeOut;
|
||||
socket.ReceiveTimeout = receiveTimeOut;
|
||||
}
|
||||
|
||||
internal byte[] GetResponse(byte[] msg)
|
||||
{
|
||||
byte[] recvData;
|
||||
SendData(msg);
|
||||
recvData = ReceiveData();//Response value packet
|
||||
//recvData = ReceiveData();//Auth response packet
|
||||
return recvData;
|
||||
}
|
||||
|
||||
internal List<byte[]> GetMultiPacketResponse(byte[] msg)
|
||||
{
|
||||
List<byte[]> recvBytes = new List<byte[]>();
|
||||
//bool isRemaining = true;
|
||||
byte[] recvData;
|
||||
SendData(msg);
|
||||
//SendData(EmptyPkt);//Empty packet
|
||||
recvData = ReceiveData();//reply
|
||||
recvBytes.Add(recvData);
|
||||
#if false
|
||||
do
|
||||
{
|
||||
recvData = ReceiveData();//may or may not be an empty packet
|
||||
if (BitConverter.ToInt32(recvData, 4) == (int)PacketId.Empty)
|
||||
isRemaining = false;
|
||||
else
|
||||
recvBytes.Add(recvData);
|
||||
} while (isRemaining);
|
||||
#endif
|
||||
return recvBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
166
src/QueryMaster/UdpQuery.cs
Normal file
166
src/QueryMaster/UdpQuery.cs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using Ionic.BZip2;
|
||||
using System.Net;
|
||||
using Ionic.Crc;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
internal class UdpQuery : ServerSocket
|
||||
{
|
||||
private const int SinglePacket = -1;
|
||||
private const int MultiPacket = -2;
|
||||
private EngineType Type;
|
||||
internal UdpQuery(IPEndPoint address, int sendTimeOut, int receiveTimeOut)
|
||||
: base(SocketType.Udp)
|
||||
{
|
||||
Connect(address);
|
||||
socket.SendTimeout = sendTimeOut;
|
||||
socket.ReceiveTimeout = receiveTimeOut;
|
||||
}
|
||||
|
||||
internal byte[] GetResponse(byte[] msg, EngineType type)
|
||||
{
|
||||
Type = type;
|
||||
byte[] recvData;
|
||||
SendData(msg);
|
||||
recvData = ReceiveData();
|
||||
try
|
||||
{
|
||||
int header = BitConverter.ToInt32(recvData, 0);
|
||||
switch (header)
|
||||
{
|
||||
case SinglePacket: return ParseSinglePkt(recvData);
|
||||
case MultiPacket: return ParseMultiPkt(recvData);
|
||||
default: throw new InvalidHeaderException("Protocol header is not valid");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.Data.Add("ReceivedData", recvData);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] ParseSinglePkt(byte[] data)
|
||||
{
|
||||
return data.Skip(4).ToArray();
|
||||
}
|
||||
|
||||
private byte[] ParseMultiPkt(byte[] data)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case EngineType.Source: return SourcePackets(data);
|
||||
case EngineType.GoldSource: return GoldSourcePackets(data);
|
||||
default: throw new ArgumentException("An invalid EngineType was specified.");
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GoldSourcePackets(byte[] data)
|
||||
{
|
||||
var pktCount = data[8] & 0x0F;
|
||||
List<KeyValuePair<int, byte[]>> pktList = new List<KeyValuePair<int, byte[]>>(pktCount);
|
||||
pktList.Add(new KeyValuePair<int, byte[]>(data[8] >> 4, data));
|
||||
|
||||
byte[] recvData;
|
||||
for (int i = 1; i < pktCount; i++)
|
||||
{
|
||||
recvData = new byte[BufferSize];
|
||||
recvData = ReceiveData();
|
||||
pktList.Add(new KeyValuePair<int, byte[]>(recvData[8] >> 4, recvData));
|
||||
}
|
||||
|
||||
pktList.Sort((x, y) => x.Key.CompareTo(y.Key));
|
||||
List<byte> byteList = new List<byte>();
|
||||
byteList.AddRange(pktList[0].Value.Skip(13));
|
||||
|
||||
for (int i = 1; i < pktList.Count; i++)
|
||||
{
|
||||
byteList.AddRange(pktList[i].Value.Skip(9));
|
||||
}
|
||||
return byteList.ToArray<byte>();
|
||||
}
|
||||
|
||||
private byte[] SourcePackets(byte[] data)
|
||||
{
|
||||
byte pktCount = data[8];
|
||||
List<KeyValuePair<byte, byte[]>> pktList = new List<KeyValuePair<byte, byte[]>>(pktCount);
|
||||
pktList.Add(new KeyValuePair<byte, byte[]>(data[9], data));
|
||||
|
||||
byte[] recvData;
|
||||
for (int i = 1; i < pktCount; i++)
|
||||
{
|
||||
recvData = ReceiveData();
|
||||
pktList.Add(new KeyValuePair<byte, byte[]>(recvData[9], recvData));
|
||||
}
|
||||
|
||||
pktList.Sort((x, y) => x.Key.CompareTo(y.Key));
|
||||
Parser parser = null;
|
||||
bool isCompressed = false;
|
||||
int checksum = 0;
|
||||
List<byte> recvList = new List<byte>();
|
||||
parser = new Parser(pktList[0].Value);
|
||||
parser.Skip(4);//header
|
||||
if (parser.ReadInt() < 0)//ID
|
||||
isCompressed = true;
|
||||
parser.ReadByte();//total
|
||||
int pktId = parser.ReadByte();// packet id
|
||||
parser.ReadShort();//size
|
||||
if (isCompressed)
|
||||
{
|
||||
parser.Skip(2);//[this is not equal to decompressed length of data]
|
||||
checksum = parser.ReadInt();//Checksum
|
||||
}
|
||||
recvList.AddRange(parser.GetUnParsedData());
|
||||
|
||||
for (int i = 1; i < pktList.Count; i++)
|
||||
{
|
||||
parser = new Parser(pktList[i].Value);
|
||||
parser.Skip(12);//multipacket header only
|
||||
recvList.AddRange(parser.GetUnParsedData());
|
||||
}
|
||||
recvData = recvList.ToArray<byte>();
|
||||
if (isCompressed)
|
||||
{
|
||||
recvData = Decompress(recvData);
|
||||
if (!IsValid(recvData, checksum))
|
||||
throw new InvalidPacketException("packet's checksum value does not match with the calculated checksum");
|
||||
}
|
||||
return recvData.Skip(4).ToArray<byte>();
|
||||
}
|
||||
|
||||
private byte[] Decompress(byte[] data)
|
||||
{
|
||||
using (var input = new MemoryStream(data))
|
||||
using (var output = new MemoryStream())
|
||||
using (var unZip = new BZip2InputStream(input))
|
||||
{
|
||||
int ch = unZip.ReadByte();
|
||||
|
||||
while (ch != -1)
|
||||
{
|
||||
output.WriteByte((byte)ch);
|
||||
ch = unZip.ReadByte();
|
||||
}
|
||||
output.Flush();
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValid(byte[] data, int Checksum)
|
||||
{
|
||||
using (var Input = new MemoryStream(data))
|
||||
{
|
||||
if (Checksum == new CRC32().GetCrc32(Input))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
66
src/QueryMaster/Util.cs
Normal file
66
src/QueryMaster/Util.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace QueryMaster
|
||||
{
|
||||
static class Util
|
||||
{
|
||||
private static Dictionary<string, short> GoldSourceGames = new Dictionary<string, short>()
|
||||
{
|
||||
{"Counter-Strike",10},
|
||||
{ "Team Fortress Classic",20},
|
||||
{ "Day of Defeat",30},
|
||||
{ "Deathmatch Classic",40},
|
||||
{ "Opposing Force",50},
|
||||
{"Ricochet",60},
|
||||
{ "Half-Life",70},
|
||||
{ "Condition Zero",80},
|
||||
{ "Counter-Strike 1.6 dedicated server",90},
|
||||
{"Condition Zero Deleted Scenes",100},
|
||||
{"Half-Life: Blue Shift",130},
|
||||
};
|
||||
internal static short GetGameId(string name)
|
||||
{
|
||||
if (GoldSourceGames.ContainsKey(name))
|
||||
return GoldSourceGames[name];
|
||||
return 0;
|
||||
}
|
||||
internal static string BytesToString(byte[] bytes)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
|
||||
internal static string BytesToString(byte[] bytes, int index , int count )
|
||||
{
|
||||
return Encoding.UTF8.GetString(bytes, index, count);
|
||||
}
|
||||
|
||||
internal static byte[] StringToBytes(string str)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(str);
|
||||
}
|
||||
|
||||
internal static byte[] StringToBytes(string str, int index, int count)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(str.ToCharArray(), index, count);
|
||||
}
|
||||
|
||||
internal static byte[] MergeByteArrays(byte[] array1, byte[] array2)
|
||||
{
|
||||
byte[] newArray = new byte[array1.Length + array2.Length];
|
||||
Buffer.BlockCopy(array1, 0, newArray, 0, array1.Length);
|
||||
Buffer.BlockCopy(array2, 0, newArray, array1.Length, array2.Length);
|
||||
return newArray;
|
||||
}
|
||||
internal static byte[] MergeByteArrays(byte[] array1, byte[] array2, byte[] array3)
|
||||
{
|
||||
byte[] newArray = new byte[array1.Length + array2.Length + array3.Length];
|
||||
Buffer.BlockCopy(array1, 0, newArray, 0, array1.Length);
|
||||
Buffer.BlockCopy(array2, 0, newArray, array1.Length, array2.Length);
|
||||
Buffer.BlockCopy(array3, 0, newArray, array1.Length + array2.Length, array3.Length);
|
||||
return newArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue