From 57ddbebd8c4b483876da13a5b2f25e2ce4b93257 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Tue, 10 Mar 2015 15:45:20 -0500 Subject: [PATCH] added preliminary heartbeat system modified the deployment of outdated message inform user of invalid rcon password reworked layout of first time server setup fixed KDR not being properly truncated in some cases fixed duplicate game-end event more crash fixes --- Admin/Command.cs | 4 ++ Admin/Connection.cs | 14 ++++++ Admin/Database.cs | 2 +- Admin/Event.cs | 4 +- Admin/File.cs | 10 ++++- Admin/Heartbeat.cs | 24 +++++++++++ Admin/IW4M ADMIN.csproj | 1 + Admin/Main.cs | 96 ++++++++++++++++++++++++----------------- Admin/Player.cs | 2 +- Admin/RCON.cs | 12 +++++- Admin/Server.cs | 94 +++++++++++++++++++++++++++------------- Admin/Utilities.cs | 6 +++ 12 files changed, 192 insertions(+), 77 deletions(-) create mode 100644 Admin/Heartbeat.cs diff --git a/Admin/Command.cs b/Admin/Command.cs index 15674f2db..9fac290ee 100644 --- a/Admin/Command.cs +++ b/Admin/Command.cs @@ -167,7 +167,11 @@ namespace IW4MAdmin { E.Target.LastOffense = Utilities.removeWords(E.Data, 1); E.Target.lastEvent = E; // needs to be fixed +#if DEBUG String Message = "^1Player Banned: ^5" + E.Target.LastOffense + "^7 (appeal at nbsclan.org)"; +#else + String Message = "^1Player Banned: ^5" + E.Target.LastOffense; +#endif if (E.Origin.getLevel() > E.Target.getLevel()) { E.Target.Ban(Message, E.Origin); diff --git a/Admin/Connection.cs b/Admin/Connection.cs index 3c5a3dacd..f970f446c 100644 --- a/Admin/Connection.cs +++ b/Admin/Connection.cs @@ -35,6 +35,20 @@ namespace IW4MAdmin } } + public void Request(String data) + { + try + { + WebResponse Resp = WebRequest.Create(data).GetResponse(); + Resp.Close(); + } + + catch (System.Net.WebException E) + { + return; + } + } + private String Location; private WebRequest ConnectionHandle; } diff --git a/Admin/Database.cs b/Admin/Database.cs index 0d78b3e22..e1c0cd6c8 100644 --- a/Admin/Database.cs +++ b/Admin/Database.cs @@ -187,7 +187,7 @@ namespace IW4MAdmin { updatedPlayer.Add("KILLS", P.stats.Kills); updatedPlayer.Add("DEATHS", P.stats.Deaths); - updatedPlayer.Add("KDR", P.stats.KDR); + updatedPlayer.Add("KDR", Math.Round(P.stats.KDR, 2)); updatedPlayer.Add("SKILL", P.stats.Skill); Update("STATS", updatedPlayer, String.Format("Number = '{0}'", P.getDBID())); diff --git a/Admin/Event.cs b/Admin/Event.cs index 35fdef7ab..c6a92255b 100644 --- a/Admin/Event.cs +++ b/Admin/Event.cs @@ -84,11 +84,11 @@ namespace IW4MAdmin return new Event(GType.Say, Utilities.removeNastyChars(message), SV.clientFromLine(line, 3, false), null, null); } - if (eventType == "d" || eventType == ":") + if (eventType == ":") return new Event(GType.MapEnd, null, null, null, null); if (line[0].Length > 400) // blaze it - return new Event(GType.MapChange, null, null, null, null); + return new Event(GType.MapChange, line[0], null, null, null); return null; diff --git a/Admin/File.cs b/Admin/File.cs index fe953005b..c4cd735cb 100644 --- a/Admin/File.cs +++ b/Admin/File.cs @@ -64,6 +64,14 @@ namespace IW4MAdmin return 0; } + public void Close() + { + if(Handle != null) + Handle.Close(); + if (writeHandle != null) + writeHandle.Close(); + } + //FROM http://stackoverflow.com/questions/398378/get-last-10-lines-of-very-large-text-file-10gb-c-sharp public string ReadEndTokens() { @@ -105,7 +113,7 @@ namespace IW4MAdmin } public String[] readAll() { - return Handle.ReadToEnd().Split('\n'); + return Handle.ReadToEnd().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); } public String[] end(int neededLines) diff --git a/Admin/Heartbeat.cs b/Admin/Heartbeat.cs new file mode 100644 index 000000000..4f94ca0bc --- /dev/null +++ b/Admin/Heartbeat.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IW4MAdmin +{ + class Heartbeat + { + public Heartbeat(Server I) + { + Handle = new Connection("http://raidmax.org/IW4M/Admin"); + Instance = I; + } + + public void Send() + { + String URI = String.Format("http://raidmax.org/IW4M/Admin/heartbeat.php?address={0}&name={1}&map={2}&players={3}", Instance.getPort().ToString(), Instance.getName(), Instance.getMap(), Instance.getClientNum().ToString()); + Handle.Request(URI); + } + + private Connection Handle; + private Server Instance; + } +} diff --git a/Admin/IW4M ADMIN.csproj b/Admin/IW4M ADMIN.csproj index 4878af297..4a817a6ed 100644 --- a/Admin/IW4M ADMIN.csproj +++ b/Admin/IW4M ADMIN.csproj @@ -94,6 +94,7 @@ + diff --git a/Admin/Main.cs b/Admin/Main.cs index b1c8f3be4..b9394866a 100644 --- a/Admin/Main.cs +++ b/Admin/Main.cs @@ -24,57 +24,40 @@ namespace IW4MAdmin else Console.WriteLine(" Version " + Version + " (unable to retrieve latest)"); Console.WriteLine("====================================================="); + + + foreach (Server IW4M in checkConfig()) + { + //Threading seems best here + Thread monitorThread = new Thread(new ThreadStart(IW4M.Monitor)); + monitorThread.Start(); + Utilities.Wait(0.3); + Console.WriteLine("Now monitoring " + IW4M.getName()); + } + + Utilities.Wait(5); //Give them time to read an error before exiting - - file Config = new file("config\\servers.cfg"); - String[] SV_CONF = Config.readAll(); - - if (SV_CONF == null || SV_CONF.Length < 1 || SV_CONF[0] == String.Empty) - { - setupConfig(); - SV_CONF = new file("config\\servers.cfg").readAll(); - } - - - if (Config.getSize() > 0 && SV_CONF != null) - { - foreach (String S in SV_CONF) - { - if (S.Length < 1) - continue; - - String[] sv = S.Split(':'); - - Console.WriteLine("Starting admin on port " + sv[1]); - - Server IW4M; - IW4M = new Server(sv[0], Convert.ToInt32(sv[1]), sv[2]); - - //Threading seems best here - Thread monitorThread = new Thread(new ThreadStart(IW4M.Monitor)); - monitorThread.Start(); - } - - } - else - { - Console.WriteLine("[FATAL] CONFIG FILE DOES NOT EXIST OR IS INCORRECT!"); - Utilities.Wait(5); - } - } static void setupConfig() { + bool validPort = false; Console.WriteLine("Hey there, it looks like you haven't set up a server yet. Let's get started!"); + Console.Write("Please enter the IP: "); IP = Console.ReadLine(); - Console.Write("Please enter the Port: "); - Port = Convert.ToInt32(Console.ReadLine()); + + while (!validPort) + { + Console.Write("Please enter the Port: "); + int.TryParse(Console.ReadLine(), out Port); + if (Port != 0) + validPort = true; + } + Console.Write("Please enter the RCON password: "); RCON = Console.ReadLine(); file Config = new file("config\\servers.cfg", true); - Config.Write(IP + ":" + Port + ":" + RCON); Console.WriteLine("Great! Let's go ahead and start 'er up."); } @@ -83,5 +66,38 @@ namespace IW4MAdmin Connection Ver = new Connection("http://raidmax.org/IW4M/Admin/version.php"); return Ver.Read(); } + + static List checkConfig() + { + + file Config = new file("config\\servers.cfg"); + String[] SV_CONF = Config.readAll(); + List Servers = new List(); + + + if (SV_CONF == null || SV_CONF.Length < 1 || SV_CONF[0] == String.Empty) + { + setupConfig(); // get our first time server + Config.Write(IP + ':' + Port + ':' + RCON); + Servers.Add(new Server(IP, Port, RCON)); + } + + else + { + foreach (String L in SV_CONF) + { + String[] server_line = L.Split(':'); + int newPort; + if (server_line.Length < 3 || !int.TryParse(server_line[1], out newPort)) + { + Console.WriteLine("You have an error in your server.cfg"); + continue; + } + Servers.Add(new Server(server_line[0], newPort, server_line[2])); + } + } + + return Servers; + } } } diff --git a/Admin/Player.cs b/Admin/Player.cs index 5b90389de..e5c8f76d6 100644 --- a/Admin/Player.cs +++ b/Admin/Player.cs @@ -10,7 +10,7 @@ namespace IW4MAdmin { Kills = K; Deaths = D; - KDR = kdr; + KDR = Math.Round(kdr,2); Skill = Math.Round(skill,2); } diff --git a/Admin/RCON.cs b/Admin/RCON.cs index d62eaf178..b9cb6c0df 100644 --- a/Admin/RCON.cs +++ b/Admin/RCON.cs @@ -67,7 +67,7 @@ namespace IW4MAdmin { String STR_REQUEST; if (message != "getstatus") - STR_REQUEST = String.Format("ÿÿÿÿrcon {0} {1}", Instance.getPassword(), message); + STR_REQUEST = String.Format("ÿÿÿÿrcon {0} {1}", Instance.getPassword().Replace("\r", String.Empty), message); else STR_REQUEST = String.Format("ÿÿÿÿ getstatus"); @@ -93,7 +93,15 @@ namespace IW4MAdmin Byte[] receive = sv_connection.Receive(ref endPoint); int num = int.Parse("0a", System.Globalization.NumberStyles.AllowHexSpecifier); - return System.Text.Encoding.UTF8.GetString(receive).Split((char)num); + String[] response = System.Text.Encoding.UTF8.GetString(receive).Split((char)num); + + if(response[1] == "Invalid password.") + { + Instance.Log.Write("Invalid RCON password specified", Log.Level.Debug); + return null; + } + + return response; } catch (SocketException) diff --git a/Admin/Server.cs b/Admin/Server.cs index d2a8f6871..d61e0ddfb 100644 --- a/Admin/Server.cs +++ b/Admin/Server.cs @@ -30,6 +30,7 @@ namespace IW4MAdmin rules = new List(); messages = new List(); events = new Queue(); + HB = new Heartbeat(this); nextMessage = 0; initCommands(); initMessages(); @@ -43,6 +44,11 @@ namespace IW4MAdmin return hostname; } + public String getMap() + { + return mapname; + } + //Returns current server IP set by `net_ip` -- *STRING* public String getIP() { @@ -73,6 +79,11 @@ namespace IW4MAdmin return players; } + public int getClientNum() + { + return clientnum; + } + //Returns list of all active bans (loaded at runtime) public List getBans() { @@ -282,8 +293,8 @@ namespace IW4MAdmin String[] oldLines = new String[8]; DateTime start = DateTime.Now; - Utilities.Wait(1); - Broadcast("IW4M Admin is now ^2ONLINE"); + //Utilities.Wait(1); + //Broadcast("IW4M Admin is now ^2ONLINE"); while (errors <=5) { @@ -292,14 +303,13 @@ namespace IW4MAdmin lastMessage = DateTime.Now - start; if(lastMessage.TotalSeconds > messageTime && messages.Count > 0) { - if (Program.Version != Program.latestVersion && Program.latestVersion != 0) - Broadcast("^5IW4M Admin ^7is outdated. Please ^5update ^7to version " + Program.latestVersion); Broadcast(messages[nextMessage]); if (nextMessage == (messages.Count - 1)) nextMessage = 0; else nextMessage++; start = DateTime.Now; + HB.Send(); } if (l_size != logFile.getSize()) @@ -367,8 +377,40 @@ namespace IW4MAdmin { try { + //get sv_hostname + String[] p = RCON.responseSendRCON("sv_hostname"); + + if (p == null) + { + Log.Write("Could not obtain server name!", Log.Level.All); + return false; + } + + p = p[1].Split('"'); + hostname = Utilities.stripColors(p[3].Substring(0, p[3].Length - 2).Trim()); + p = null; + //END + + Thread.Sleep(FLOOD_TIMEOUT); + + //get mapname + p = RCON.responseSendRCON("mapname"); + + if (p == null) + { + Log.Write("Could not obtain map name!", Log.Level.All); + return false; + } + + p = p[1].Split('"'); + mapname = Utilities.stripColors(p[3].Substring(0, p[3].Length - 2).Trim()); + p = null; + //END + + Thread.Sleep(FLOOD_TIMEOUT); + //GET fs_basepath - String[] p = RCON.responseSendRCON("fs_basepath"); + p = RCON.responseSendRCON("fs_basepath"); if (p == null) { @@ -459,20 +501,6 @@ namespace IW4MAdmin Thread.Sleep(FLOOD_TIMEOUT); - //get sv_hostname - p = RCON.responseSendRCON("sv_hostname"); - - if (p == null) - { - Log.Write("Could not obtain server name!", Log.Level.All); - return false; - } - - p = p[1].Split('"'); - hostname = p[3].Substring(0, p[3].Length - 2).Trim(); - p = null; - //END - if (Mod == String.Empty || onelog == "1") logPath = Basepath + '\\' + "m2demo" + '\\' + log; else @@ -501,13 +529,13 @@ namespace IW4MAdmin { if (E.Type == Event.GType.Connect) { - this.addPlayer(E.Origin); + addPlayer(E.Origin); return true; } if (E.Type == Event.GType.Disconnect) { - if (getNumPlayers() > 0) + if (getNumPlayers() > 0 && E.Origin != null) { DB.updatePlayer(E.Origin); stats.updatePlayer(E.Origin); @@ -518,7 +546,7 @@ namespace IW4MAdmin if (E.Type == Event.GType.Kill) { - if (E.Origin != null && E.Target != null) + if (E.Origin != null && E.Target != null && E.Origin.stats != null) { E.Origin.stats.Kills++; E.Origin.stats.Update(); @@ -527,7 +555,7 @@ namespace IW4MAdmin } } - if (E.Type == Event.GType.Say) + if (E.Type == Event.GType.Say && E.Origin != null) { if (E.Data.Length < 2) return false; @@ -561,17 +589,18 @@ namespace IW4MAdmin if (E.Type == Event.GType.MapChange) { - Log.Write("Map change detected..", Log.Level.Production); - return true; - //TODO here + Log.Write("New map loaded", Log.Level.Debug); + String[] statusResponse = E.Data.Split('\\'); + if (statusResponse.Length >= 15 && statusResponse[13] == "mapname") + mapname = maps.Find(m => m.Name.Equals(statusResponse[14])).Alias; //update map for heartbeat } if (E.Type == Event.GType.MapEnd) { - Log.Write("Game ending...", Log.Level.Production); + Log.Write("Game ending...", Log.Level.Debug); foreach (Player P in players) { - if (P == null) + if (P == null || P.stats == null) continue; stats.updatePlayer(P); Log.Write("Updated stats for client " + P.getDBID(), Log.Level.Debug); @@ -707,6 +736,8 @@ namespace IW4MAdmin if (lines[0] != l && l.Length > 1) messages.Add(l); } + if (Program.Version != Program.latestVersion && Program.latestVersion != 0) + messages.Add("^5IW4M Admin ^7is outdated. Please ^5update ^7to version " + Program.latestVersion); } private void initRules() @@ -740,11 +771,11 @@ namespace IW4MAdmin commands.Add(new SBan("ban", "permanently ban a player from the server. syntax: !ban ", "b", Player.Permission.SeniorAdmin, 2, true)); commands.Add(new WhoAmI("whoami", "give information about yourself. syntax: !whoami.", "who", Player.Permission.User, 0, false)); commands.Add(new List("list", "list active clients syntax: !list.", "l", Player.Permission.Moderator, 0, false)); - commands.Add(new Help("help", "list all available commands. syntax: !help.", "l", Player.Permission.User, 0, false)); + commands.Add(new Help("help", "list all available commands. syntax: !help.", "h", Player.Permission.User, 0, false)); commands.Add(new FastRestart("fastrestart", "fast restart current map. syntax: !fastrestart.", "fr", Player.Permission.Moderator, 0, false)); commands.Add(new MapRotate("maprotate", "cycle to the next map in rotation. syntax: !maprotate.", "mr", Player.Permission.Administrator, 0, false)); commands.Add(new SetLevel("setlevel", "set player to specified administration level. syntax: !setlevel .", "sl", Player.Permission.Owner, 2, true)); - commands.Add(new Usage("usage", "get current application memory usage. syntax: !usage.", "u", Player.Permission.Moderator, 0, false)); + commands.Add(new Usage("usage", "get current application memory usage. syntax: !usage.", "us", Player.Permission.Moderator, 0, false)); commands.Add(new Uptime("uptime", "get current application running time. syntax: !uptime.", "up", Player.Permission.Moderator, 0, false)); commands.Add(new Warn("warn", "warn player for infringing rules syntax: !warn .", "w", Player.Permission.Moderator, 2, true)); commands.Add(new WarnClear("warnclear", "remove all warning for a player syntax: !warnclear .", "wc", Player.Permission.Administrator, 1, true)); @@ -774,11 +805,13 @@ namespace IW4MAdmin public List rules; public Queue events; public Database stats; + public Heartbeat HB; //Info private String IP; private int Port; private String hostname; + private String mapname; private int clientnum; private string rcon_pass; private List players; @@ -788,6 +821,7 @@ namespace IW4MAdmin private TimeSpan lastMessage; private int nextMessage; private int errors = 0; + private Connection Heartbeat; //Log stuff private String Basepath; diff --git a/Admin/Utilities.cs b/Admin/Utilities.cs index 3c4aecb82..301ac3d54 100644 --- a/Admin/Utilities.cs +++ b/Admin/Utilities.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.Threading; +using System.Text.RegularExpressions; namespace IW4MAdmin { @@ -73,5 +74,10 @@ namespace IW4MAdmin } return lineNumber; } + + public static String stripColors(String str) + { + return Regex.Replace(str, @"\^[0-9]", ""); + } } }