commit 3d366e65227c508e262a3b55f0a1a9a7674b1ac4 Author: RaidMax Date: Sun Mar 8 16:20:10 2015 -0500 Intial Commit of Version 0.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..796453635 --- /dev/null +++ b/.gitignore @@ -0,0 +1,189 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +x64/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +## TODO: Comment the next line if you want to checkin your +## web deploy settings but do note that will include unencrypted +## passwords +#*.pubxml + +# NuGet Packages Directory +packages/* +## TODO: If the tool you use requires repositories.config +## uncomment the next line +#!packages/repositories.config + +# Enable "build/" folder in the NuGet Packages folder since +# NuGet packages use it for MSBuild targets. +# This line needs to be after the ignore of the build folder +# (and the packages folder if the line above has been uncommented) +!packages/build/ + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml \ No newline at end of file diff --git a/Admin/4D1.ico b/Admin/4D1.ico new file mode 100644 index 000000000..36b041520 Binary files /dev/null and b/Admin/4D1.ico differ diff --git a/Admin/Admin.cs b/Admin/Admin.cs new file mode 100644 index 000000000..273f62e69 --- /dev/null +++ b/Admin/Admin.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IW4MAdmin +{ + class Admin + { + public Admin() + { + Time = DateTime.Now; + Server = new Server("127.0.0.1", 28960, "NO"); + } + + public Server Server; + + public static String getTime() + { + return DateTime.Now.ToString("H:mm:ss"); + } + + public void Monitor() + { + Server.Monitor(); + } + + private DateTime Time; + } +} diff --git a/Admin/App.config b/Admin/App.config new file mode 100644 index 000000000..d3d299647 --- /dev/null +++ b/Admin/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Admin/Bans.cs b/Admin/Bans.cs new file mode 100644 index 000000000..3c2e4d98a --- /dev/null +++ b/Admin/Bans.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IW4MAdmin +{ + class Ban + { + public Ban(String Reas, String TargID, String From) + { + Reason = Reas; + npID = TargID; + bannedByID = From; + When = DateTime.Now; + } + + public String getReason() + { + return Reason; + } + + public String getID() + { + return npID; + } + + public String getBanner() + { + return bannedByID; + } + + private String Reason; + private String npID; + private String bannedByID; + private DateTime When; + + } + +} diff --git a/Admin/Command.cs b/Admin/Command.cs new file mode 100644 index 000000000..6dc5214b7 --- /dev/null +++ b/Admin/Command.cs @@ -0,0 +1,431 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IW4MAdmin +{ + abstract class Command + { + public Command(String N, String D, String U, Player.Permission P, int args, bool nT) + { + Name = N; + Description = D; + Usage = U; + Permission = P; + Arguments = args; + hasTarget = nT; + } + + //Get command name + public String getName() + { + return Name; + } + //Get description on command + public String getDescription() + { + return Description; + } + //Get the example usage of the command + public String getAlias() + { + return Usage; + } + //Get the required permission to execute the command + public Player.Permission getNeededPerm() + { + return Permission; + } + + public int getNumArgs() + { + return Arguments; + } + + public bool needsTarget() + { + return hasTarget; + } + + //Execute the command + abstract public void Execute(Event E); + + private String Name; + private String Description; + private String Usage; + private int Arguments; + private bool hasTarget; + + public Player.Permission Permission; + } + + class Owner : Command + { + public Owner(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + if (E.Owner.owner == null) + { + E.Origin.setLevel(Player.Permission.Owner); + E.Origin.Tell("Congratulations, you have claimed ownership of this server!"); + E.Owner.owner = E.Origin; + E.Owner.DB.updatePlayer(E.Origin); + } + else + E.Origin.Tell("This server already has an owner!"); + } + + } + + + class Warn : Command + { + public Warn(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + if (E.Origin.getLevel() <= E.Target.getLevel()) + E.Origin.Tell("You cannot warn " + E.Target.getName()); + else + { + E.Target.LastOffense = Utilities.removeWords(E.Data, 1); + E.Target.Warnings++; + String Message = String.Format("^1WARNING ^7[^3{0}^7]: ^3{1}^7, {2}", E.Target.Warnings, E.Target.getName(), E.Target.LastOffense); + E.Owner.Broadcast(Message); + if (E.Target.Warnings >= 4) + E.Target.Kick("You were kicked for too many warnings!"); + } + + } + + } + + class WarnClear : Command + { + public WarnClear(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + E.Target.LastOffense = String.Empty; + E.Target.Warnings = 0; + String Message = String.Format("All warning cleared for {0}", E.Target.getName()); + E.Owner.Broadcast(Message); + } + + } + + class Kick : Command + { + public Kick(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + E.Target.LastOffense = Utilities.removeWords(E.Data, 1); + String Message = "^1Player Kicked: ^5" + E.Target.LastOffense + " ^1Admin: ^5" + E.Origin.getName(); + if (E.Origin.getLevel() > E.Target.getLevel()) + E.Target.Kick(Message); + else + E.Origin.Tell("You cannot kick " + E.Target.getName()); + } + + } + + class Say : Command + { + public Say(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + E.Owner.Broadcast("^1" + E.Origin.getName() + " - ^6" + E.Data + "^7"); + } + + } + + class TempBan : Command + { + public TempBan(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + E.Target.LastOffense = Utilities.removeWords(E.Data, 1); + String Message = "^1Player Temporarily Banned: ^5" + E.Target.LastOffense + "^7 (1 hour)"; + if (E.Origin.getLevel() > E.Target.getLevel()) + E.Target.tempBan(Message); + else + E.Origin.Tell("You cannot temp ban " + E.Target.getName()); + + } + + } + + class SBan : Command + { + public SBan(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + E.Target.LastOffense = Utilities.removeWords(E.Data, 1); + E.Target.lastEvent = E; // needs to be fixed + String Message = "^1Player Banned: ^5" + E.Target.LastOffense + "^7 (appeal at nbsclan.org)"; + if (E.Origin.getLevel() > E.Target.getLevel()) + E.Target.Ban(Message, E.Origin); + else + E.Origin.Tell("You cannot ban " + E.Target.getName()); + } + + } + + class Unban : Command + { + public Unban(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + if (E.Owner.Unban(E.Data.Trim())) + E.Origin.Tell("Successfully unbanned " + E.Data.Trim()); + else + E.Origin.Tell("Unable to find a ban for that GUID"); + } + + } + + class WhoAmI : Command + { + public WhoAmI(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + String You = String.Format("You are {0} at client spot {1} with xuid {2}. You have connected {3} times and are currently ranked {4}", E.Origin.getName(), E.Origin.getClientNum(), E.Origin.getID(), E.Origin.getConnections(), E.Origin.getLevel()); + E.Origin.Tell(You); + } + + } + + class List : Command + { + public List(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + foreach (Player P in E.Owner.getPlayers()) + { + if (P == null) + continue; + + E.Origin.Tell(String.Format("[^3{0}^7]{3}[^3{1}^7] {2}", P.getLevel(), P.getClientNum(), P.getName(), Utilities.getSpaces(Player.Permission.SeniorAdmin.ToString().Length - P.getLevel().ToString().Length))); + } + } + + } + + class Help : Command + { + public Help(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + String cmd = E.Data.Trim(); + + if (cmd.Length > 2) + { + bool found = false; + foreach (Command C in E.Owner.getCommands()) + { + if (C.getName().Contains(cmd) || C.getName() == cmd) + { + E.Origin.Tell(" [^3" + C.getName() + "^7] " + C.getDescription()); + found = true; + } + } + + if (!found) + E.Origin.Tell("Could not find that command"); + } + + else + { + foreach (Command C in E.Owner.getCommands()) + { + if (E.Origin.getLevel() >= C.getNeededPerm()) + { + E.Origin.Tell(" [^3" + C.getName() + "^7] "); + } + } + E.Origin.Tell("Type !help to get command usage example"); + } + } + + } + + class FastRestart : Command + { + public FastRestart(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + E.Owner.Broadcast("Performing fast restart in 5 seconds..."); + E.Owner.fastRestart(5); + } + + } + + class MapRotate : Command + { + public MapRotate(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + E.Owner.Broadcast("Performing map rotate in 5 seconds..."); + E.Owner.mapRotate(5); + } + + } + + class SetLevel : Command + { + public SetLevel(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + if (E.Target == E.Origin) + { + E.Origin.Tell("You can't set your own level, silly."); + return; + } + + Player.Permission newPerm = Utilities.matchPermission(Utilities.removeWords(E.Data, 1)); + + if (newPerm > Player.Permission.Banned) + { + E.Target.setLevel(newPerm); + E.Target.Tell("Congratulations! You have been promoted to ^3" + newPerm); + E.Origin.Tell(E.Target.getName() + " was successfully promoted!"); + //NEEED TO mOVE + E.Owner.DB.updatePlayer(E.Target); + } + + else + E.Origin.Tell("Invalid group specified."); + } + + } + + class Usage : Command + { + public Usage(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + E.Origin.Tell("IW4M Admin is using " + Math.Round(((System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / 2048f) / 1200f), 1) + "MB"); + } + + } + + class Uptime : Command + { + public Uptime(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + TimeSpan uptime = DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime; + E.Origin.Tell(String.Format("IW4M Admin has been up for {0} days, {1} hours, and {2} minutes", uptime.Days, uptime.Hours, uptime.Minutes)); + } + + } + + class Admins : Command + { + public Admins(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + foreach (Player P in E.Owner.getPlayers()) + { + if (P != null && P.getLevel() > Player.Permission.User) + { + E.Origin.Tell(String.Format("[^3{0}^7]{3}[^3{1}^7] {2}", P.getLevel(), P.getClientNum(), P.getName(), Utilities.getSpaces(Player.Permission.SeniorAdmin.ToString().Length - P.getLevel().ToString().Length))); + } + } + } + + } + + class Wisdom : Command + { + public Wisdom(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + String Quote = new Connection("http://www.iheartquotes.com/api/v1/random?max_lines=1&max_characters=200").Read(); + E.Owner.Broadcast(Utilities.removeNastyChars(Quote)); + } + + } + + + class MapCMD : Command + { + public MapCMD(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + string newMap = E.Data.Trim().ToLower(); + foreach (Map m in E.Owner.maps) + { + if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap) + { + E.Owner.Broadcast("Changing to map ^2" + m.Alias); + Utilities.Wait(3); + E.Owner.Map(m.Name); + return; + } + } + + E.Owner.Broadcast("Attempting to change to unknown map ^1" + newMap); + Utilities.Wait(3); + E.Owner.Map(newMap); + } + + } + + class Find : Command + { + public Find(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + var db_players = E.Owner.DB.findPlayers(E.Data.Trim()); + if (db_players == null) + { + E.Origin.Tell("No players found"); + return; + } + + foreach (Player P in db_players) + { + String mesg = String.Format("[^3{0}^7] [^3@{1}^7] - {2}", P.getName(), P.getDBID(), P.getID()); + E.Origin.Tell(mesg); + } + + } + + } + + class Rules : Command + { + public Rules(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } + + public override void Execute(Event E) + { + if (E.Owner.rules.Count < 1) + E.Origin.Tell("This server has not set any rules."); + else + { + foreach (String r in E.Owner.rules) + E.Origin.Tell("- " + r); + } + } + + } +} diff --git a/Admin/Connection.cs b/Admin/Connection.cs new file mode 100644 index 000000000..fa1ce8df4 --- /dev/null +++ b/Admin/Connection.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.IO; + +namespace IW4MAdmin +{ + class Connection + { + public Connection(String Loc) + { + Location = Loc; + ConnectionHandle = WebRequest.Create(Location); + ConnectionHandle.Proxy = null; + } + + public String Read() + { + WebResponse Resp = ConnectionHandle.GetResponse(); + StreamReader data_in = new StreamReader(Resp.GetResponseStream()); + String result = data_in.ReadToEnd(); + + data_in.Close(); + Resp.Close(); + + return result; + } + + private String Location; + private WebRequest ConnectionHandle; + } +} diff --git a/Admin/Database.cs b/Admin/Database.cs new file mode 100644 index 000000000..1f78e5181 --- /dev/null +++ b/Admin/Database.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Data.SQLite; +using System.Data; +using System.IO; +using System.Collections; + +namespace IW4MAdmin +{ + class Database + { + public Database(String FN) + { + FileName = FN; + DBCon = String.Format("Data Source={0}", FN); + Con = new SQLiteConnection(DBCon); + Init(); + } + + private void Init() + { + if(!File.Exists(FileName)) + { + String query = "CREATE TABLE [CLIENTS] ( [Name] TEXT NULL, [npID] TEXT NULL, [Number] INTEGER PRIMARY KEY AUTOINCREMENT, [Level] INT DEFAULT 0 NULL, [LastOffense] TEXT NULL, [Connections] INT DEFAULT 1 NULL);"; + ExecuteNonQuery(query); + query = "CREATE TABLE [BANS] ( [Reason] TEXT NULL, [npID] TEXT NULL, [bannedByID] Text NULL)"; + ExecuteNonQuery(query); + } + } + + public Player getPlayer(String ID, int cNum) + { + String Query = String.Format("SELECT * FROM CLIENTS WHERE npID = '{0}' LIMIT 1", ID); + DataTable Result = GetDataTable(Query); + + if (Result != null && Result.Rows.Count > 0) + { + DataRow ResponseRow = Result.Rows[0]; + return new Player(ResponseRow["Name"].ToString(), ResponseRow["npID"].ToString(), cNum, (Player.Permission)(ResponseRow["Level"]), Convert.ToInt32(ResponseRow["Number"]), ResponseRow["LastOffense"].ToString(), ((int)ResponseRow["Connections"] + 1)); + } + + else + return null; + } + + public List findPlayers(String name) + { + String Query = String.Format("SELECT * FROM CLIENTS WHERE Name LIKE '%{0}%' LIMIT 10", name); + DataTable Result = GetDataTable(Query); + + List Players = new List(); + + if (Result != null && Result.Rows.Count > 0) + { + foreach (DataRow p in Result.Rows) + { + Players.Add(new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), ((int)p["Connections"]))); + } + return Players; + } + + else + return null; + } + + public Player findPlayers(int dbIndex) + { + String Query = String.Format("SELECT * FROM CLIENTS WHERE Number = '{0}' LIMIT 1", dbIndex); + DataTable Result = GetDataTable(Query); + + if (Result != null && Result.Rows.Count > 0) + { + foreach (DataRow p in Result.Rows) + return new Player(p["Name"].ToString(), p["npID"].ToString(), -1, (Player.Permission)(p["Level"]), Convert.ToInt32(p["Number"]), p["LastOffense"].ToString(), ((int)p["Connections"])); + } + + return null; + } + + public Player getOwner() + { + String Query = String.Format("SELECT * FROM CLIENTS WHERE Level = '{0}'", 4); + DataTable Result = GetDataTable(Query); + + if (Result != null && Result.Rows.Count > 0) + { + DataRow ResponseRow = Result.Rows[0]; + return new Player(ResponseRow["Name"].ToString(), ResponseRow["npID"].ToString(), -1, (Player.Permission)(ResponseRow["Level"]), Convert.ToInt32(ResponseRow["Number"]), null, 0); + } + + else + return null; + } + + public List getBans() + { + List Bans = new List(); + DataTable Result = GetDataTable("SELECT * FROM BANS"); + + foreach (DataRow Row in Result.Rows) + Bans.Add(new Ban(Row["Reason"].ToString(), Row["npID"].ToString(), Row["bannedByID"].ToString())); + + return Bans; + } + + public void removeBan(String GUID) + { + String Query = String.Format("DELETE FROM BANS WHERE npID = '{0}'", GUID); + ExecuteNonQuery(Query); + } + + public void addPlayer(Player P) + { + Dictionary newPlayer = new Dictionary(); + + newPlayer.Add("Name", Utilities.removeNastyChars(P.getName())); + newPlayer.Add("npID", P.getID()); + newPlayer.Add("Level", (int)P.getLevel()); + // newPlayer.Add("Number", P.getClientNum().ToString()); + newPlayer.Add("LastOffense", ""); + newPlayer.Add("Connections", 1); + + Insert("CLIENTS", newPlayer); + } + + public void updatePlayer(Player P) + { + Dictionary updatedPlayer = new Dictionary(); + + updatedPlayer.Add("Name", P.getName()); + updatedPlayer.Add("npID", P.getID()); + updatedPlayer.Add("Level", (int)P.getLevel()); + // updatedPlayer.Add("Number", P.getClientNum().ToString()); + updatedPlayer.Add("LastOffense", P.getLastO()); + updatedPlayer.Add("Connections", P.getConnections()); + + Update("CLIENTS", updatedPlayer, String.Format("npID = '{0}'", P.getID())); + } + + public void addBan(Ban B) + { + Dictionary newBan = new Dictionary(); + + newBan.Add("Reason", B.getReason()); + newBan.Add("npID", B.getID()); + newBan.Add("bannedByID", B.getBanner()); + + Insert("BANS", newBan); + } + + //HELPERS + + public bool Insert(String tableName, Dictionary data) + { + String columns = ""; + String values = ""; + Boolean returnCode = true; + foreach (KeyValuePair val in data) + { + columns += String.Format(" {0},", val.Key); + values += String.Format(" '{0}',", val.Value); + } + columns = columns.Substring(0, columns.Length - 1); + values = values.Substring(0, values.Length - 1); + try + { + this.ExecuteNonQuery(String.Format("insert into {0}({1}) values({2});", tableName, columns, values)); + } + catch (Exception fail) + { + Console.WriteLine(fail.Message); + returnCode = false; + } + return returnCode; + } + + public bool Update(String tableName, Dictionary data, String where) + { + String vals = ""; + Boolean returnCode = true; + if (data.Count >= 1) + { + foreach (KeyValuePair val in data) + { + vals += String.Format(" {0} = '{1}',", val.Key, val.Value); + } + vals = vals.Substring(0, vals.Length - 1); + } + try + { + this.ExecuteNonQuery(String.Format("update {0} set {1} where {2};", tableName, vals, where)); + } + catch (Exception fail) + { + Console.WriteLine(fail.Message); + returnCode = false; + } + return returnCode; + } + + public DataRow getDataRow(String Q) + { + DataRow Result = GetDataTable(Q).Rows[0]; + return Result; + } + + private int ExecuteNonQuery(String Request) + { + Con.Open(); + SQLiteCommand CMD = new SQLiteCommand(Con); + CMD.CommandText = Request; + int rowsUpdated = CMD.ExecuteNonQuery(); + Con.Close(); + return rowsUpdated; + } + + public DataTable GetDataTable(String sql) + { + DataTable dt = new DataTable(); + try + { + Con.Open(); + SQLiteCommand mycommand = new SQLiteCommand(Con); + mycommand.CommandText = sql; + SQLiteDataReader reader = mycommand.ExecuteReader(); + dt.Load(reader); + reader.Close(); + Con.Close(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + throw new Exception(e.Message); + } + return dt; + } + //END + + private String FileName; + private String DBCon; + private SQLiteConnection Con; + } +} diff --git a/Admin/Event.cs b/Admin/Event.cs new file mode 100644 index 000000000..f9e0c061b --- /dev/null +++ b/Admin/Event.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace IW4MAdmin +{ + class Event + { + public enum GType + { + //FROM SERVER + Connect, + Disconnect, + Say, + Kill, + Death, + MapChange, + MapEnd, + + //FROM ADMIN + Broadcast, + Tell, + Kick, + Ban, + + Unknown, + } + + public Event(GType t, string d, Player O, Player T, Server S) + { + Type = t; + Data = d; + Origin = O; + Target = T; + Owner = S; + } + + //This needs to be here + public Command isValidCMD(List list) + { + if (this.Data.Substring(0, 1) == "!") + { + string[] cmd = this.Data.Substring(1, this.Data.Length - 1).Split(' '); + + foreach (Command C in list) + { + if (C.getName() == cmd[0].ToLower() || C.getAlias() == cmd[0].ToLower()) + return C; + } + + return null; + } + + else + return null; + } + + public static Event requestEvent(String[] line, Server SV) + { + try + { + String eventType = line[0].Substring(line[0].Length - 1); + eventType = eventType.Trim(); + + if (eventType == "J") + return new Event(GType.Connect, null, SV.clientFromLine(line, 3, true), null, SV); + + if (eventType == "Q") + return new Event(GType.Disconnect, null, SV.clientFromLine(line, 3, false), null, null); + + if (line[0].Substring(line[0].Length - 3).Trim() == "say") + { + Regex rgx = new Regex("[^a-zA-Z0-9 -! -_]"); + string message = rgx.Replace(line[4], ""); + if (message.Length < 2) + message = " "; + return new Event(GType.Say, Utilities.removeNastyChars(message), SV.clientFromLine(line, 3, false), null, null); + } + + if (eventType == "d") + 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 null; + } + catch (Exception E) + { + SV.Log.Write("Error requesting event " + E.Message, Log.Level.Debug); + return null; + } + } + + + public GType Type; + public string Data; // Data is usually the message sent by player + public Player Origin; + public Player Target; + public Server Owner; + + } +} diff --git a/Admin/File.cs b/Admin/File.cs new file mode 100644 index 000000000..fe953005b --- /dev/null +++ b/Admin/File.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace IW4MAdmin +{ + class file + { + public file(String fileName) + { + //Not safe for directories with more than one folder but meh + _Directory = fileName.Split('\\')[0]; + Name = (fileName.Split('\\'))[fileName.Split('\\').Length-1]; + + if (!Directory.Exists(_Directory)) + Directory.CreateDirectory(_Directory); + + if (!File.Exists(fileName)) + { + FileStream penis = File.Create(fileName); + penis.Close(); + } + Handle = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); + sze = Handle.BaseStream.Length; + } + + public file(String file, bool write) + { + Name = file; + writeHandle = new StreamWriter(new FileStream(Name, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)); + sze = 0; + } + + public long getSize() + { + sze = Handle.BaseStream.Length; + return sze; + } + + public void Write(String line) + { + writeHandle.WriteLine(line); + writeHandle.Flush(); + } + + public String[] getParameters(int num) + { + if (sze > 0) + { + String firstLine = Handle.ReadLine(); + String[] Parms = firstLine.Split(':'); + if (Parms.Length < num) + return null; + else + return Parms; + } + + return null; + } + + public int getNumLines() + { + return 0; + } + + //FROM http://stackoverflow.com/questions/398378/get-last-10-lines-of-very-large-text-file-10gb-c-sharp + public string ReadEndTokens() + { + Encoding encoding = Encoding.ASCII; + string tokenSeparator = "\n"; + int numberOfTokens = 2; + + int sizeOfChar = encoding.GetByteCount("\n"); + byte[] buffer = encoding.GetBytes(tokenSeparator); + + using (FileStream fs = new FileStream(this.Name, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + Int64 tokenCount = 0; + Int64 endPosition = fs.Length / sizeOfChar; + + for (Int64 position = sizeOfChar; position < endPosition; position += sizeOfChar) + { + fs.Seek(-position, SeekOrigin.End); + fs.Read(buffer, 0, buffer.Length); + + if (encoding.GetString(buffer) == tokenSeparator) + { + tokenCount++; + if (tokenCount == numberOfTokens) + { + byte[] returnBuffer = new byte[fs.Length - fs.Position]; + fs.Read(returnBuffer, 0, returnBuffer.Length); + return encoding.GetString(returnBuffer); + } + } + } + + // handle case where number of tokens in file is less than numberOfTokens + fs.Seek(0, SeekOrigin.Begin); + buffer = new byte[fs.Length]; + fs.Read(buffer, 0, buffer.Length); + return encoding.GetString(buffer); + } + } + public String[] readAll() + { + return Handle.ReadToEnd().Split('\n'); + } + + public String[] end(int neededLines) + { + var lines = new List(); + while (!Handle.EndOfStream) + { + String lins = Handle.ReadLine(); + lines.Add(lins.ToString()); + if (lines.Count > neededLines) + { + lines.RemoveAt(0); + } + } + + return lines.ToArray(); + } + + public String[] Tail(int lineCount) + { + var buffer = new List(lineCount); + string line; + for (int i = 0; i < lineCount; i++) + { + line = Handle.ReadLine(); + if (line == null) return buffer.ToArray(); + buffer.Add(line); + } + + int lastLine = lineCount - 1; //The index of the last line read from the buffer. Everything > this index was read earlier than everything <= this indes + + while (null != (line = Handle.ReadLine())) + { + lastLine++; + if (lastLine == lineCount) lastLine = 0; + buffer[lastLine] = line; + } + + if (lastLine == lineCount - 1) return buffer.ToArray(); + var retVal = new string[lineCount]; + buffer.CopyTo(lastLine + 1, retVal, 0, lineCount - lastLine - 1); + buffer.CopyTo(0, retVal, lineCount - lastLine - 1, lastLine + 1); + return retVal; + } + //END + + private long sze; + private String Name; + private String _Directory; + StreamReader Handle; + StreamWriter writeHandle; + } +} diff --git a/Admin/IW4M ADMIN.csproj b/Admin/IW4M ADMIN.csproj new file mode 100644 index 000000000..f51bdc649 --- /dev/null +++ b/Admin/IW4M ADMIN.csproj @@ -0,0 +1,145 @@ + + + + + Debug + AnyCPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115} + Exe + Properties + IW4MAdmin + IW4MAdmin + v2.0 + 512 + + false + C:\Users\Michael\Desktop\IW4MAdmin\ + true + Disk + false + Foreground + 7 + Days + false + false + true + IW4M Administration + RaidMax LLC + publish.htm + true + 2 + 0.1.0.%2a + false + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 0 + + + 0D02A7F5C6EA170625B5BF533E667AE6C3F93065 + + + IW4M ADMIN_TemporaryKey.pfx + + + true + + + LocalIntranet + + + Properties\app.manifest + + + true + + + 4D1.ico + + + IW4MAdmin.Program + + + + + + False + bin\Release\System.Data.SQLite.dll + + + + + + + + + + + + + + + + + + True + True + Settings.settings + + + + + + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + + + + + + + + + \ No newline at end of file diff --git a/Admin/Log.cs b/Admin/Log.cs new file mode 100644 index 000000000..2471a3217 --- /dev/null +++ b/Admin/Log.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IW4MAdmin +{ + class Log + { + public enum Level + { + All, + Debug, + Production, + None, + } + + public Log(file logf, Level mode) + { + logFile = logf; + logMode = mode; + } + + + public void Write(String line, Level lv) + { + String Line = String.Format("{1} - [{0}]: {2}", lv, getTime(), line); + switch(logMode) + { + case Level.All: + if (lv == Level.All || lv == Level.Debug || lv == Level.Production) + Console.WriteLine(Line); + break; + case Level.Debug: + if (lv == Level.All || lv == Level.Debug) + Console.WriteLine(Line); + break; + case Level.Production: + if (lv == Level.Production) + Console.WriteLine(Line); + break; + } + + logFile.Write(Line); + } + + private string getTime() + { + return DateTime.Now.ToString("HH:mm:ss"); + } + + private file logFile; + private Level logMode; + } +} diff --git a/Admin/Main.cs b/Admin/Main.cs new file mode 100644 index 000000000..2b6965ef6 --- /dev/null +++ b/Admin/Main.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IW4MAdmin +{ + class Program + { + static void Main(string[] args) + { + + file Config = new file("config\\servers.cfg"); + String[] SV_CONF = Config.getParameters(3); + double Version = 0.1; + + if (Config.getSize() > 0 && SV_CONF != null) + { + Console.WriteLine("====================================================="); + Console.WriteLine(" IW4M ADMIN v" + Version); + Console.WriteLine(" by RaidMax "); + Console.WriteLine("====================================================="); + Console.WriteLine("Starting admin on port " + SV_CONF[1]); + + Server IW4M; + IW4M = new Server(SV_CONF[0], Convert.ToInt32(SV_CONF[1]), SV_CONF[2]); + IW4M.Monitor(); + } + else + { + Console.WriteLine("[FATAL] CONFIG FILE DOES NOT EXIST OR IS INCORRECT!"); + Utilities.Wait(5); + } + + } + } +} diff --git a/Admin/Maps.cs b/Admin/Maps.cs new file mode 100644 index 000000000..8914a664a --- /dev/null +++ b/Admin/Maps.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IW4MAdmin +{ + class Map + { + public Map(String N, String A) + { + Name = N; + Alias = A; + } + + public String Name; + public String Alias; + } +} diff --git a/Admin/Player.cs b/Admin/Player.cs new file mode 100644 index 000000000..dd375d390 --- /dev/null +++ b/Admin/Player.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace IW4MAdmin +{ + class Player + { + public enum Permission + { + Banned = -1, + User = 0, + Moderator = 1, + Administrator = 2, + SeniorAdmin = 3, + Owner = 4, + Creator = 5, + } + + public Player(string n, string id, int num, int l) + { + Name = n; + npID = id; + Number = num; + Level = (Player.Permission)l; + LastOffense = null; + Connections = 0; + Warnings = 0; + } + + public Player(string n, string id, int num, Player.Permission l, int cind, String lo, int con) + { + Name = n; + npID = id; + Number = num; + Level = l; + dbID = cind; + LastOffense = lo; + Connections = con; + Warnings = 0; + } + + public String getName() + { + return Name; + } + + public String getID() + { + return npID; + } + + public String getDBID() + { + return Convert.ToString(dbID); + } + + public int getClientNum() + { + return Number; + } + + public Player.Permission getLevel() + { + return Level; + } + + public int getConnections() + { + return Connections; + } + + public String getLastO() + { + return LastOffense; + } + + // BECAUSE IT NEEDS TO BE CHANGED! + public void setLevel(Player.Permission Perm) + { + Level = Perm; + } + + public void Tell(String Message) + { + lastEvent.Owner.Tell(Message, this); + } + + public void Warn(String Message) + { + lastEvent.Owner.Broadcast(Message); + } + + public void Kick(String Message) + { + lastEvent.Owner.Kick(Message, this); + } + + public void tempBan(String Message) + { + lastEvent.Owner.tempBan(Message, this); + } + + public void Ban(String Message, Player Sender) + { + lastEvent.Owner.Ban(Message, this, Sender); + } + + //should be moved to utils + public Player findPlayer(String Nme) + { + foreach (Player P in lastEvent.Owner.getPlayers()) + { + if (P == null) + continue; + if (P.getName().ToLower().Contains(Name.ToLower())) + return P; + } + + return null; + } + + private string Name; + private string npID; + private int Number; + private Player.Permission Level; + private int dbID; + private int Connections; + + public Event lastEvent; + public String LastOffense; + public int Warnings; + } +} diff --git a/Admin/Properties/AssemblyInfo.cs b/Admin/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..7fb6d6e90 --- /dev/null +++ b/Admin/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Resources; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("IW4M Admin")] +[assembly: AssemblyDescription("Admin for IW4x projects")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("RaidMax LLC")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("© 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(true)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("62f47df4-c8eb-4b5d-bdd8-9de0ca6b570f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.1.*")] +[assembly: NeutralResourcesLanguageAttribute("en")] diff --git a/Admin/Properties/Settings.settings b/Admin/Properties/Settings.settings new file mode 100644 index 000000000..049245f40 --- /dev/null +++ b/Admin/Properties/Settings.settings @@ -0,0 +1,6 @@ + + + + + + diff --git a/Admin/Properties/app.manifest b/Admin/Properties/app.manifest new file mode 100644 index 000000000..887fa2b9d --- /dev/null +++ b/Admin/Properties/app.manifest @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Admin/RCON.cs b/Admin/RCON.cs new file mode 100644 index 000000000..69188c3e7 --- /dev/null +++ b/Admin/RCON.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +//DILEMMA -- Seperate intance of RCON for each server or no? +namespace IW4MAdmin +{ + class RCON + { + enum Type + { + Query, + Execute, + } + + public RCON(Server I) + { + sv_connection = new UdpClient(); + sv_connection.Client.SendTimeout = 1000; + sv_connection.Client.ReceiveTimeout = 1000; + Instance = I; + toSend = new Queue(); + } + //When we don't care about a response + public bool sendRCON(String message) + { + try + { + String STR_REQUEST = String.Format("ÿÿÿÿrcon {0} {1}", Instance.getPassword(), message); + + Byte[] Request_ = Encoding.Unicode.GetBytes(STR_REQUEST); + Byte[] Request = new Byte[Request_.Length / 2]; + + int count = 0; //This is kinda hacky but Unicode -> ASCII doesn't seem to be working correctly for this. + foreach (Byte b in Request_) + { + if (b != 0) + Request[count / 2] = b; + count++; + } + + + System.Net.IPAddress IP = System.Net.IPAddress.Parse(Instance.getIP()); + IPEndPoint endPoint = new IPEndPoint(IP, Instance.getPort()); + + sv_connection.Connect(endPoint); + sv_connection.Send(Request, Request.Length); + } + + catch (SocketException) + { + Instance.Log.Write("Unable to reach server for sending RCON", Log.Level.Debug); + sv_connection.Close(); + return false; + } + + return true; + } + //We want to read the reponse + public String[] responseSendRCON(String message) + { + try + { + String STR_REQUEST; + if (message != "getstatus") + STR_REQUEST = String.Format("ÿÿÿÿrcon {0} {1}", Instance.getPassword(), message); + else + STR_REQUEST = String.Format("ÿÿÿÿ getstatus"); + + Byte[] Request_ = Encoding.Unicode.GetBytes(STR_REQUEST); + Byte[] Request = new Byte[Request_.Length/2]; + + int count = 0; //This is kinda hacky but Unicode -> ASCII doesn't seem to be working correctly for this. + foreach (Byte b in Request_) + { + if (b != 0) + Request[count/2] = b; + count++; + } + + + System.Net.IPAddress IP = System.Net.IPAddress.Parse(Instance.getIP()); + IPEndPoint endPoint = new IPEndPoint(IP, Instance.getPort()); + + sv_connection.Connect(endPoint); + sv_connection.Send(Request, Request.Length); + + + 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); + } + + catch (SocketException) + { + Instance.Log.Write("Unable to reach server for sending RCON", Log.Level.Debug); + sv_connection.Close(); + return null; + } + } + + public void addRCON(String Message, int delay) + { + toSend.Enqueue(Message); + } + + public void ManageRCONQueue() + { + while (true) + { + if (toSend.Count > 0) + { + sendRCON(toSend.Peek()); + toSend.Dequeue(); + } + Utilities.Wait(0.3); + } + } + + private UdpClient sv_connection; + private Server Instance; + private DateTime lastCMD; + private Queue toSend; + } +} diff --git a/Admin/SQLiteDatabase.cs b/Admin/SQLiteDatabase.cs new file mode 100644 index 000000000..2d6268bc0 --- /dev/null +++ b/Admin/SQLiteDatabase.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SQLite; + +namespace IW4MAdmin +{ + + + public class SQLiteDatabase + { + String dbConnection; + + /// + /// Default Constructor for SQLiteDatabase Class. + /// + public SQLiteDatabase(String file) + { + dbConnection = "Data Source=" + file; + } + + /// + /// Single Param Constructor for specifying the DB file. + /// + /// The File containing the DB + public SQLiteDatabase(String inputFile) + { + dbConnection = String.Format("Data Source={0}", inputFile); + } + + /// + /// Single Param Constructor for specifying advanced connection options. + /// + /// A dictionary containing all desired options and their values + public SQLiteDatabase(Dictionary connectionOpts) + { + String str = ""; + foreach (KeyValuePair row in connectionOpts) + { + str += String.Format("{0}={1}; ", row.Key, row.Value); + } + str = str.Trim().Substring(0, str.Length - 1); + dbConnection = str; + } + + /// + /// Allows the programmer to run a query against the Database. + /// + /// The SQL to run + /// A DataTable containing the result set. + public DataTable GetDataTable(string sql) + { + DataTable dt = new DataTable(); + try + { + SQLiteConnection cnn = new SQLiteConnection(dbConnection); + cnn.Open(); + SQLiteCommand mycommand = new SQLiteCommand(cnn); + mycommand.CommandText = sql; + SQLiteDataReader reader = mycommand.ExecuteReader(); + dt.Load(reader); + reader.Close(); + cnn.Close(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + throw new Exception(e.Message); + } + return dt; + } + + /// + /// Allows the programmer to interact with the database for purposes other than a query. + /// + /// The SQL to be run. + /// An Integer containing the number of rows updated. + public int ExecuteNonQuery(string sql) + { + SQLiteConnection cnn = new SQLiteConnection(dbConnection); + cnn.Open(); + SQLiteCommand mycommand = new SQLiteCommand(cnn); + mycommand.CommandText = sql; + int rowsUpdated = mycommand.ExecuteNonQuery(); + cnn.Close(); + return rowsUpdated; + } + + /// + /// Allows the programmer to retrieve single items from the DB. + /// + /// The query to run. + /// A string. + public string ExecuteScalar(string sql) + { + SQLiteConnection cnn = new SQLiteConnection(dbConnection); + cnn.Open(); + SQLiteCommand mycommand = new SQLiteCommand(cnn); + mycommand.CommandText = sql; + object value = mycommand.ExecuteScalar(); + cnn.Close(); + if (value != null) + { + return value.ToString(); + } + return ""; + } + + /// + /// Allows the programmer to easily update rows in the DB. + /// + /// The table to update. + /// A dictionary containing Column names and their new values. + /// The where clause for the update statement. + /// A boolean true or false to signify success or failure. + public bool Update(String tableName, Dictionary data, String where) + { + String vals = ""; + Boolean returnCode = true; + if (data.Count >= 1) + { + foreach (KeyValuePair val in data) + { + vals += String.Format(" {0} = '{1}',", val.Key.ToString(), val.Value.ToString()); + } + vals = vals.Substring(0, vals.Length - 1); + } + try + { + this.ExecuteNonQuery(String.Format("update {0} set {1} where {2};", tableName, vals, where)); + } + catch( Exception fail) + { + Console.WriteLine(fail.Message); + returnCode = false; + } + return returnCode; + } + + /// + /// Allows the programmer to easily delete rows from the DB. + /// + /// The table from which to delete. + /// The where clause for the delete. + /// A boolean true or false to signify success or failure. + public bool Delete(String tableName, String where) + { + Boolean returnCode = true; + try + { + this.ExecuteNonQuery(String.Format("delete from {0} where {1};", tableName, where)); + } + catch (Exception fail) + { + Console.WriteLine(fail.Message); + returnCode = false; + } + return returnCode; + } + + /// + /// Allows the programmer to easily insert into the DB + /// + /// The table into which we insert the data. + /// A dictionary containing the column names and data for the insert. + /// A boolean true or false to signify success or failure. + public bool Insert(String tableName, Dictionary data) + { + String columns = ""; + String values = ""; + Boolean returnCode = true; + foreach (KeyValuePair val in data) + { + columns += String.Format(" {0},", val.Key.ToString()); + values += String.Format(" '{0}',", val.Value); + } + columns = columns.Substring(0, columns.Length - 1); + values = values.Substring(0, values.Length - 1); + try + { + this.ExecuteNonQuery(String.Format("insert into {0}({1}) values({2});", tableName, columns, values)); + } + catch (Exception fail) + { + Console.WriteLine(fail.Message); + returnCode = false; + } + return returnCode; + } + + /// + /// Allows the programmer to easily delete all data from the DB. + /// + /// A boolean true or false to signify success or failure. + public bool ClearDB() + { + DataTable tables; + try + { + tables = this.GetDataTable("select NAME from SQLITE_MASTER where type='table' order by NAME;"); + foreach (DataRow table in tables.Rows) + { + this.ClearTable(table["NAME"].ToString()); + } + return true; + } + catch + { + return false; + } + } + + /// + /// Allows the user to easily clear all data from a specific table. + /// + /// The name of the table to clear. + /// A boolean true or false to signify success or failure. + public bool ClearTable(String table) + { + try + { + + this.ExecuteNonQuery(String.Format("delete from {0};", table)); + return true; + } + catch + { + return false; + } + } + } + + +} diff --git a/Admin/Server.cs b/Admin/Server.cs new file mode 100644 index 000000000..856e56596 --- /dev/null +++ b/Admin/Server.cs @@ -0,0 +1,759 @@ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; //SLEEP +using System.IO; + + +namespace IW4MAdmin +{ + class Server + { + const int FLOOD_TIMEOUT = 300; + + public Server(string address, int port, string password) + { + IP = address; + Port = port; + rcon_pass = password; + clientnum = 0; + RCON = new RCON(this); + logFile = new file("admin_" + address + "_" + port + ".log", true); + Log = new Log(logFile, Log.Level.Production); + players = new List(new Player[18]); + DB = new Database(port + ".db"); + Bans = DB.getBans(); + owner = DB.getOwner(); + maps = new List(); + rules = new List(); + messages = new List(); + events = new Queue(); + nextMessage = 0; + initCommands(); + initMessages(); + initMaps(); + initRules(); + } + + //Returns the current server name -- *STRING* + public String getName() + { + return hostname; + } + + //Returns current server IP set by `net_ip` -- *STRING* + public String getIP() + { + return IP; + } + + //Returns current server port set by `net_port` -- *INT* + public int getPort() + { + return Port; + } + + //Returns number of active clients on server -- *INT* + public int getNumPlayers() + { + return clientnum; + } + + //Returns the list of commands + public List getCommands() + { + return commands; + } + + public List getPlayers() + { + return players; + } + + public List getBans() + { + return Bans; + } + + //Performs update on server statistics read from log. + public void Update() + { + } + + //Add player object p to `players` list + public bool addPlayer(Player P) + { + try + { + if (DB.getPlayer(P.getID(), P.getClientNum()) == null) + DB.addPlayer(P); + else + { + //messy way to prevent loss of last event + Player A; + A = DB.getPlayer(P.getID(), P.getClientNum()); + A.lastEvent = P.lastEvent; + P = A; + } + + players[P.getClientNum()] = null; + players[P.getClientNum()] = P; + + clientnum++; + + if (P.getLevel() == Player.Permission.Banned) + { + Log.Write("Banned client " + P.getName() + " trying to connect...", Log.Level.Production); + String Message = "^1Player Kicked: ^7Previously Banned for ^5" + isBanned(P).getReason(); + P.Kick(Message); + } + + else + Log.Write("Client " + P.getName() + " connecting...", Log.Level.Production); + + return true; + } + catch (Exception E) + { + Log.Write("Unable to add player - " + E.Message, Log.Level.Debug); + return false; + } + } + + //Remove player by CLIENT NUMBER + public bool removePlayer(int cNum) + { + Log.Write("Client at " + cNum + " disconnecting...", Log.Level.Production); + players[cNum] = null; + clientnum--; + return true; + } + + public Player clientFromLine(String[] line, int name_pos, bool create) + { + string Name = line[name_pos].ToString().Trim(); + if (create) + { + Player C = new Player(Name, line[1].ToString(), Convert.ToInt16(line[2]), 0); + return C; + } + + else + { + foreach (Player P in players) + { + if (P == null) + continue; + + if (line[1].Trim() == P.getID()) + return P; + } + + Log.Write("Could not find player but player is in server. Lets try to manually add (looks like you didn't start me on an empty server)", Log.Level.All); + addPlayer(new Player(Name, line[1].ToString(), Convert.ToInt16(line[2]), 0)); + return players[Convert.ToInt16(line[2])]; + } + } + + public Player clientFromLine(String Name) + { + foreach (Player P in players) + { + if (P == null) + continue; + if (P.getName().ToLower().Contains(Name.ToLower())) + return P; + } + + return null; + } + + public Ban isBanned(Player C) + { + if (C.getLevel() == Player.Permission.Banned) + { + foreach (Ban B in Bans) + { + if (B.getID() == C.getID()) + return B; + } + } + + return null; + } + + public Command processCommand(Event E, Command C) + { + E.Data = Utilities.removeWords(E.Data, 1); + String[] Args = E.Data.Trim().Split(' '); + + if (Args.Length < (C.getNumArgs())) + { + E.Origin.Tell("Not enough arguments supplied!"); + return null; + } + + if(E.Origin.getLevel() < C.getNeededPerm()) + { + E.Origin.Tell("You do not have access to that command!"); + return null; + } + + if (C.needsTarget()) + { + int cNum = -1; + int.TryParse(Args[0], out cNum); + + if (Args[0][0] == '@') + { + int dbID = -1; + int.TryParse(Args[0].Substring(1, Args[0].Length-1), out dbID); + Player found = E.Owner.DB.findPlayers(dbID); + if (found != null) + E.Target = found; + } + + else if(Args[0].Length < 3 && cNum > -1 && cNum < 18) + { + if (players[cNum] != null) + E.Target = players[cNum]; + } + + else + E.Target = clientFromLine(Args[0]); + + if (E.Target == null) + { + E.Origin.Tell("Unable to find specified player."); + return null; + } + } + return C; + } + + private void addEvent(Event E) + { + events.Enqueue(E); + } + + private void manageEventQueue() + { + while (true) + { + if (events.Count > 0) + { + processEvent(events.Peek()); + events.Dequeue(); + } + Utilities.Wait(0.1); + } + } + + //Starts the monitoring process + public void Monitor() + { + if (!intializeBasics()) + { + Log.Write("Shutting due to uncorrectable errors..." + logPath, Log.Level.Debug); + Utilities.Wait(10); + Environment.Exit(-1); + } + + //Handles new rcon requests in a fashionable manner + Thread RCONQueue = new Thread(new ThreadStart(RCON.ManageRCONQueue)); + RCONQueue.Start(); + + //Handles new events in a fashionable manner + Thread eventQueue = new Thread(new ThreadStart(manageEventQueue)); + eventQueue.Start(); + + long l_size = -1; + String[] lines = new String[8]; + String[] oldLines = new String[8]; + DateTime start = DateTime.Now; + + Utilities.Wait(1); + Broadcast("IW4M Admin is now ^2ONLINE"); + + while (errors <=5) + { + try + { + lastMessage = DateTime.Now - start; + if(lastMessage.TotalSeconds > messageTime && messages.Count > 0) + { + Broadcast(messages[nextMessage]); + if (nextMessage == (messages.Count - 1)) + nextMessage = 0; + else + nextMessage++; + start = DateTime.Now; + } + + if (l_size != logFile.getSize()) + { + lines = logFile.Tail(8); + if (lines != oldLines) + { + l_size = logFile.getSize(); + int end; + if (lines.Length == oldLines.Length) + end = lines.Length - 1; + else + end = Math.Abs((lines.Length - oldLines.Length)) - 1; + + for (int count = 0; count < lines.Length; count++) + { + if (lines.Length < 1 && oldLines.Length < 1) + continue; + + if (lines[count] == oldLines[oldLines.Length - 1]) + continue; + + if (lines[count].Length < 10) //Not a needed line + continue; + + else + { + string[] game_event = lines[count].Split(';'); + Event event_ = Event.requestEvent(game_event, this); + if (event_ != null) + { + if (event_.Origin == null) + event_.Origin = new Player("WORLD", "-1", -1, 0); + + event_.Origin.lastEvent = event_; + event_.Origin.lastEvent.Owner = this; + + addEvent(event_); + } + } + + } + } + } + oldLines = lines; + l_size = logFile.getSize(); + Thread.Sleep(1); + } + catch (Exception E) + { + Log.Write("Something unexpected occured. Hopefully we can ignore it - " + E.Message + " @" + Utilities.GetLineNumber(E), Log.Level.All); + errors++; + continue; + } + + } + + RCONQueue.Abort(); + eventQueue.Abort(); + + } + + private bool intializeBasics() + { + try + { + //GET fs_basepath + String[] p = RCON.responseSendRCON("fs_basepath"); + + if (p == null) + { + Log.Write("Could not obtain basepath!", Log.Level.All); + return false; + } + + p = p[1].Split('"'); + Basepath = p[3].Substring(0, p[3].Length - 2).Trim(); + p = null; + + Thread.Sleep(FLOOD_TIMEOUT); + //END + + //get fs_game + p = RCON.responseSendRCON("fs_game"); + + if (p == null) + { + Log.Write("Could not obtain mod path!", Log.Level.All); + return false; + } + + p = p[1].Split('"'); + Mod = p[3].Substring(0, p[3].Length - 2).Trim().Replace('/', '\\'); + p = null; + + Thread.Sleep(FLOOD_TIMEOUT); + //END + + //get g_log + p = RCON.responseSendRCON("g_log"); + + if (p == null) + { + Log.Write("Could not obtain log path!", Log.Level.All); + return false; + } + + if (p.Length < 4) + { + Thread.Sleep(FLOOD_TIMEOUT); + Log.Write("Server does not appear to have map loaded. Please map_rotate", Log.Level.All); + return false; + } + + p = p[1].Split('"'); + string log = p[3].Substring(0, p[3].Length - 2).Trim(); + p = null; + + Thread.Sleep(FLOOD_TIMEOUT); + //END + + //get g_logsync + p = RCON.responseSendRCON("g_logsync"); + + if (p == null) + { + Log.Write("Could not obtain log sync status!", Log.Level.All); + return false; + } + + + p = p[1].Split('"'); + int logsync = Convert.ToInt32(p[3].Substring(0, p[3].Length - 2).Trim()); + p = null; + + Thread.Sleep(FLOOD_TIMEOUT); + if (logsync != 1) + RCON.sendRCON("g_logsync 1"); + + Thread.Sleep(FLOOD_TIMEOUT); + //END + + //get iw4m_onelog + p = RCON.responseSendRCON("iw4m_onelog"); + + if (p[0] == String.Empty || p[1].Length < 15) + { + Log.Write("Could not obtain iw4m_onelog value!", Log.Level.All); + return false; + } + + p = p[1].Split('"'); + string onelog = p[3].Substring(0, p[3].Length - 2).Trim(); + p = null; + //END + + 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 + logPath = Basepath + '\\' + Mod + '\\' + log; + + if (!File.Exists(logPath)) + { + Log.Write("Gamelog does not exist!", Log.Level.All); + return false; + } + + logFile = new file(logPath); + Log.Write("Log file is " + logPath, Log.Level.Debug); + + return true; + } + catch (Exception E) + { + Log.Write("Error during initialization - " + E.Message, Log.Level.All); + return false; + } + } + + //Process any server event + public bool processEvent(Event E) + { + if (E.Type == Event.GType.Connect) + { + this.addPlayer(E.Origin); + return true; + } + + if (E.Type == Event.GType.Disconnect) + { + if (getNumPlayers() > 0) + removePlayer(E.Origin.getClientNum()); + return true; + } + + if (E.Type == Event.GType.Say) + { + Log.Write("Message from " + E.Origin.getName() + ": " + E.Data, Log.Level.Debug); + + if (E.Data.Substring(0, 1) != "!") + return true; + + Command C = E.isValidCMD(commands); + if (C != null) + { + C = processCommand(E, C); + if (C != null) + { + C.Execute(E); + return true; + } + else + { + Log.Write("Error processing command by " + E.Origin.getName(), Log.Level.Debug); + return true; + } + } + + else + E.Origin.Tell("You entered an invalid command!"); + + return true; + } + + if (E.Type == Event.GType.MapChange) + { + Log.Write("Map change detected..", Log.Level.Production); + return true; + //TODO here + } + + if (E.Type == Event.GType.MapEnd) + { + Log.Write("Game ending...", Log.Level.Production); + return true; + } + + return false; + } + + //THESE MAY NEED TO BE MOVED + public void Broadcast(String Message) + { + RCON.addRCON("sayraw " + Message, 0); + } + + public void Tell(String Message, Player Target) + { + RCON.addRCON("tell " + Target.getClientNum() + " " + Message + "^7", 0); + } + + public void Kick(String Message, Player Target) + { + RCON.addRCON("clientkick " + Target.getClientNum() + " \"" + Message + "^7\"", 0); + } + + public void Ban(String Message, Player Target, Player Origin) + { + RCON.addRCON("tempbanclient " + Target.getClientNum() + " \"" + Message + "^7\"", 0); + if (Origin != null) + { + Target.setLevel(Player.Permission.Banned); + Ban newBan = new Ban(Target.getLastO(), Target.getID(), Origin.getID()); + Bans.Add(newBan); + DB.addBan(newBan); + DB.updatePlayer(Target); + } + } + + public bool Unban(String GUID) + { + foreach (Ban B in Bans) + { + if (B.getID() == GUID) + { + DB.removeBan(GUID); + Bans.Remove(B); + Player P = DB.getPlayer(GUID, 0); + P.setLevel(Player.Permission.User); + DB.updatePlayer(P); + return true; + } + } + return false; + } + + + public void fastRestart(int delay) + { + Utilities.Wait(delay); + RCON.addRCON("fast_restart", 0); + } + + public void mapRotate(int delay) + { + Utilities.Wait(delay); + RCON.addRCON("map_rotate", 0); + } + + public void tempBan(String Message, Player Target) + { + RCON.addRCON("tempbanclient " + Target.getClientNum() + " \"" + Message + "\"", 0); + } + + public void mapRotate() + { + RCON.addRCON("map_rotate", 0); + } + + public void Map(String map) + { + RCON.addRCON("map " + map, 0); + } + //END + + //THIS IS BAD BECAUSE WE DON"T WANT EVERYONE TO HAVE ACCESS :/ + public String getPassword() + { + return rcon_pass; + } + + private void initMaps() + { + file mapfile = new file("config\\maps.cfg"); + String[] _maps = mapfile.readAll(); + if (_maps.Length > 2) // readAll returns minimum one empty string + { + foreach (String m in _maps) + { + String[] m2 = m.Split(':'); + if (m2.Length > 1) + { + Map map = new Map(m2[0].Trim(), m2[1].Trim()); + maps.Add(map); + } + } + } + else + Log.Write("Maps configuration appears to be empty - skipping...", Log.Level.All); + } + + private void initMessages() + { + file messageCFG = new file("config\\messages.cfg"); + String[] lines = messageCFG.readAll(); + + if (lines.Length < 2) //readAll returns minimum one empty string + { + Log.Write("Messages configuration appears empty - skipping...", Log.Level.All); + return; + } + + int mTime = -1; + int.TryParse(lines[0], out mTime); + + if (messageTime == -1) + messageTime = 60; + else + messageTime = mTime; + + foreach (String l in lines) + { + if (lines[0] != l && l.Length > 1) + messages.Add(l); + } + } + + private void initRules() + { + file ruleFile = new file("config\\rules.cfg"); + String[] _rules = ruleFile.readAll(); + if (_rules.Length > 2) // readAll returns minimum one empty string + { + foreach (String r in _rules) + { + if (r.Length > 1) + rules.Add(r); + } + } + else + Log.Write("Rules configuration appears empty - skipping...", Log.Level.All); + } + + private void initCommands() + { + // Something like *COMMAND* | NAME | HELP MSG | ALIAS | NEEDED PERMISSION | # OF REQUIRED ARGS | HAS TARGET | + + commands = new List(); + + if(owner == null) + commands.Add(new Owner("owner", "claim ownership of the server", "owner", Player.Permission.User, 0, false)); + + commands.Add(new Kick("kick", "kick a player by name. syntax: !kick .", "k", Player.Permission.Moderator, 2, true)); + commands.Add(new Say("say", "broadcast message to all players. syntax: !say .", "s", Player.Permission.Moderator, 1, false)); + commands.Add(new TempBan("tempban", "temporarily ban a player for 1 hour. syntax: !tempban .", "tb", Player.Permission.Moderator, 2, true)); + 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 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 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)); + commands.Add(new Unban("unban", "unban player by guid. syntax: !unban .", "ub", Player.Permission.Administrator, 1, false)); + commands.Add(new Admins("admins", "list currently connected admins. syntax: !admins.", "a", Player.Permission.User, 0, false)); + commands.Add(new Wisdom("wisdom", "get a random wisdom quote. syntax: !wisdom", "w", Player.Permission.Administrator, 0, false)); + commands.Add(new MapCMD("map", "change to specified map. syntax: !map", "m", Player.Permission.Administrator, 1, false)); + commands.Add(new Find("find", "find player in database. syntax: !find ", "f", Player.Permission.Administrator, 1, false)); + commands.Add(new Rules("rules", "list server rules. syntax: !rules", "r", Player.Permission.User, 0, false)); + + /* + commands.Add(new commands { command = "stats", desc = "view your server stats.", requiredPer = 0 }); + commands.Add(new commands { command = "speed", desc = "change player speed. syntax: !speed ", requiredPer = 3 }); + commands.Add(new commands { command = "gravity", desc = "change game gravity. syntax: !gravity ", requiredPer = 3 }); + + commands.Add(new commands { command = "version", desc = "view current app version.", requiredPer = 0 });*/ + } + + //Objects + public Log Log; + public RCON RCON; + public Database DB; + public List Bans; + public Player owner; + public List maps; + public List rules; + public Queue events; + + //Info + private String IP; + private int Port; + private String hostname; + private int clientnum; + private string rcon_pass; + private List players; + private List commands; + private List messages; + private int messageTime; + private TimeSpan lastMessage; + private int nextMessage; + private int errors = 0; + + //Log stuff + private String Basepath; + private String Mod; + private String logPath; + private file logFile; + } +} diff --git a/Admin/Utilities.cs b/Admin/Utilities.cs new file mode 100644 index 000000000..3c4aecb82 --- /dev/null +++ b/Admin/Utilities.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace IW4MAdmin +{ + class Utilities + { + //Get string with specified number of spaces -- really only for visual output + public static String getSpaces(int Num) + { + String SpaceString = String.Empty; + while (Num > 0) + { + SpaceString += ' '; + Num--; + } + + return SpaceString; + } + + //Sleep for x amount of seconds + public static void Wait(double time) + { + Thread.Sleep((int)Math.Ceiling(time*1000)); + } + + //Remove words from a space delimited string + public static String removeWords(String str, int num) + { + String newStr = String.Empty; + String[] tmp = str.Split(' '); + + for (int i = 0; i < tmp.Length; i++) + { + if (i >= num) + newStr += tmp[i] + ' '; + } + + return newStr; + } + + public static Player.Permission matchPermission(String str) + { + String lookingFor = str.ToLower(); + + for (Player.Permission Perm = Player.Permission.User; Perm < Player.Permission.Owner; Perm++) + { + if (lookingFor.Contains(Perm.ToString().ToLower())) + return Perm; + } + + return Player.Permission.Banned; + } + + public static String removeNastyChars(String str) + { + return str.Replace("`", "").Replace("'", "").Replace("\\", "").Replace("\"", "").Replace("^", "").Replace(""", "''"); + } + + public static int GetLineNumber(Exception ex) + { + var lineNumber = 0; + const string lineSearch = ":line "; + var index = ex.StackTrace.LastIndexOf(lineSearch); + if (index != -1) + { + var lineNumberText = ex.StackTrace.Substring(index + lineSearch.Length); + if (int.TryParse(lineNumberText, out lineNumber)) + { + } + } + return lineNumber; + } + } +} diff --git a/IW4M Admin.sln b/IW4M Admin.sln new file mode 100644 index 000000000..a4117879e --- /dev/null +++ b/IW4M Admin.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IW4M ADMIN", "Admin\IW4M ADMIN.csproj", "{DD5DCDA2-51DB-4B1A-922F-5705546E6115}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Debug|Any CPU.Build.0 = Release|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD5DCDA2-51DB-4B1A-922F-5705546E6115}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal