diff --git a/Minecraft Server Starter.sln b/Minecraft Server Starter.sln
new file mode 100644
index 0000000..7f2a264
--- /dev/null
+++ b/Minecraft Server Starter.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.24720.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minecraft Server Starter", "Minecraft Server Starter\Minecraft Server Starter.csproj", "{22E6F114-52F5-447E-82A3-D951E5E82E5F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {22E6F114-52F5-447E-82A3-D951E5E82E5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {22E6F114-52F5-447E-82A3-D951E5E82E5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {22E6F114-52F5-447E-82A3-D951E5E82E5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {22E6F114-52F5-447E-82A3-D951E5E82E5F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Minecraft Server Starter/App.config b/Minecraft Server Starter/App.config
new file mode 100644
index 0000000..37830cc
--- /dev/null
+++ b/Minecraft Server Starter/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/Minecraft Server Starter/App.xaml b/Minecraft Server Starter/App.xaml
new file mode 100644
index 0000000..2261259
--- /dev/null
+++ b/Minecraft Server Starter/App.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Minecraft Server Starter/App.xaml.cs b/Minecraft Server Starter/App.xaml.cs
new file mode 100644
index 0000000..6446100
--- /dev/null
+++ b/Minecraft Server Starter/App.xaml.cs
@@ -0,0 +1,94 @@
+// Made by Lonami Exo (C) LonamiWebs
+// Creation date: february 2016
+// Modifications:
+// - No modifications made
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Windows;
+
+namespace Minecraft_Server_Starter
+{
+ public partial class App : Application
+ {
+ public App()
+ {
+ InitializeComponent();
+
+ SelectCulture(Thread.CurrentThread.CurrentUICulture.ToString());
+
+ Settings.Init("LonamiWebs\\Minecraft Server Starter", new Dictionary
+ {
+ { "eulaAccepted", false },
+ { "minRam", 512 },
+ { "maxRam", 1024 },
+ { "javaPath", Java.FindJavaPath() },
+ { "priority", (int)ProcessPriorityClass.Normal },
+ { "notificationEnabled", true },
+ { "notificationLoc", (int)Toast.Location.TopLeft },
+ { "mssFolder", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "LonamiWebs\\Minecraft Server Starter")},
+ { "ignoreCommandsBlock", true }
+ });
+ }
+
+ public static void SelectCulture(string culture)
+ {
+ // List all our resources
+ List dictionaryList = new List();
+ foreach (ResourceDictionary dictionary in Current.Resources.MergedDictionaries)
+ dictionaryList.Add(dictionary);
+
+ // We want our specific culture
+ string requestedCulture = string.Format("Strings.{0}.xaml", culture);
+ ResourceDictionary resourceDictionary = dictionaryList.FirstOrDefault(d => d.Source.OriginalString == requestedCulture);
+ if (resourceDictionary == null)
+ {
+ requestedCulture = "Strings.xaml";
+ resourceDictionary = dictionaryList.FirstOrDefault(d => d.Source.OriginalString == requestedCulture);
+ }
+
+ // If we have the requested resource, remove it from the list and place at the end.\
+ // Then this language will be our string table to use.
+ if (resourceDictionary != null)
+ {
+ Application.Current.Resources.MergedDictionaries.Remove(resourceDictionary);
+ Application.Current.Resources.MergedDictionaries.Add(resourceDictionary);
+ }
+ // Inform the threads of the new culture
+ Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(culture);
+ Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Minecraft Server Starter/Classes/MinecraftClasses/Backup.cs b/Minecraft Server Starter/Classes/MinecraftClasses/Backup.cs
new file mode 100644
index 0000000..00866ab
--- /dev/null
+++ b/Minecraft Server Starter/Classes/MinecraftClasses/Backup.cs
@@ -0,0 +1,427 @@
+///
+/// Copyright (c) 2016 All Rights Reserved
+///
+/// Lonami Exo
+/// February 2016
+/// Class representing a backup
+
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.Serialization;
+using System.Threading.Tasks;
+using ExtensionMethods;
+using System.Collections.Generic;
+
+using SProperties = Minecraft_Server_Starter.ServerProperties;
+
+namespace Minecraft_Server_Starter
+{
+ [DataContract]
+ public class Backup
+ {
+ #region Constant fields
+
+ // the extension used to save Backup files
+ const string fileExtension = ".info";
+
+ // where the backups are stored
+ static string backupsFolder =>
+ Path.Combine(Settings.GetValue("mssFolder"), "Backups");
+
+ // the base location for this backup
+ string baseLocation => Path.Combine(backupsFolder, ID);
+
+ #endregion
+
+ #region Public properties
+
+ [DataMember]
+ public Server Server { get; set; }
+
+ public string ID { get { return creationDate.ToString(); } }
+ public string DisplayName
+ {
+ get
+ {
+ return Server.Name + " " +
+ CreationDate.ToShortDateString() + " " +
+ CreationDate.ToLongTimeString();
+ }
+ }
+
+ public string Size
+ => new FileInfo(baseLocation + ".zip").Length.ToFileSizeString();
+
+ [DataMember]
+ public bool Worlds { get; set; }
+ [DataMember]
+ public bool ServerProperties { get; set; }
+ [DataMember]
+ public bool WhiteList { get; set; }
+ [DataMember]
+ public bool Ops { get; set; }
+ [DataMember]
+ public bool Banned { get; set; }
+ [DataMember]
+ public bool Logs { get; set; }
+
+ [DataMember]
+ public bool Everything { get; set; }
+
+ public DateTime CreationDate { get { return UnixDate.UnixTimeToDateTime(creationDate); } }
+ [DataMember]
+ long creationDate { get; set; }
+
+ [DataMember]
+ string worldsName { get; set; }
+
+ #endregion
+
+ #region Constructors
+
+ public Backup(Server server) : this(server,
+ true, true, true, true, true, true, true)
+ { }
+
+ public Backup(Server server, bool worlds, bool serverProperties, bool whiteList,
+ bool ops, bool banned, bool logs, bool everything)
+ {
+ Server = server;
+
+ Worlds = worlds;
+ ServerProperties = serverProperties;
+ WhiteList = whiteList;
+ Ops = ops;
+ Banned = banned;
+ Logs = logs;
+
+ Everything = everything;
+ }
+
+ #endregion
+
+ #region Public methods
+
+ ///
+ /// Sets the creation date to now
+ ///
+ public void SetCreation()
+ {
+ creationDate = UnixDate.DateTimeToUnixTime(DateTime.Now);
+ }
+
+ ///
+ /// Generates a new backup from an old backup
+ ///
+ /// The old backup without the creation date set
+ /// The backup with the creation dae set
+ public static Backup Generate(Backup backup)
+ {
+ backup.creationDate = UnixDate.DateTimeToUnixTime(DateTime.Now);
+ return backup;
+ }
+
+ #endregion
+
+ #region Private methods
+
+ // what files do we need to copy from the given folder with the current settings?
+ IEnumerable GetRequiredCopyFiles(string folder)
+ {
+ var propertiesPath = Path.Combine(folder, SProperties.PropertiesName);
+ string tmpFile;
+
+ if (ServerProperties)
+ {
+ yield return propertiesPath;
+ }
+ if (WhiteList)
+ {
+ // new server old server
+ tmpFile = GetIfExistsFile(folder, "whitelist.json", "white-list.txt");
+ if (!string.IsNullOrEmpty(tmpFile))
+ yield return tmpFile;
+ }
+ if (Ops)
+ {
+ // new server old server
+ tmpFile = GetIfExistsFile(folder, "ops.json", "ops.txt");
+ if (!string.IsNullOrEmpty(tmpFile))
+ yield return tmpFile;
+ }
+ if (Banned)
+ {
+ // new server old server
+ tmpFile = GetIfExistsFile(folder, "banned-ips.json", "banned-ips.txt");
+ if (!string.IsNullOrEmpty(tmpFile))
+ yield return tmpFile;
+
+ // new server old server
+ tmpFile = GetIfExistsFile(folder, "banned-players.json", "banned-players.txt");
+ if (!string.IsNullOrEmpty(tmpFile))
+ yield return tmpFile;
+ }
+ if (Logs)
+ {
+ // old server
+ tmpFile = GetIfExistsFile(folder, "server.log");
+ if (!string.IsNullOrEmpty(tmpFile))
+ yield return tmpFile;
+ }
+ }
+
+ // what directories do we need to copy from the given folder with the current settings?
+ IEnumerable GetRequiredCopyDirectories(string folder)
+ {
+ var propertiesPath = Path.Combine(folder, SProperties.PropertiesName);
+ var properties = SProperties.FromFile(propertiesPath);
+
+ if (Worlds)
+ yield return Path.Combine(folder, (worldsName = properties["level-name"].Value));
+
+ if (Logs)
+ {
+ // new server
+ var dir = Path.Combine(folder, "logs");
+ if (Directory.Exists(dir))
+ yield return dir;
+ }
+ }
+
+ // gets the first existing file from one of the given possibilities
+ static string GetIfExistsFile(string folder, params string[] possibilities)
+ {
+ foreach (var possibility in possibilities)
+ if (File.Exists(Path.Combine(folder, possibility)))
+ return Path.Combine(folder, possibility);
+
+ return null;
+ }
+
+ // saves the current backup to the specified file
+ void Save(string file) => Serializer.Serialize(this, file);
+
+ // loads a backup from the specified file
+ static Backup Load(string file) => Serializer.Deserialize(file);
+
+ #endregion
+
+ #region Saving
+
+ ///
+ /// Saves the backup, determining whether the server is running (to backup a copy instead the original) or not
+ ///
+ /// If the server is running, a temporary copy of itself will be created so no errors are thrown
+ /// True if the operation was successful
+ public async Task Save(bool isServerRunning)
+ {
+ SetCreation();
+ bool success = true;
+
+ await Task.Factory.StartNew(() =>
+ {
+ var zipLocation = baseLocation + ".zip";
+ var bakLocation = baseLocation + fileExtension;
+
+ try
+ {
+ if (!Directory.Exists(Path.GetDirectoryName(baseLocation)))
+ Directory.CreateDirectory(Path.GetDirectoryName(baseLocation));
+
+ var folder = Server.Location;
+ var folderName = Path.GetFileName(folder);
+
+ // so we can perform the backup even with server running
+ var tmpFolder = Path.Combine(Path.GetTempPath(),
+ Path.GetFileNameWithoutExtension(Path.GetTempFileName()), folderName);
+
+ if (Everything)
+ {
+ if (isServerRunning) // if the server is open, copy the server and then perform the backup
+ {
+ new DirectoryInfo(folder).CopyDirectory(new DirectoryInfo(tmpFolder));
+
+ ZipFile.CreateFromDirectory(tmpFolder, zipLocation);
+
+ try { Directory.Delete(tmpFolder, true); }
+ catch { };
+ }
+ else // otherwise perform the backup directly
+ {
+ ZipFile.CreateFromDirectory(folder, zipLocation);
+ }
+ }
+ else
+ {
+ using (ZipArchive zip = ZipFile.Open(zipLocation, ZipArchiveMode.Create))
+ {
+ if (isServerRunning)
+ {
+ Directory.CreateDirectory(tmpFolder);
+
+ // copy all the files and directories to a temporary location before adding them to the zip file
+ foreach (var cfile in GetRequiredCopyFiles(folder))
+ {
+ var tmpFile = Path.Combine(tmpFolder, Path.GetFileName(cfile));
+ File.Copy(cfile, tmpFile);
+ zip.AddFile(tmpFile);
+ }
+
+ foreach (var cdir in GetRequiredCopyDirectories(folder))
+ {
+ var tmpDir = Path.Combine(tmpFolder, Path.GetFileName(cdir));
+ new DirectoryInfo(cdir).CopyDirectory(new DirectoryInfo(tmpDir));
+ zip.AddDirectory(tmpDir);
+ }
+
+ // clear the temporary directory
+ try { Directory.Delete(tmpFolder, true); }
+ catch { };
+ }
+ else
+ {
+ // if the server is closed, directly add all the files and directories to the zip
+ foreach (var cfile in GetRequiredCopyFiles(folder))
+ zip.AddFile(cfile);
+
+ foreach (var cdir in GetRequiredCopyDirectories(folder))
+ zip.AddDirectory(cdir);
+ }
+ }
+ }
+
+ // if we got this far, save the backup information
+ Save(baseLocation + fileExtension);
+ }
+ catch // perhaps the server log is being used, or it doesn't exist; clear failed files
+ {
+ if (File.Exists(baseLocation + ".zip"))
+ try { File.Delete(baseLocation + ".zip"); }
+ catch { }
+
+ success = false;
+ }
+ });
+
+ return success;
+ }
+
+ #endregion
+
+ #region Loading
+
+ ///
+ /// Loads a backup
+ ///
+ /// For which server?
+ /// Should the worlds be loaded?
+ /// Should the server properties be loaded?
+ /// Should the white list be loaded?
+ /// Should the op list be loaded?
+ /// Should the banned players list be loaded?
+ /// Should the logs be loaded?
+ /// Should just everything be loaded?
+ public void Load(Server server, bool worlds, bool serverProperties, bool whiteList,
+ bool ops, bool banned, bool logs, bool everything)
+ {
+ // if all the options match, we want to extract everything from the backup
+ bool all = Worlds == worlds &&
+ ServerProperties == serverProperties &&
+ WhiteList == whiteList &&
+ Ops == ops &&
+ Banned == banned &&
+ Logs == logs;
+
+ using (ZipArchive zip = ZipFile.Open(baseLocation + ".zip", ZipArchiveMode.Read))
+ {
+ if (all)
+ zip.ExtractToDirectory(server.Location, true);
+
+ // else, extract only those options which we want
+ else
+ {
+ foreach (var entry in zip.Entries)
+ {
+ var entryName = entry.FullName.Contains("/") ?
+ entry.FullName.Substring(0, entry.FullName.IndexOf('/') + 1) :
+ entry.FullName;
+
+ if (
+ (worlds && entryName == worldsName + "/") ||
+ (logs && (entryName == "logs/" || entryName == "server.log")) ||
+ (serverProperties && entryName == "server.properties") ||
+ (whiteList && (entryName == "whitelist.json" || entryName == "white-list.txt")) ||
+ (ops && (entryName == "ops.json" || entryName == "ops.txt")) ||
+ (banned && (entryName == "banned-ips.json" || entryName == "banned-ips.txt" ||
+ entryName == "banned-players.json" || entryName == "banned-players.txt")))
+ {
+ entry.ExtractToFileSafe(Path.Combine(server.Location, entry.FullName), true);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Retrieves a backup given its id
+ ///
+ /// The id of the backup to retrieve
+ /// The retrieved backup
+ public static Backup GetBackup(string id)
+ {
+ var file = Path.Combine(backupsFolder, id + fileExtension);
+ if (File.Exists(file))
+ return Load(file);
+
+ return null;
+ }
+
+ ///
+ /// Gets all the available backups
+ ///
+ /// What backup name should be shown first?
+ /// All the saved backups
+ public static List GetBackups(string prioritySortingName)
+ {
+ try
+ {
+ if (Directory.Exists(backupsFolder))
+ {
+ var files = Directory.GetFiles(backupsFolder, "*" + fileExtension);
+ var backups = new List(files.Length);
+ foreach (var file in files)
+ backups.Add(Load(file));
+
+ backups.Sort(new BackupSort(prioritySortingName));
+
+ return backups;
+ }
+
+ Directory.CreateDirectory(backupsFolder);
+ }
+ catch { }
+
+ return new List();
+ }
+
+ #endregion
+
+ #region Deleting
+
+ public bool Delete()
+ {
+ if (File.Exists(baseLocation + fileExtension))
+ try { File.Delete(baseLocation + fileExtension); }
+ catch { return false; }
+
+ if (File.Exists(baseLocation + ".zip"))
+ try { File.Delete(baseLocation + ".zip"); }
+ catch { return false; }
+
+ return true;
+ }
+
+ #endregion
+ }
+}
diff --git a/Minecraft Server Starter/Classes/MinecraftClasses/EULA.cs b/Minecraft Server Starter/Classes/MinecraftClasses/EULA.cs
new file mode 100644
index 0000000..b420a95
--- /dev/null
+++ b/Minecraft Server Starter/Classes/MinecraftClasses/EULA.cs
@@ -0,0 +1,40 @@
+///
+/// Copyright (c) 2016 All Rights Reserved
+///
+/// Lonami Exo
+/// February 2016
+/// Class used to prompt Mojang's EULA message
+
+using System.IO;
+
+namespace Minecraft_Server_Starter
+{
+ ///
+ /// Class to interop with Minecraft EULA
+ ///
+ public static class EULA
+ {
+ // a string representing the Minecraft eula
+ const string eulaString =
+@"#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/Minecraft_eula).
+#{0}
+eula=true
+";
+
+ ///
+ /// Create an EULA file if it's set to false or it doesn't exist
+ ///
+ public static void CreateEULA(string dir)
+ {
+ if (string.IsNullOrEmpty(dir))
+ return;//
+
+ if (!Directory.Exists(dir))
+ Directory.CreateDirectory(dir);
+
+ string eula = Path.Combine(dir, "eula.txt");
+ if (!File.Exists(eula) || File.ReadAllText(eula).Contains("eula=false"))
+ File.WriteAllText(eula, string.Format(eulaString, UnixDate.GetUniversalDate()));
+ }
+ }
+}
diff --git a/Minecraft Server Starter/Classes/MinecraftClasses/Heads.cs b/Minecraft Server Starter/Classes/MinecraftClasses/Heads.cs
new file mode 100644
index 0000000..6a608dc
--- /dev/null
+++ b/Minecraft Server Starter/Classes/MinecraftClasses/Heads.cs
@@ -0,0 +1,66 @@
+///
+/// Copyright (c) 2016 All Rights Reserved
+///
+/// Lonami Exo
+/// February 2016
+/// A simple class to get hum... Minecraft players heads from their skins
+
+using ExtensionMethods;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using System.Windows.Media.Imaging;
+
+namespace Minecraft_Server_Starter
+{
+ class Heads
+ {
+ #region Constant fields
+
+ // where the cached heads are stored
+ static string headsFolder =>
+ Path.Combine(Settings.GetValue("mssFolder"), "Heads");
+
+ #endregion
+
+ #region Public methods
+
+ ///
+ /// Return a player's head, and caches it in disk if it didn't exist yet
+ ///
+ /// The username to get the head from
+ /// The player head
+ public static async Task GetPlayerHead(string player)
+ {
+ var file = Path.Combine(headsFolder, player + ".png");
+ if (File.Exists(file))
+ return new BitmapImage(new Uri(file));
+
+ if (!Directory.Exists(headsFolder))
+ Directory.CreateDirectory(headsFolder);
+
+ var skin = await SkinDownloader.GetSkinHead(player);
+ skin.Save(file);
+ return skin;
+ }
+
+ ///
+ /// Clears the player heads from disk. Returns false if some couldn't be deleted
+ ///
+ ///
+ public static bool ClearPlayerHeads()
+ {
+ bool success = true;
+ if (!Directory.Exists(headsFolder)) return success;
+
+ foreach (var head in Directory.GetFiles(headsFolder, "*.png"))
+ {
+ try { File.Delete(head); }
+ catch { success = false; }
+ }
+ return success;
+ }
+
+ #endregion
+ }
+}
diff --git a/Minecraft Server Starter/Classes/MinecraftClasses/MinecraftServer.cs b/Minecraft Server Starter/Classes/MinecraftClasses/MinecraftServer.cs
new file mode 100644
index 0000000..e650722
--- /dev/null
+++ b/Minecraft Server Starter/Classes/MinecraftClasses/MinecraftServer.cs
@@ -0,0 +1,394 @@
+///
+/// Copyright (c) 2016 All Rights Reserved
+///
+/// Lonami Exo
+/// February 2016
+/// Class used to interop with a Minecraft server
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Minecraft_Server_Starter
+{
+ public enum Status { Opening, Open, Closing, Closed }
+
+ public class MinecraftServer
+ {
+ #region Private fields
+
+ #region String analysis regexs
+
+ // two possible formats for time:
+ // yyyy-MM-dd hh:mm:ss
+ // [hh:mm:ss]
+ Regex timeRegex = new Regex(@"(?:\[\d{2}:\d{2}:\d{2}\])|(?:\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})", RegexOptions.Compiled);
+ // three possible formats for type:
+ // [(INFO|WARN|ERROR)]
+ // [Server thread/(INFO|WARN|ERROR)]
+ // [Server Shutdown Thread/INFO]
+ Regex typeRegex = new Regex(@"\[(?:Server(?: Shutdown)? (?:t|T)hread\/)?(INFO|WARN|WARNING|ERROR)\]", RegexOptions.Compiled);
+
+ // only one format: Done (d...
+ Regex loadedRegex = new Regex(@"Done \(\d", RegexOptions.Compiled);
+
+ Regex commandBlockRegex = new Regex(@"\[(.+?)\]", RegexOptions.Compiled);
+ const string serverName = "Server"; // avoid these on command blocks
+
+ Regex playerJoinedRegex = new Regex(@"(\w+) ?\[\/", RegexOptions.Compiled);
+ Regex playerLeftRegex = new Regex(@"(\w+) lost connection", RegexOptions.Compiled);
+
+ // spigot format
+ Regex spigotTimeTypeRegex = new Regex(@"\[(\d{2}:\d{2}:\d{2}) (INFO|WARN|ERROR)\]: ", RegexOptions.Compiled);
+
+ #endregion
+
+ #region Holders
+
+ // status
+ Status _ServerStatus = Status.Closed;
+
+ // the process holding the server
+ Process server;
+ // the streamwriter to the server process
+ StreamWriter input;
+
+ List onlinePlayers = new List();
+
+ #endregion
+
+ #endregion
+
+ #region Public properties
+
+ // returns the current server status
+ public Status ServerStatus
+ {
+ get { return _ServerStatus; }
+ set
+ {
+ var lstValue = _ServerStatus;
+ _ServerStatus = value;
+
+ if (value == Status.Opening && value != lstValue)
+ onServerMessage(Res.GetStr("initializingServer"));
+
+ if (value == Status.Closed && value != lstValue)
+ onServerMessage(Res.GetStr("serverHasClosed"));
+
+ onServerStatusChanged(value);
+ }
+ }
+
+ // selected server
+ public Server Server { get; private set; }
+
+ // online players
+ public List OnlinePlayers { get { return new List(onlinePlayers); } }
+
+ // ignore commands block
+ public bool IgnoreCommandBlocks {
+ get { return Settings.GetValue("ignoreCommandsBlock"); }
+ set { Settings.SetValue("ignoreCommandsBlock", value); }
+ }
+
+ #endregion
+
+ #region Events
+
+ // occurs when the server changes it's status
+ public delegate void ServerStatusChangedEventHandler(Status status);
+ public event ServerStatusChangedEventHandler ServerStatusChanged;
+ void onServerStatusChanged(Status status)
+ {
+ if (ServerStatusChanged != null)
+ ServerStatusChanged(status);
+ }
+
+ // occurs when the server's output receives content
+ public delegate void ServerMessageEventHandler(string time, string type, string typeName, string message);
+ public event ServerMessageEventHandler ServerMessage;
+ void onServerMessage(string msg)
+ {
+ if (ServerMessage != null)
+ {
+ var tuple = analyseMessage(msg);
+ if (tuple != null) // might be invalid input, such as command blocks when they're disabled
+ ServerMessage(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
+ }
+ }
+
+ // occurs when an analysed message represents a player joining/leaving
+ public delegate void PlayerEventHandler(bool joined, string player);
+ public event PlayerEventHandler Player;
+ void onPlayer(bool joined, string player)
+ {
+ if (joined) onlinePlayers.Add(player);
+ else onlinePlayers.Remove(player);
+
+ if (Player != null)
+ Player(joined, player);
+ }
+
+ #endregion
+
+ #region Constructors
+
+ public MinecraftServer(Server server)
+ {
+ Server = server;
+ EULA.CreateEULA(Server.Location);
+ }
+
+ #endregion
+
+ #region Current server management
+
+ // start the server
+ public void Start()
+ {
+ if (ServerStatus != Status.Closed)
+ return; // if it's not closed, we can't "unclose" it because it already is
+
+ // notify server is opening
+ ServerStatus = Status.Opening;
+
+ Server.Use(); // update last use date
+
+ // create a new server process
+ server = Process.Start(getPsi());
+
+ // attach event handlers
+ server.OutputDataReceived += server_OutputDataReceived;
+ server.ErrorDataReceived += server_OutputDataReceived;
+ server.Exited += server_Exited;
+
+ // set streamwriter to it's stdin
+ input = server.StandardInput;
+
+ // begin read async
+ server.BeginOutputReadLine();
+ server.BeginErrorReadLine();
+ }
+
+ // kill the server
+ public void Kill()
+ {
+ if (ServerStatus == Status.Closed)
+ return; // already closed, return
+
+ // stop receiving poop
+ server.OutputDataReceived -= server_OutputDataReceived;
+ server.ErrorDataReceived -= server_OutputDataReceived;
+ server.Exited -= server_Exited;
+
+ // DIE, DIE!! MWAHAHAHA... right
+ server.Kill();
+ ServerStatus = Status.Closed;
+ }
+
+ #endregion
+
+ #region Commands
+
+ // send a command
+ public void SendCommand(string command) { input.WriteLine(command); }
+
+ // stop (saving first)
+ public void Stop() { SendCommand("stop"); }
+
+ // say something!
+ public void Say(string msg) { SendCommand("say " + msg); }
+
+ // kick a noob
+ public void Kick(string player) { SendCommand("kick " + player); }
+
+ // ban a very noob
+ public void Ban(string player) { SendCommand("ban " + player); }
+
+ // pardon (unban) an innocent noob
+ public void Pardon(string player) { SendCommand("pardon " + player); }
+
+ // op a pro player
+ public void Op(string player) { SendCommand("op " + player); }
+
+ // deop a traitor
+ public void Deop(string player) { SendCommand("deop " + player); }
+
+ // toggle rain
+ public void ToggleRain() { SendCommand("toggledownfall"); }
+
+ // invoke the sun
+ public void SetDay() { SendCommand("time set 0"); }
+
+ // invoke the moon
+ public void SetNight() { SendCommand("time set 14000"); }
+
+ // save the world
+ public void Save() { SendCommand("save-all"); }
+
+ #endregion
+
+ #region Server output handling
+
+ // the server wrote something to it's stdout
+ void server_OutputDataReceived(object sender, DataReceivedEventArgs e)
+ {
+ if (e.Data == null)
+ {
+ ServerStatus = Status.Closed;
+ return;
+ }
+ else
+ onServerMessage(e.Data);
+ }
+
+ // oops, someone died, not me (._ . ")
+ void server_Exited(object sender, EventArgs e)
+ {
+ ServerStatus = Status.Closed;
+ }
+
+ #endregion
+
+ #region Server messages analysis
+
+ // return a tuple formed by