diff --git a/Admin/IW4M ADMIN.csproj b/Admin/IW4M ADMIN.csproj index ebbc76fdf..07aee0cfe 100644 --- a/Admin/IW4M ADMIN.csproj +++ b/Admin/IW4M ADMIN.csproj @@ -43,6 +43,7 @@ DEBUG;TRACE prompt 4 + true x86 @@ -52,6 +53,7 @@ TRACE prompt 0 + true LocalIntranet @@ -108,6 +110,7 @@ + diff --git a/Admin/IW4_GameStructs.cs b/Admin/IW4_GameStructs.cs new file mode 100644 index 000000000..7811ef54d --- /dev/null +++ b/Admin/IW4_GameStructs.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + + +namespace IW4MAdmin +{ + [StructLayout(LayoutKind.Explicit)] + public struct netadr_t + { + [FieldOffset(0x0)] + Int32 type; + + [FieldOffset(0x4)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public Byte[] ip; + + [FieldOffset(0x8)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + public Byte[] ipx; + + [FieldOffset(0x12)] + public short port; + } + + [StructLayout(LayoutKind.Explicit)] + public struct client_s + { + [FieldOffset(0x0)] + public Int32 state; + + [FieldOffset(0x28)] + public netadr_t adr; + + [FieldOffset(0x65C)] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] + public String connectInfoString; + + [FieldOffset(0x20EA4)] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst=400)] // doubt this is the correct size + public String lastUserCmd; + + [FieldOffset(0x212A4)] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] + public String name; + + [FieldOffset(0x212C0)] + public int snapNum; + + [FieldOffset(0x212C8)] + public short ping; + + [FieldOffset(0x41AF0)] + public int isBot; + + [FieldOffset(0x43F00)] + public UInt64 steamid; + }; + + [StructLayout(LayoutKind.Explicit)] + public struct dvar_t + { + [FieldOffset(0)] + public IntPtr name; + + [FieldOffset(4)] + public IntPtr description; + + [FieldOffset(8)] + public uint flags; + + [FieldOffset(12)] + short type; + + [FieldOffset(16)] + public IntPtr current; + + [FieldOffset(32)] + public IntPtr latched; + + [FieldOffset(48)] + public IntPtr _default; + + [FieldOffset(65)] + public IntPtr min; + + [FieldOffset(68)] + public IntPtr max; + } + + // not iw4 + public struct dvar + { + public String name; + + public String description; + + public int flags; + + short type; + + public String current; + + public String latched; + + public String _default; + + public int min; + + public int max; + } + + + + class Helpers + { + public static String NET_AdrToString(netadr_t a) + { + // not worrying about NA_TYPE + StringBuilder s = new StringBuilder(64); + s.AppendFormat("{0}.{1}.{2}.{3}:{4}", a.ip[0], a.ip[1], a.ip[2], a.ip[3], a.port); + return s.ToString(); + } + + public static unsafe T ReadStruct(byte[] buffer) where T : struct + { + fixed (byte* b = buffer) + return (T)Marshal.PtrToStructure(new IntPtr(b), typeof(T)); + } + } + +} diff --git a/Admin/Main.cs b/Admin/Main.cs index fa5b80f2c..042f8d7c5 100644 --- a/Admin/Main.cs +++ b/Admin/Main.cs @@ -1,7 +1,11 @@ -using System; +#define USINGMEMORY +using System; using System.Collections.Generic; using System.Text; using System.Threading; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Net; namespace IW4MAdmin { @@ -12,7 +16,8 @@ namespace IW4MAdmin static String RCON; static public double Version = 0.9; static public double latestVersion; - static public List Servers;// + static public List Servers; + static public bool usingMemory = true; static void Main(string[] args) { @@ -26,8 +31,14 @@ namespace IW4MAdmin Console.WriteLine(" Version " + Version + " (unable to retrieve latest)"); Console.WriteLine("====================================================="); - Servers = checkConfig(); - foreach (Server IW4M in Servers) + List viableServers = getServers(); + + if (viableServers.Count < 1) + viableServers = checkConfig(); // fall back to config + + Servers = viableServers; + + foreach (Server IW4M in viableServers) { //Threading seems best here Server SV = IW4M; @@ -88,7 +99,7 @@ namespace IW4MAdmin Config = new file("config\\servers.cfg", true); Config.Write(IP + ':' + Port + ':' + RCON); Config.Close(); - Servers.Add(new Server(IP, Port, RCON)); + Servers.Add(new Server(IP, Port, RCON, 0)); } else @@ -102,11 +113,43 @@ namespace IW4MAdmin Console.WriteLine("You have an error in your server.cfg"); continue; } - Servers.Add(new Server(server_line[0], newPort, server_line[2])); + Servers.Add(new Server(server_line[0], newPort, server_line[2],0)); } } return Servers; } + + [DllImport("kernel32.dll")] + public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); + + [DllImport("kernel32.dll")] + public static extern bool ReadProcessMemory(int hProcess, + int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead); + + static List getServers() + { + List Servers = new List(); + foreach ( Process P in Process.GetProcessesByName("iw4m")) + { + IntPtr Handle = OpenProcess(0x0010, false, P.Id); + int numberRead = 0; + Byte[] dediStuff = new Byte[1]; + ReadProcessMemory((int)Handle, 0x5DEC04, dediStuff, 1, ref numberRead); + + if (dediStuff[0] == 0) + { + Console.WriteLine("Viable IW4M Instance found with PID #" + P.Id); + + dvar net_ip = Utilities.getDvar(0x64A1DF8, (int)Handle); + dvar net_port = Utilities.getDvar(0x64A3004, (int)Handle); + dvar rcon_password = Utilities.getDvar(0x111FF634, (int)Handle); + + Servers.Add(new Server(Dns.GetHostAddresses(net_ip.current)[1].ToString(), Convert.ToInt32(net_port.current), rcon_password.current, (int)Handle)); + } + } + return Servers; + + } } } diff --git a/Admin/Server.cs b/Admin/Server.cs index 72193231e..e0aa65919 100644 --- a/Admin/Server.cs +++ b/Admin/Server.cs @@ -1,10 +1,12 @@ - +//#define USINGMEMORY using System; using System.Collections.Generic; using System.Collections; using System.Text; using System.Threading; //SLEEP using System.IO; +using System.Diagnostics; +using System.Runtime.InteropServices; namespace IW4MAdmin @@ -13,8 +15,9 @@ namespace IW4MAdmin { const int FLOOD_TIMEOUT = 300; - public Server(string address, int port, string password) + public Server(string address, int port, string password, int H) { + Handle = H; IP = address; Port = port; rcon_pass = password; @@ -334,7 +337,7 @@ namespace IW4MAdmin { if (players[cNum] == null) { - Log.Write("Error - Disconnecting client slot is already empty!", Log.Level.Debug); + //Log.Write("Error - Disconnecting client slot is already empty!", Log.Level.Debug); return false; } @@ -509,7 +512,7 @@ namespace IW4MAdmin } - //process new event every 100 milliseconds + //process new event every 50 milliseconds private void manageEventQueue() { while (isRunning) @@ -519,10 +522,16 @@ namespace IW4MAdmin processEvent(events.Peek()); events.Dequeue(); } - Utilities.Wait(0.1); + Utilities.Wait(0.05); } } + [DllImport("kernel32.dll")] + public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); + + [DllImport("kernel32.dll")] + public static extern bool ReadProcessMemory(int hProcess, int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead); + //Starts the monitoring process public void Monitor() { @@ -551,11 +560,11 @@ namespace IW4MAdmin Utilities.Wait(10); return; } - +#if DEBUG == false //Thread to handle polling server for IP's Thread statusUpdate = new Thread(new ThreadStart(pollServer)); statusUpdate.Start(); - +#endif //Handles new events in a fashionable manner Thread eventQueue = new Thread(new ThreadStart(manageEventQueue)); eventQueue.Start(); @@ -576,6 +585,8 @@ namespace IW4MAdmin while (isRunning) { + + #if DEBUG == false try #endif @@ -613,6 +624,27 @@ namespace IW4MAdmin } } +#if DEBUG + if ((DateTime.Now - lastPoll).Milliseconds > 750) + { + int numberRead = 0; + + for (int i = 0; i < players.Count; i++) + { + Byte[] buff = new Byte[681872]; // struct size ( 0.68MB :( ) + ReadProcessMemory((int)Handle, 0x31D9390 + (buff.Length)*(i), buff, buff.Length, ref numberRead); // svs_clients start + current client + + client_s eachClient = (client_s)Helpers.ReadStruct(buff); + + if (eachClient.state == 0) + removePlayer(i); + else if (eachClient.state > 1) + addPlayer(new Player(eachClient.name, eachClient.steamid.ToString("x16"), i, 0, i, null, 0, Helpers.NET_AdrToString(eachClient.adr).Split(':')[0])); + } + + lastPoll = DateTime.Now; + } +#endif if (l_size != logFile.getSize()) { @@ -658,7 +690,7 @@ namespace IW4MAdmin } oldLines = lines; l_size = logFile.getSize(); - Thread.Sleep(1); + Thread.Sleep(250); } #if DEBUG == false catch (Exception E) @@ -672,7 +704,6 @@ namespace IW4MAdmin isRunning = false; RCONQueue.Abort(); eventQueue.Abort(); - } private void pollServer() @@ -748,6 +779,30 @@ namespace IW4MAdmin } //Vital RCON commands to establish log file and server name. May need to cleanup in the future +#if USINGMEMORY + private bool initializeBasics() + { + dvar map = Utilities.getDvar(0x2098DDC, Handle); + + String mapOut; + + mapname = map.current; + + dvar sv_hostname = Utilities.getDvar(0x2098D98, Handle); + hostname = sv_hostname.current; + + dvar shortversion = Utilities.getDvar(0x1AD79D0, Handle); + IW_Ver = shortversion.current; + + dvar party_maxplayers = Utilities.getDvar(0x1080998, Handle); + maxClients = Convert.ToInt32(party_maxplayers.current); + + dvar g_gametype = Utilities.getDvar(0x1AD7934, Handle); + Gametype = g_gametype.current; + + // skipping website b/c dynamically allocated ( we will pick it up on maprotate ) + } +#else private bool intializeBasics() { try @@ -922,11 +977,11 @@ namespace IW4MAdmin return false; } } - +#endif //Process any server event public bool processEvent(Event E) { - /*if (E.Type == Event.GType.Connect) // this is anow handled by rcon status :( + /*if (E.Type == Event.GType.Connect) // this is anow handled by memory :) { if (E.Origin == null) Log.Write("Connect event triggered, but no client is detected!", Log.Level.Debug); @@ -1089,7 +1144,8 @@ namespace IW4MAdmin { try { - mapname = maps.Find(m => m.Name.Equals(mapname)).Alias; + Map newMap = maps.Find(m => m.Name.Equals(newMapName)); + mapname = newMap.Alias; } catch (Exception) @@ -1151,7 +1207,14 @@ namespace IW4MAdmin public void Tell(String Message, Player Target) { if (Target.getClientNum() > -1) + { +#if DEBUG + RCON.addRCON("tell " + Target.getClientNum() + " " + Message + "^7"); + +#else RCON.addRCON("tellraw " + Target.getClientNum() + " " + Message + "^7"); // I fixed tellraw :> +#endif + } } public void Kick(String Message, Player Target) @@ -1460,6 +1523,7 @@ namespace IW4MAdmin private Dictionary Macros; private Moserware.TrueSkill Skills; private DateTime lastWebChat; + private int Handle; //Will probably move this later diff --git a/Admin/Utilities.cs b/Admin/Utilities.cs index 213dcfd6c..516a73a66 100644 --- a/Admin/Utilities.cs +++ b/Admin/Utilities.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Text; using System.Threading; using System.Text.RegularExpressions; +using System.Runtime.InteropServices; namespace IW4MAdmin { @@ -314,6 +315,81 @@ namespace IW4MAdmin return "a very long time"; } + [DllImport("kernel32.dll")] + public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); + + [DllImport("kernel32.dll")] + public static extern bool ReadProcessMemory(int hProcess, int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead); + + public static dvar getDvar(int Location, int Handle) + { + int numberRead = 0; + Byte[] Buff = new Byte[72]; + Byte[] Ptr = new Byte[4]; + + ReadProcessMemory(Handle, Location, Ptr, Ptr.Length, ref numberRead); // get location of dvar + + ReadProcessMemory(Handle, (int)BitConverter.ToUInt32(Ptr, 0), Buff, Buff.Length, ref numberRead); // read dvar memory + + dvar_t dvar_raw = Helpers.ReadStruct(Buff); // get the dvar struct + + dvar dvar_actual = new dvar(); // gotta convert to something readable + + dvar_actual.name = getStringFromPointer((int)dvar_raw.name, Handle); + dvar_actual.description = getStringFromPointer((int)dvar_raw.description, Handle); + + if ((int)dvar_raw._default > short.MaxValue) + dvar_actual._default = getStringFromPointer((int)dvar_raw._default, Handle); + else + dvar_actual._default = dvar_raw._default.ToString(); + + if ((int)dvar_raw.current > short.MaxValue) + dvar_actual.current = getStringFromPointer((int)dvar_raw.current, Handle); + else + dvar_actual.current = dvar_raw.current.ToString(); + + if ((int)dvar_raw.latched > short.MaxValue) + dvar_actual.latched = getStringFromPointer((int)dvar_raw.latched, Handle); + else + dvar_actual.latched = dvar_raw.latched.ToString(); + + dvar_actual.flags = getIntFromPointer((int)dvar_raw.flags, Handle); + dvar_actual.max = getIntFromPointer((int)dvar_raw.max, Handle); + dvar_actual.min = getIntFromPointer((int)dvar_raw.min, Handle); + + // done! + + return dvar_actual; + } + + public static String getStringFromPointer(int Location, int Handle) + { + int numberRead = 0; + Byte[] Buff = new Byte[256]; + + ReadProcessMemory(Handle, Location, Buff, Buff.Length, ref numberRead); + + StringBuilder str = new StringBuilder(); + for ( int i = 0; i < Buff.Length; i++) + { + if (Buff[i] == 0) + break; + + str.Append((char)Buff[i]); + } + return str.ToString(); + } + + public static int getIntFromPointer(int Location, int Handle) + { + int numberRead = 0; + Byte[] Buff = new Byte[4]; + + ReadProcessMemory(Handle, Location, Buff, Buff.Length, ref numberRead); + + return BitConverter.ToInt32(Buff, 0); + } + public static String timesConnected(int connection) { String Prefix = String.Empty; diff --git a/Admin/version.txt b/Admin/version.txt index 1853511c5..4102f2667 100644 --- a/Admin/version.txt +++ b/Admin/version.txt @@ -2,6 +2,7 @@ CHANGELOG: -fixed issue with `history` timelime -fixed issue with mapname not being updated +-now reads memory for player info! ( experimental debug only ) VERSION: 0.9 CHANGELOG: