diff --git a/CSGSI.sln b/CSGSI.sln index 6a48b06..c1e13ce 100644 --- a/CSGSI.sln +++ b/CSGSI.sln @@ -1,6 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSGSI", "CSGSI\CSGSI.csproj", "{8D55804C-2CF1-4A65-9C3E-8E41CD809535}" EndProject Global diff --git a/CSGSI/CSGSI.csproj b/CSGSI/CSGSI.csproj index 5719041..5a4deaa 100644 --- a/CSGSI/CSGSI.csproj +++ b/CSGSI/CSGSI.csproj @@ -45,10 +45,19 @@ - - + + + + + + + + + + + + - diff --git a/CSGSI/GSIListener.cs b/CSGSI/GSIListener.cs deleted file mode 100644 index 98721cc..0000000 --- a/CSGSI/GSIListener.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Threading; -using System.Security.Principal; - -namespace CSGSI -{ - /// - /// A class that listens for HTTP POST requests and keeps track of previous game states - /// - public static class GSIListener - { - private const int MAX_GAMESTATES = 10; - - private static AutoResetEvent waitForConnection = new AutoResetEvent(false); - private static List GameStates = new List(); - - /// - /// The most recently received GameState object - /// - public static GameState CurrentGameState - { - get - { - if (GameStates.Count > 0) - return GameStates[GameStates.Count - 1]; - else - return null; - } - } - - private static int m_Port; - private static bool m_Running = false; - private static HttpListener listener; - - /// - /// Gets the port that is currently listening - /// - public static int Port { get { return m_Port; } } - - /// - /// Gets a bool determining if the listening process is running - /// - public static bool Running { get { return m_Running; } } - - /// - /// Occurs after a new GameState has been received - /// - public static event EventHandler NewGameState = delegate { }; - - /// - /// Starts listening for HTTP POST requests on the specified port - /// !!! Fails if the application is started without administrator privileges !!! - /// - /// The port to listen on - /// Returns true if the listener could be started, false otherwise - public static bool Start(int port) - { - if (!m_Running && UacHelper.IsProcessElevated) - { - m_Port = port; - listener = new HttpListener(); - listener.Prefixes.Add("http://127.0.0.1:" + port + "/"); - Thread listenerThread = new Thread(new ThreadStart(Run)); - m_Running = true; - listenerThread.Start(); - return true; - } - return false; - } - - /// - /// Stops listening for HTTP POST requests - /// - public static void Stop() - { - m_Running = false; - } - - private static void Run() - { - try - { - listener.Start(); - } - catch (HttpListenerException) - { - m_Running = false; - return; - } - while (m_Running) - { - listener.BeginGetContext(ReceiveGameState, listener); - waitForConnection.WaitOne(); - waitForConnection.Reset(); - } - listener.Stop(); - } - - private static void ReceiveGameState(IAsyncResult result) - { - HttpListenerContext context = listener.EndGetContext(result); - HttpListenerRequest request = context.Request; - string JSON; - - waitForConnection.Set(); - - using (Stream inputStream = request.InputStream) - { - using (StreamReader sr = new StreamReader(inputStream)) - { - JSON = sr.ReadToEnd(); - } - } - using (HttpListenerResponse response = context.Response) - { - response.StatusCode = (int)HttpStatusCode.OK; - response.StatusDescription = "OK"; - response.Close(); - } - - GameState gs = new GameState(JSON); - GameStates.Add(gs); - NewGameState(gs, EventArgs.Empty); - - while (GameStates.Count > MAX_GAMESTATES) - GameStates.RemoveAt(0); - } - } -} diff --git a/CSGSI/GameState.cs b/CSGSI/GameState.cs index 6045225..0895652 100644 --- a/CSGSI/GameState.cs +++ b/CSGSI/GameState.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using CSGSI.Nodes; namespace CSGSI { /// @@ -12,86 +13,132 @@ namespace CSGSI /// public class GameState { - private JObject m_Data; + private JObject _Data; - private GameStateNode m_Provider; - private GameStateNode m_Map; - private GameStateNode m_Round; - private GameStateNode m_Player; - private GameStateNode m_Auth; - private GameStateNode m_Added; - private GameStateNode m_Previously; + private ProviderNode _Provider; + private MapNode _Map; + private RoundNode _Round; + private PlayerNode _Player; + private AllPlayersNode _AllPlayers; + private GameState _Previously; + private GameState _Added; + private AuthNode _Auth; - /// - /// The "provider" subnode - /// - public GameStateNode Provider { get { return m_Provider; } } + public ProviderNode Provider + { + get + { + if (_Provider == null) + { + _Provider = new ProviderNode(_Data["provider"]?.ToString() ?? ""); + } - /// - /// The "map" subnode - /// - public GameStateNode Map { get { return m_Map; } } + return _Provider; + } + } + public MapNode Map + { + get + { + if (_Map == null) + { + _Map = new MapNode(_Data["map"]?.ToString() ?? ""); + } - /// - /// The "round" subnode - /// - public GameStateNode Round { get { return m_Round; } } + return _Map; + } + } + public RoundNode Round + { + get + { + if (_Round == null) + { + _Round = new RoundNode(_Data["round"]?.ToString() ?? ""); + } - /// - /// The "player" subnode - /// - public GameStateNode Player { get { return m_Player; } } + return _Round; + } + } + public PlayerNode Player + { + get + { + if (_Player == null) + { + _Player = new PlayerNode(_Data["player"]?.ToString() ?? ""); + } - /// - /// The "auth" subnode - /// - public GameStateNode Auth { get { return m_Auth; } } + return _Player; + } + } + public AllPlayersNode AllPlayers + { + get + { + if (_AllPlayers == null) + { + _AllPlayers = new AllPlayersNode(_Data["allplayers"]?.ToString() ?? ""); + } - /// - /// The "added" subnode - /// - public GameStateNode Added { get { return m_Added; } } + return _AllPlayers; + } + } + public GameState Previously + { + get + { + if (_Previously == null) + { + _Previously = new GameState(_Data["previously"]?.ToString() ?? ""); + } - /// - /// The "previously" subnode - /// - public GameStateNode Previously { get { return m_Previously; } } + return _Previously; + } + } + public GameState Added + { + get + { + if (_Added == null) + { + _Added = new GameState(_Data["added"]?.ToString() ?? ""); + } + return _Added; + } + } + public AuthNode Auth + { + get + { + if(_Auth == null) + { + _Auth = new AuthNode(_Data["auth"]?.ToString() ?? ""); + } + + return _Auth; + } + } - private string m_JSON; /// /// The JSON string that was used to generate this object /// - public string JSON { get { return m_JSON; } } - + public readonly string JSON; + /// /// Initialises a new GameState object using a JSON string /// /// public GameState(string JSONstring) { - m_JSON = JSONstring; + if(JSONstring.Equals("")) + { + JSONstring = "{}"; + } - if (!JSONstring.Equals("")) - m_Data = JObject.Parse(JSONstring); - - m_Provider = (HasRootNode("provider") ? new GameStateNode(m_Data["provider"]) : GameStateNode.Empty()); - m_Map = (HasRootNode("map") ? new GameStateNode(m_Data["map"]) : GameStateNode.Empty()); - m_Round = (HasRootNode("round") ? new GameStateNode(m_Data["round"]) : GameStateNode.Empty()); - m_Player = (HasRootNode("player") ? new GameStateNode(m_Data["player"]) : GameStateNode.Empty()); - m_Auth = (HasRootNode("auth") ? new GameStateNode(m_Data["auth"]) : GameStateNode.Empty()); - m_Added = (HasRootNode("added") ? new GameStateNode(m_Data["added"]) : GameStateNode.Empty()); - m_Previously = (HasRootNode("previously") ? new GameStateNode(m_Data["previously"]) : GameStateNode.Empty()); - } - - /// - /// Determines if the specified node exists in this GameState object - /// - /// - /// Returns true if the specified node exists, false otherwise - public bool HasRootNode(string rootnode) - { - return (m_Data != null && m_Data[rootnode] != null); + JSON = JSONstring; + _Data = JObject.Parse(JSONstring); } } } diff --git a/CSGSI/GameStateListener.cs b/CSGSI/GameStateListener.cs new file mode 100644 index 0000000..cc50d24 --- /dev/null +++ b/CSGSI/GameStateListener.cs @@ -0,0 +1,151 @@ +using System; +using System.IO; +using System.Net; +using System.Threading; +using System.Text.RegularExpressions; +using System.Diagnostics; + +namespace CSGSI +{ + public delegate void NewGameStateHandler(GameState gs); + + /// + /// A class that listens for HTTP POST requests + /// + public class GameStateListener + { + private AutoResetEvent waitForConnection = new AutoResetEvent(false); + private GameState m_CurrentGameState; + private int m_Port; + private bool m_Running = false; + private HttpListener m_Listener; + + /// + /// The most recently received GameState + /// + public GameState CurrentGameState + { + get + { + return m_CurrentGameState; + } + } + + /// + /// Gets the port that this GameStateListener instance is listening to + /// + public int Port { get { return m_Port; } } + + /// + /// Gets a value indicating if the listening process is running + /// + public bool Running { get { return m_Running; } } + + /// + /// Occurs after a new GameState has been received + /// + public event NewGameStateHandler NewGameState = delegate { }; + + /// + /// A GameStateListener that listens for connections to http://localhost:<Port>/ + /// + /// + public GameStateListener(int Port) + { + m_Port = Port; + m_Listener = new HttpListener(); + m_Listener.Prefixes.Add("http://localhost:" + Port + "/"); + } + + /// + /// A GameStateListener that listens for connections to the specified URI + /// + /// The URI to listen to + public GameStateListener(string URI) + { + if (!URI.EndsWith("/")) + URI += "/"; + + Regex URIPattern = new Regex("^https?:\\/\\/.+:([0-9]*)\\/$", RegexOptions.IgnoreCase); + Match PortMatch = URIPattern.Match(URI); + if (!PortMatch.Success) + { + throw new ArgumentException("Not a valid URI: " + URI); + } + m_Port = Convert.ToInt32(PortMatch.Groups[1].Value); + + m_Listener = new HttpListener(); + m_Listener.Prefixes.Add(URI); + } + + /// + /// Starts listening for HTTP POST requests on the specified port + /// + /// The port to listen on + /// Returns true on success + public bool Start() + { + if (!m_Running) + { + Thread m_ListenerThread = new Thread(new ThreadStart(Run)); + try + { + m_Listener.Start(); + } + catch (HttpListenerException) + { + return false; + } + m_Running = true; + m_ListenerThread.Start(); + return true; + } + + return false; + } + + /// + /// Stops listening for HTTP POST requests + /// + public void Stop() + { + m_Running = false; + } + + private void Run() + { + while (m_Running) + { + m_Listener.BeginGetContext(ReceiveGameState, m_Listener); + waitForConnection.WaitOne(); + waitForConnection.Reset(); + } + m_Listener.Stop(); + } + + private void ReceiveGameState(IAsyncResult result) + { + HttpListenerContext context = m_Listener.EndGetContext(result); + HttpListenerRequest request = context.Request; + string JSON; + + waitForConnection.Set(); + + using (Stream inputStream = request.InputStream) + { + using (StreamReader sr = new StreamReader(inputStream)) + { + JSON = sr.ReadToEnd(); + } + } + using (HttpListenerResponse response = context.Response) + { + response.StatusCode = (int)HttpStatusCode.OK; + response.StatusDescription = "OK"; + response.Close(); + } + m_CurrentGameState = new GameState(JSON); + NewGameState(m_CurrentGameState); + } + } +} diff --git a/CSGSI/GameStateNode.cs b/CSGSI/GameStateNode.cs deleted file mode 100644 index 433bc5d..0000000 --- a/CSGSI/GameStateNode.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json.Linq; - -namespace CSGSI -{ - /// - /// A sub node of a GameState object - /// - public class GameStateNode - { - private JToken m_Data; - - private GameStateNode() - { - m_Data = JToken.Parse("{}"); - } - - /// - /// Initializes a new GameStateNode using a JToken object - /// - /// - public GameStateNode(JToken node) - { - m_Data = node; - } - - /// - /// Get the value of a specific subnode of this GameStateNode - /// - /// The name of the subnode to get the value of - /// The string value of the specified subnode - public string GetValue(string node) - { - if (m_Data[node] == null) - return ""; - - return m_Data[node].ToString(); - } - - /// - /// Get a specific subnode as a new GameStateNode - /// - /// The name of the subnode - /// A new GameStateNode object containing the subnode - public GameStateNode GetNode(string node) - { - if (m_Data[node] == null) - return GameStateNode.Empty(); - - return new GameStateNode(m_Data[node]); - } - - /// - /// An empty GameStateNode to substitute for a null value - /// - /// - public static GameStateNode Empty() - { - return new GameStateNode(); - } - - /// - /// Get a specific subnode as a new GameStateNode - /// - /// The name of the subnode to get the value of - /// A new GameStateNode object containing the subnode - public GameStateNode this[string node] - { - get { return GetNode(node); } - } - } -} diff --git a/CSGSI/Nodes/AllPlayersNode.cs b/CSGSI/Nodes/AllPlayersNode.cs new file mode 100644 index 0000000..6b0d048 --- /dev/null +++ b/CSGSI/Nodes/AllPlayersNode.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +namespace CSGSI.Nodes +{ + public class AllPlayersNode : NodeBase + { + private readonly List _Players = new List(); + + public PlayerNode GetByName(string Name) + { + PlayerNode pn = _Players.Find(x => x.Name == Name); + if (pn != null) + return pn; + + return new PlayerNode(""); + } + + public PlayerNode GetBySteamID(string SteamID) + { + PlayerNode pn = _Players.Find(x => x.SteamID == SteamID); + if (pn != null) + return pn; + + return new PlayerNode(""); + } + + public int Count { get { return _Players.Count; } } + + internal AllPlayersNode(string JSON) + : base(JSON) + { + foreach (JToken jt in m_Data.Children()) + { + PlayerNode pn = new PlayerNode(jt.First.ToString()); + pn._SteamID = jt.Value()?.Name ?? ""; + _Players.Add(pn); + } + } + + /// + /// Gets the player with index <index> + /// + /// + /// + public PlayerNode this[int index] + { + get + { + if (index > _Players.Count - 1) + { + return new PlayerNode(""); + } + + return _Players[index]; + } + } + + public IEnumerator GetEnumerator() + { + return _Players.GetEnumerator(); + } + } +} diff --git a/CSGSI/Nodes/AuthNode.cs b/CSGSI/Nodes/AuthNode.cs new file mode 100644 index 0000000..614d11f --- /dev/null +++ b/CSGSI/Nodes/AuthNode.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CSGSI.Nodes +{ + public class AuthNode : NodeBase + { + public readonly string Token; + + internal AuthNode(string JSON) + : base(JSON) + { + Token = GetString("token"); + } + + } +} diff --git a/CSGSI/Nodes/MapNode.cs b/CSGSI/Nodes/MapNode.cs new file mode 100644 index 0000000..0a843f3 --- /dev/null +++ b/CSGSI/Nodes/MapNode.cs @@ -0,0 +1,48 @@ +using System; + +namespace CSGSI.Nodes +{ + public class MapNode : NodeBase + { + public readonly MapMode Mode; + public readonly string Name; + public readonly MapPhase Phase; + public readonly int Round; + public readonly int TeamCT; + public readonly int TeamT; + + internal MapNode(string JSON) + : base(JSON) + { + Mode = GetEnum("mode"); + Phase = GetEnum("phase"); + Round = GetInt32("round"); + } + } + + public enum MapPhase + { + Undefined, + Warmup, + Live, + Intermission, + GameOver + } + + public enum MapMode + { + Undefined, + Casual, + Competitive, + DeathMatch, + /// + /// Gun Game + /// + GunGameProgressive, + /// + /// Arms Race & Demolition + /// + GunGameTRBomb, + Custom + } +} diff --git a/CSGSI/Nodes/MatchStatsNode.cs b/CSGSI/Nodes/MatchStatsNode.cs new file mode 100644 index 0000000..2d38f78 --- /dev/null +++ b/CSGSI/Nodes/MatchStatsNode.cs @@ -0,0 +1,23 @@ +using System; + +namespace CSGSI.Nodes +{ + public class MatchStatsNode : NodeBase + { + public readonly int Kills; + public readonly int Assists; + public readonly int Deaths; + public readonly int MVPs; + public readonly int Score; + + internal MatchStatsNode(string JSON) + : base(JSON) + { + Kills = GetInt32("kills"); + Assists = GetInt32("assists"); + Deaths = GetInt32("deaths"); + MVPs = GetInt32("mvps"); + Score = GetInt32("score"); + } + } +} diff --git a/CSGSI/Nodes/NodeBase.cs b/CSGSI/Nodes/NodeBase.cs new file mode 100644 index 0000000..8ff571b --- /dev/null +++ b/CSGSI/Nodes/NodeBase.cs @@ -0,0 +1,46 @@ +using System; +using Newtonsoft.Json.Linq; + +namespace CSGSI.Nodes +{ + public class NodeBase + { + protected string m_JSON; + protected JObject m_Data; + + public string JSON + { + get { return m_JSON; } + } + + internal NodeBase(string JSON) + { + if (JSON.Equals("")) + { + JSON = "{}"; + } + m_Data = JObject.Parse(JSON); + m_JSON = JSON; + } + + internal string GetString(string Name) + { + return m_Data?[Name]?.ToString() ?? ""; + } + + internal int GetInt32(string Name) + { + return Convert.ToInt32(m_Data[Name]?.ToString() ?? "-1"); + } + + internal T GetEnum(string Name) + { + return (T)Enum.Parse(typeof(T), (m_Data[Name]?.ToString().Replace(" ", String.Empty) ?? "Undefined"), true); + } + + internal bool GetBool(string Name) + { + return m_Data?[Name]?.ToObject() ?? false; + } + } +} diff --git a/CSGSI/Nodes/PlayerNode.cs b/CSGSI/Nodes/PlayerNode.cs new file mode 100644 index 0000000..bfc753e --- /dev/null +++ b/CSGSI/Nodes/PlayerNode.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CSGSI.Nodes +{ + public class PlayerNode : NodeBase + { + internal string _SteamID; + public string SteamID { get { return _SteamID; } } + public readonly string Name; + public readonly string Team; + public readonly PlayerActivity Activity; + public readonly WeaponsNode Weapons; + public readonly MatchStatsNode MatchStats; + public readonly PlayerStateNode State; + + internal PlayerNode(string JSON) + : base(JSON) + { + _SteamID = GetString("steamid"); + Name = GetString("name"); + Team = GetString("team"); + State = new PlayerStateNode(m_Data?.SelectToken("state")?.ToString() ?? "{}"); + Weapons = new WeaponsNode(m_Data?.SelectToken("weapons")?.ToString() ?? "{}"); + MatchStats = new MatchStatsNode(m_Data?.SelectToken("match_stats")?.ToString() ?? "{}"); + Activity = GetEnum("activity"); + } + } + + public enum PlayerActivity + { + Undefined, + Menu, + Playing, + /// + /// Console is open + /// + TextInput + } +} diff --git a/CSGSI/Nodes/PlayerStateNode.cs b/CSGSI/Nodes/PlayerStateNode.cs new file mode 100644 index 0000000..be76b0a --- /dev/null +++ b/CSGSI/Nodes/PlayerStateNode.cs @@ -0,0 +1,31 @@ +using System; + +namespace CSGSI.Nodes +{ + public class PlayerStateNode : NodeBase + { + public readonly int Health; + public readonly int Armor; + public readonly bool Helmet; + public readonly int Flashed; + public readonly int Smoked; + public readonly int Burning; + public readonly int Money; + public readonly int RoundKills; + public readonly int RoundKillHS; + + internal PlayerStateNode(string JSON) + : base(JSON) + { + Health = GetInt32("health"); + Armor = GetInt32("armor"); + Helmet = GetBool("helmet"); + Flashed = GetInt32("flashed"); + Smoked = GetInt32("smoked"); + Burning = GetInt32("burning"); + Money = GetInt32("money"); + RoundKills = GetInt32("round_kills"); + RoundKillHS = GetInt32("round_killhs"); + } + } +} diff --git a/CSGSI/Nodes/ProviderNode.cs b/CSGSI/Nodes/ProviderNode.cs new file mode 100644 index 0000000..2f6416d --- /dev/null +++ b/CSGSI/Nodes/ProviderNode.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CSGSI.Nodes +{ + public class ProviderNode : NodeBase + { + public readonly string Name; + public readonly int AppID; + public readonly int Version; + public readonly string SteamID; + public readonly string TimeStamp; + + internal ProviderNode(string JSON) + : base(JSON) + { + Name = GetString("name"); + AppID = GetInt32("appid"); + Version = GetInt32("version"); + SteamID = GetString("steamid"); + TimeStamp = GetString("timestamp"); + } + } +} diff --git a/CSGSI/Nodes/RoundNode.cs b/CSGSI/Nodes/RoundNode.cs new file mode 100644 index 0000000..9561327 --- /dev/null +++ b/CSGSI/Nodes/RoundNode.cs @@ -0,0 +1,42 @@ +using System; + +namespace CSGSI.Nodes +{ + public class RoundNode : NodeBase + { + public readonly RoundPhase Phase; + public readonly BombState Bomb; + public readonly RoundWinTeam WinTeam; + + internal RoundNode(string JSON) + : base(JSON) + { + Phase = GetEnum("phase"); + Bomb = GetEnum("bomb"); + WinTeam = GetEnum("win_team"); + } + } + + public enum RoundPhase + { + Undefined, + Live, + Over, + FreezeTime + } + + public enum BombState + { + Undefined, + Planted, + Exploded, + Defused + } + + public enum RoundWinTeam + { + Undefined, + T, + CT + } +} diff --git a/CSGSI/Nodes/WeaponNode.cs b/CSGSI/Nodes/WeaponNode.cs new file mode 100644 index 0000000..343fe51 --- /dev/null +++ b/CSGSI/Nodes/WeaponNode.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CSGSI.Nodes +{ + public class WeaponNode : NodeBase + { + public readonly string Name; + public readonly string Paintkit; + public readonly WeaponType Type; + public readonly int AmmoClip; + public readonly int AmmoClipMax; + public readonly int AmmoReserve; + public readonly WeaponState State; + + internal WeaponNode(string JSON) + : base(JSON) + { + Name = GetString("name"); + Paintkit = GetString("paintkit"); + Type = GetEnum("type"); + AmmoClip = GetInt32("ammo_clip"); + AmmoClipMax = GetInt32("ammo_clip_max"); + AmmoReserve = GetInt32("ammo_clip_reserve"); + State = GetEnum("state"); + } + } + + public enum WeaponType + { + Undefined, + Rifle, + SniperRifle, + SubmachineGun, + Shotgun, + MachineGun, + Pistol, + Knife, + Grenade, + C4 + } + + public enum WeaponState + { + Undefined, + Active, + Holstered, + Reloading + } +} diff --git a/CSGSI/Nodes/WeaponsNode.cs b/CSGSI/Nodes/WeaponsNode.cs new file mode 100644 index 0000000..174abe8 --- /dev/null +++ b/CSGSI/Nodes/WeaponsNode.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using System.Linq; + +using System; + +namespace CSGSI.Nodes +{ + public class WeaponsNode : NodeBase + { + private List _Weapons = new List(); + + public int Count { get { return _Weapons.Count; } } + public WeaponNode ActiveWeapon + { + get + { + foreach(WeaponNode w in _Weapons) + { + if (w.State == WeaponState.Active || w.State == WeaponState.Reloading) + return w; + } + + return new WeaponNode(""); + } + } + + internal WeaponsNode(string JSON) + : base(JSON) + { + foreach(JToken jt in m_Data.Children()) + { + _Weapons.Add(new WeaponNode(jt.First.ToString())); + } + } + + /// + /// Gets the weapon with index <index> + /// + /// + /// + public WeaponNode this[int index] + { + get + { + if (index > _Weapons.Count - 1) + { + return new WeaponNode(""); + } + + return _Weapons[index]; + } + } + } +} diff --git a/CSGSI/UacHelper.cs b/CSGSI/UacHelper.cs deleted file mode 100644 index 9be9e60..0000000 --- a/CSGSI/UacHelper.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Microsoft.Win32; -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Security.Principal; - -namespace CSGSI -{ - //stolen in it's entirety from http://stackoverflow.com/a/4497572 :O - //this class is necessary since HttpListener.Start() will throw an exception if the application isn't started with admin privileges - internal static class UacHelper - { - private const string uacRegistryKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System"; - private const string uacRegistryValue = "EnableLUA"; - - private static uint STANDARD_RIGHTS_READ = 0x00020000; - private static uint TOKEN_QUERY = 0x0008; - private static uint TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY); - - [DllImport("advapi32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle); - - [DllImport("advapi32.dll", SetLastError = true)] - public static extern bool GetTokenInformation(IntPtr TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, IntPtr TokenInformation, uint TokenInformationLength, out uint ReturnLength); - - public enum TOKEN_INFORMATION_CLASS - { - TokenUser = 1, - TokenGroups, - TokenPrivileges, - TokenOwner, - TokenPrimaryGroup, - TokenDefaultDacl, - TokenSource, - TokenType, - TokenImpersonationLevel, - TokenStatistics, - TokenRestrictedSids, - TokenSessionId, - TokenGroupsAndPrivileges, - TokenSessionReference, - TokenSandBoxInert, - TokenAuditPolicy, - TokenOrigin, - TokenElevationType, - TokenLinkedToken, - TokenElevation, - TokenHasRestrictions, - TokenAccessInformation, - TokenVirtualizationAllowed, - TokenVirtualizationEnabled, - TokenIntegrityLevel, - TokenUIAccess, - TokenMandatoryPolicy, - TokenLogonSid, - MaxTokenInfoClass - } - - public enum TOKEN_ELEVATION_TYPE - { - TokenElevationTypeDefault = 1, - TokenElevationTypeFull, - TokenElevationTypeLimited - } - - public static bool IsUacEnabled - { - get - { - RegistryKey uacKey = Registry.LocalMachine.OpenSubKey(uacRegistryKey, false); - bool result = uacKey.GetValue(uacRegistryValue).Equals(1); - return result; - } - } - - public static bool IsProcessElevated - { - get - { - if (IsUacEnabled) - { - IntPtr tokenHandle; - if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_READ, out tokenHandle)) - { - throw new ApplicationException("Could not get process token. Win32 Error Code: " + Marshal.GetLastWin32Error()); - } - - TOKEN_ELEVATION_TYPE elevationResult = TOKEN_ELEVATION_TYPE.TokenElevationTypeDefault; - - int elevationResultSize = Marshal.SizeOf((int)elevationResult); - uint returnedSize = 0; - IntPtr elevationTypePtr = Marshal.AllocHGlobal(elevationResultSize); - - bool success = GetTokenInformation(tokenHandle, TOKEN_INFORMATION_CLASS.TokenElevationType, elevationTypePtr, (uint)elevationResultSize, out returnedSize); - if (success) - { - elevationResult = (TOKEN_ELEVATION_TYPE)Marshal.ReadInt32(elevationTypePtr); - bool isProcessAdmin = elevationResult == TOKEN_ELEVATION_TYPE.TokenElevationTypeFull; - return isProcessAdmin; - } - else - { - throw new ApplicationException("Unable to determine the current elevation."); - } - } - else - { - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new WindowsPrincipal(identity); - bool result = principal.IsInRole(WindowsBuiltInRole.Administrator); - return result; - } - } - } - } -} diff --git a/README.md b/README.md index f084006..20ef11a 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,83 @@ # CSGSI A simple C# library to interface with Counter-Strike: Global Offensive *Game State Integration* -# **!! Development ceased indefinitely !!** -#### If you're looking for an actively developed alternative, see [master117's library](https://bitbucket.org/master117/csgogameobserversdk). - -

+## Table of Contents +[What is Game State Integration](#whatis) +[About CSGSI](#about) +[Installation](#installation) +[Usage](#usage) +[Layout](#layout) +[Null value handling](#nullvaluehandling) +[Example program](#example) + +
+ ## What is Game State Integration - + [This wiki page by Valve](https://developer.valvesoftware.com/wiki/Counter-Strike:_Global_Offensive_Game_State_Integration) explains the concept of GSI. + +## About CSGSI + +This library provides means to listen to a specific port for the game clients HTTP POST request. Once a request is received, the game state is parsed and can then be analyzed. + +CSGSI uses Newtonsoft's [JSON.Net Framework](http://www.newtonsoft.com/json) to parse JSON. + +Once a `GameStateListener` instance is running, it continuously listens for incoming HTTP requests. +Every time a request is received, it's JSON content is used to construct a new `GameState` object. +The `GameState` class represents the entire game state as it was sent by the client. +It also provides access to all rootnodes (see Usage). + + ## Installation + 1. Get the [latest binaries](https://github.com/rakijah/CSGSI/releases/latest) 2. Get the [JSON Framework .dll by Newtonsoft](https://github.com/JamesNK/Newtonsoft.Json/releases) 3. Extract Newtonsoft.Json.dll from `Bin\Net45\Newtonsoft.Json.dll` 4. Add a reference to both CSGSI.dll and Newtonsoft.Json.dll in your project -## About CSGSI - -This library provides means to listen to a specific port for the game clients HTTP POST request. Once a request is received, the game state is parsed and can then be analyzed to display the players' current condition, create bomb timers etc... + +## Usage + -CSGSI uses Newtonsoft's [JSON.Net Framework](http://www.newtonsoft.com/json) to parse JSON. +Create a `GameStateListener` instance by providing a port or passing a specific URI: -The main class `GSIListener` continuously listens for incoming HTTP requests. -Once a request is made, it's JSON content is used to construct a new `GameState` object. -The `GameState` class represents the entire game state as it was sent by the client. -It also provides access to all rootnodes (see Usage). -These rootnodes and every subnode of them are of type `GameStateNode`, which encapsulates the underlying JSON data. - -## Usage + GameStateListener gsl = new GameStateListener(3000); //http://localhost:3000/ + GameStateListener gsl = new GameStateListener("http://127.0.0.1:81/"); -Use `GSIListener.Start(int port)` to start listening for HTTP POST requests from the game client. This method will return *false* if starting the listener fails. +**Please note**: If your application needs to listen to a URI other than `http://localhost:*/` (for example `http://192.168.2.2:100/`), you need to ensure that it is run with administrator privileges. +In this case, `http://127.0.0.1:*/` is **not** equivalent to `http://localhost:*/`. -**!! This step fails if your application is not run with administrator privileges !!** -**Listening to a URI/port is not possible with regular privileges** +Use `GameStateListener.Start()` to start listening for HTTP POST requests from the game client. This method will return `false` if starting the listener fails (most likely due to insufficient privileges). -The GSIListener class provides the `NewGameState` event that occurs after a new game state has been received: +The GameStateListener class provides the `NewGameState` event that occurs after a new game state has been received: ``` -GSIListener.NewGameState += new EventHandler(OnNewGameState); -... +gsl.NewGameState += new EventHandler(OnNewGameState); -private static void OnNewGameState(object sender, EventArgs e) +private static void OnNewGameState(GameState gs) { - GameState gs = (GameState) sender; //the newest GameState - //... + //gs contains the current GameState } ``` -The GameState object provides access to these rootnodes that may be transmitted by the game: +The `GameState` object provides access to these rootnodes that may be transmitted by the game: * gs.Provider * gs.Map * gs.Round * gs.Player +* gs.AllPlayers * gs.Auth * gs.Added * gs.Previously -The rootnodes `allplayers_*` have not yet been implemented and `GameState` currently lacks a `GetRootNode` method (even though `HasRootNode` already exists). - -##### Example: - -``` -GameStateNode playerNode = gs.Player; -``` - -You can then access further subnodes by calling `playerNode.GetNode(string node);` (returns a `GameStateNode` object). -You can retrieve a nodes value (as a string) by calling `playerNode.GetValue(string node);` - ##### Example: ``` -playerNode.GetNode("state").GetValue("health"); +PlayerNode player = gs.Player; +int Health = player.State.Health; //100 -playerNode.GetNode("weapons").GetValue("weapon_0"); +string activeWep = player.Weapons.ActiveWeapon.JSON //{ // "name": "weapon_knife", // "paintkit": ... @@ -80,43 +85,129 @@ playerNode.GetNode("weapons").GetValue("weapon_0"); //} ``` -If the node you are trying to access using `GetValue` does not exist, it returns an empty string (`""`). + +## Layout + +``` +GameState + .Provider + .Name + .AppID + .Version + .SteamID + .TimeStamp + .Map + .Mode + .Name + .Phase + .Round + .TeamCT + .TeamT + .Round + .Phase + .Bomb + .WinTeam + .Player + .SteamID + .Name + .Team + .Activity + .Weapons + .ActiveWeapon + [] + .Name + .Paintkit + .Type + .AmmoClip + .AmmoClipMax + .AmmoReserve + .State + MatchStats + .Kills + .Assists + .Deaths + .MVPs + .Score + State + .Health + .Armor + .Helmet + .Flashed + .Smoked + .Burning + .Money + .RoundKills + .RoundKillHS + .AllPlayers + [] + Player + .Previously + GameState + .Added + GameState + .Auth + .Token +``` + +##### Examples: +`gs.Player.Weapons.ActiveWeapon.AmmoClip` -## Example program +`gs.AllPlayers[0].State.Health` + + +## Null value handling + + +In case the JSON did not contain the requested information, these values will be returned: + +Type|Default value +----|------------- +int|-1 +string| String.Empty -This snippet will start listening for HTTP POST requests and print out the transmitted JSON data: +All Enums have a value `enum.Undefined` that serves the same purpose. + + +## Example program + +Prints "Bomb has been planted", every time you plant the bomb: ``` using System; using CSGSI; +using CSGSI.Nodes; -namespace GSI_Test +namespace CSGSI_Test { class Program { + static GameStateListener gsl; static void Main(string[] args) { - //subscribe to the NewGameState event - GSIListener.NewGameState += new EventHandler(OnNewGameState); - - //start listening on http://127.0.0.1:3000/ - if (GSIListener.Start(3000)) + gsl = new GameStateListener(3000); + gsl.NewGameState += new NewGameStateHandler(OnNewGameState); + if (!gsl.Start()) { - Console.WriteLine("Listening..."); - } - else - { - Console.WriteLine("Error starting GSIListener."); + Environment.Exit(0); } + Console.WriteLine("Listening..."); } - private static void OnNewGameState(object sender, EventArgs e) + static bool IsPlanted = false; + + static void OnNewGameState(GameState gs) { - //the newest GameState object is provided as the sender - GameState gs = (GameState) sender; - - //gs.JSON returns the JSON string that was used to create this GameState object - Console.WriteLine(gs.JSON); + if(!IsPlanted && + gs.Round.Phase == RoundPhase.Live && + gs.Round.Bomb == BombState.Planted && + gs.Previously.Round.Bomb == BombState.Undefined) + { + Console.WriteLine("Bomb has been planted."); + IsPlanted = true; + }else if(IsPlanted && gs.Round.Phase == RoundPhase.FreezeTime) + { + IsPlanted = false; + } } } } @@ -125,24 +216,28 @@ namespace GSI_Test You will also need to create a custom `gamestate_integration_*.cfg` in `/csgo/cfg`, for example: `gamestate_integration_test.cfg`: ``` -"CSGSI Test" +"CSGSI Example" { - "uri" "http://127.0.0.1:3000" - "timeout" "5.0" - "buffer" "0.1" - "throttle" "0.5" - "heartbeat" "60.0" - "data" - { - "provider" "1" - "map" "1" - "round" "1" - "player_id" "1" - "player_weapons" "1" - "player_match_stats" "1" - "player_state" "1" - } + "uri" "http://localhost:3000" + "timeout" "5.0" + "auth" + { + "token" "CSGSI Test" + } + "data" + { + "provider" "1" + "map" "1" + "round" "1" + "player_id" "1" + "player_weapons" "1" + "player_match_stats" "1" + "player_state" "1" + "allplayers_id" "1" + "allplayers_state" "1" + "allplayers_match_stats" "1" + } } ``` -After starting the application and the game, this setup should output something like this: -![output example](http://i.imgur.com/baTaI0a.png) \ No newline at end of file + +**Please note**: In order to run this test application without explicit administrator privileges, you need to use the URI `http://localhost:` in this configuration file. diff --git a/old readmes/1.0.md b/old readmes/1.0.md new file mode 100644 index 0000000..f084006 --- /dev/null +++ b/old readmes/1.0.md @@ -0,0 +1,148 @@ +# CSGSI +A simple C# library to interface with Counter-Strike: Global Offensive *Game State Integration* + +# **!! Development ceased indefinitely !!** +#### If you're looking for an actively developed alternative, see [master117's library](https://bitbucket.org/master117/csgogameobserversdk). + +

+## What is Game State Integration + +[This wiki page by Valve](https://developer.valvesoftware.com/wiki/Counter-Strike:_Global_Offensive_Game_State_Integration) explains the concept of GSI. + +## Installation +1. Get the [latest binaries](https://github.com/rakijah/CSGSI/releases/latest) +2. Get the [JSON Framework .dll by Newtonsoft](https://github.com/JamesNK/Newtonsoft.Json/releases) +3. Extract Newtonsoft.Json.dll from `Bin\Net45\Newtonsoft.Json.dll` +4. Add a reference to both CSGSI.dll and Newtonsoft.Json.dll in your project + +## About CSGSI + +This library provides means to listen to a specific port for the game clients HTTP POST request. Once a request is received, the game state is parsed and can then be analyzed to display the players' current condition, create bomb timers etc... + +CSGSI uses Newtonsoft's [JSON.Net Framework](http://www.newtonsoft.com/json) to parse JSON. + +The main class `GSIListener` continuously listens for incoming HTTP requests. +Once a request is made, it's JSON content is used to construct a new `GameState` object. +The `GameState` class represents the entire game state as it was sent by the client. +It also provides access to all rootnodes (see Usage). +These rootnodes and every subnode of them are of type `GameStateNode`, which encapsulates the underlying JSON data. + +## Usage + +Use `GSIListener.Start(int port)` to start listening for HTTP POST requests from the game client. This method will return *false* if starting the listener fails. + +**!! This step fails if your application is not run with administrator privileges !!** +**Listening to a URI/port is not possible with regular privileges** + +The GSIListener class provides the `NewGameState` event that occurs after a new game state has been received: +``` +GSIListener.NewGameState += new EventHandler(OnNewGameState); +... + +private static void OnNewGameState(object sender, EventArgs e) +{ + GameState gs = (GameState) sender; //the newest GameState + //... +} +``` + +The GameState object provides access to these rootnodes that may be transmitted by the game: + +* gs.Provider +* gs.Map +* gs.Round +* gs.Player +* gs.Auth +* gs.Added +* gs.Previously + +The rootnodes `allplayers_*` have not yet been implemented and `GameState` currently lacks a `GetRootNode` method (even though `HasRootNode` already exists). + +##### Example: + +``` +GameStateNode playerNode = gs.Player; +``` + +You can then access further subnodes by calling `playerNode.GetNode(string node);` (returns a `GameStateNode` object). +You can retrieve a nodes value (as a string) by calling `playerNode.GetValue(string node);` + +##### Example: +``` +playerNode.GetNode("state").GetValue("health"); +//100 + +playerNode.GetNode("weapons").GetValue("weapon_0"); +//{ +// "name": "weapon_knife", +// "paintkit": ... +// ... +//} +``` + +If the node you are trying to access using `GetValue` does not exist, it returns an empty string (`""`). + +## Example program + +This snippet will start listening for HTTP POST requests and print out the transmitted JSON data: + +``` +using System; +using CSGSI; + +namespace GSI_Test +{ + class Program + { + static void Main(string[] args) + { + //subscribe to the NewGameState event + GSIListener.NewGameState += new EventHandler(OnNewGameState); + + //start listening on http://127.0.0.1:3000/ + if (GSIListener.Start(3000)) + { + Console.WriteLine("Listening..."); + } + else + { + Console.WriteLine("Error starting GSIListener."); + } + } + + private static void OnNewGameState(object sender, EventArgs e) + { + //the newest GameState object is provided as the sender + GameState gs = (GameState) sender; + + //gs.JSON returns the JSON string that was used to create this GameState object + Console.WriteLine(gs.JSON); + } + } +} +``` + +You will also need to create a custom `gamestate_integration_*.cfg` in `/csgo/cfg`, for example: +`gamestate_integration_test.cfg`: +``` +"CSGSI Test" +{ + "uri" "http://127.0.0.1:3000" + "timeout" "5.0" + "buffer" "0.1" + "throttle" "0.5" + "heartbeat" "60.0" + "data" + { + "provider" "1" + "map" "1" + "round" "1" + "player_id" "1" + "player_weapons" "1" + "player_match_stats" "1" + "player_state" "1" + } +} +``` +After starting the application and the game, this setup should output something like this: +![output example](http://i.imgur.com/baTaI0a.png) \ No newline at end of file