source code checkin

This commit is contained in:
Brett Hewitson 2021-01-07 16:23:23 +10:00
parent 5f8fb2c825
commit 7e57b72e35
675 changed files with 168433 additions and 0 deletions

View 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
View 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
}
}

View 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);
}
}
}

View 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) { }
}
}

View 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;
}
}
}

View 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; }
}
}

View 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
View 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;
}
}
}

View 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;
}
}
}

View 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();
}
}
}

View 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
View 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();
}
}
}

View 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")]

View 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>

View 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
View 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();
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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; }
}
}

View 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
View 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;
}
}
}

View 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);
}
}
}

View 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
View 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;
}
}
}

View 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
View 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
View 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;
}
}
}