diff options
Diffstat (limited to 'ShiftOS_TheReturn')
33 files changed, 4292 insertions, 1074 deletions
diff --git a/ShiftOS_TheReturn/AppLauncherDaemon.cs b/ShiftOS_TheReturn/AppLauncherDaemon.cs index 7ef34c1..716a6a3 100644 --- a/ShiftOS_TheReturn/AppLauncherDaemon.cs +++ b/ShiftOS_TheReturn/AppLauncherDaemon.cs @@ -34,8 +34,17 @@ using ShiftOS.Objects.ShiftFS; namespace ShiftOS.Engine { + /// <summary> + /// Provides functionality for pulling data about the App Launcher. + /// </summary> public static class AppLauncherDaemon { + /// <summary> + /// Extension method that allows you to determine if a list of <see cref="AssemblyName"/>s contains a given name. + /// </summary> + /// <param name="asms">The list of assembly names</param> + /// <param name="name">The name to look for.</param> + /// <returns>Whether or not the name was found in the list.</returns> public static bool Contains(this AssemblyName[] asms, string name) { foreach(var asm in asms) @@ -46,6 +55,10 @@ namespace ShiftOS.Engine return false; } + /// <summary> + /// Pulls a list of all available App Launcher items. + /// </summary> + /// <returns>A <see cref="List{LauncherItem}"/> containing all available App Launcher items.</returns> public static List<LauncherItem> Available() { List<LauncherItem> win = new List<LauncherItem>(); @@ -64,14 +77,29 @@ namespace ShiftOS.Engine { if (type.GetInterfaces().Contains(typeof(IShiftOSWindow))) { + bool isAllowed = true; foreach (var attr in type.GetCustomAttributes(false)) { - if (attr is LauncherAttribute) + if(attr is MultiplayerOnlyAttribute) { - var launch = attr as LauncherAttribute; - if (launch.UpgradeInstalled) + if(KernelWatchdog.MudConnected == false) { - win.Add(new LauncherItem { DisplayData = launch, LaunchType = type }); + isAllowed = false; + + } + } + if (isAllowed == true) + { + if (attr is LauncherAttribute) + { + if (Shiftorium.UpgradeAttributesUnlocked(type)) + { + var launch = attr as LauncherAttribute; + if (launch.UpgradeInstalled) + { + win.Add(new LauncherItem { DisplayData = launch, LaunchType = type }); + } + } } } } @@ -99,20 +127,39 @@ namespace ShiftOS.Engine } + /// <summary> + /// Provides a data object for app launcher items + /// </summary> public class LauncherItem { + /// <summary> + /// Display data including icons, names, and the category of the item. + /// </summary> public LauncherAttribute DisplayData { get; internal set; } + /// <summary> + /// A .NET <see cref="Type"/> that is associated with this item. + /// </summary> public Type LaunchType { get; internal set; } } + /// <summary> + /// Provides the ability to run Lua scripts from the App Launcher. + /// </summary> public class LuaLauncherItem : LauncherItem { + /// <summary> + /// Creates a new instance of the <see cref="LuaLauncherItem"/>. + /// </summary> + /// <param name="file">A script file to run when the item is activated.</param> public LuaLauncherItem(string file) { LaunchPath = file; } + /// <summary> + /// Gets or sets the launch path of this App Launcher item. + /// </summary> public string LaunchPath { get; private set; } } } diff --git a/ShiftOS_TheReturn/AppearanceManager.cs b/ShiftOS_TheReturn/AppearanceManager.cs index dc88092..7cf06c9 100644 --- a/ShiftOS_TheReturn/AppearanceManager.cs +++ b/ShiftOS_TheReturn/AppearanceManager.cs @@ -38,6 +38,7 @@ using static ShiftOS.Engine.SaveSystem; namespace ShiftOS.Engine { + // Provides functionality for managing windows within ShiftOS. public static class AppearanceManager { [Obsolete("Please use Localization.GetAllLanguages().")] @@ -46,46 +47,21 @@ namespace ShiftOS.Engine return Localization.GetAllLanguages(); } - public static void AddFocusEvents(Control ctrl, Control child) - { - child.Enter += (o, a) => - { - ctrl.BringToFront(); - }; - child.MouseDown += (o, a) => - { - ctrl.BringToFront(); - }; - - foreach (Control c in child.Controls) - { - c.Enter += (o, a) => - { - ctrl.BringToFront(); - }; - c.MouseDown += (o, a) => - { - ctrl.BringToFront(); - }; - - try - { - AddFocusEvents(ctrl, c); - } - catch { } - } - } - + // Sets the title text of the specified window. public static void SetWindowTitle(IShiftOSWindow window, string title) { + if (window == null) + throw new ArgumentNullException("window", "The window cannot be null."); winmgr.SetTitle(window, title); } + //HEY LETS FIND THE WINDOWS public static IEnumerable<Type> GetAllWindowTypes() { List<Type> types = new List<Type>(); foreach(var file in System.IO.Directory.GetFiles(Environment.CurrentDirectory)) { + // hey if a thing is an exe or a dll show up plz kthx if(file.EndsWith(".exe") || file.EndsWith(".dll")) { try @@ -103,8 +79,11 @@ namespace ShiftOS.Engine return types; } + // hey you know that window we just made appear? well give it its title public static string GetDefaultTitle(Type winType) { + if (winType == null) + throw new ArgumentNullException("winType"); foreach(var attrib in winType.GetCustomAttributes(false)) { if(attrib is DefaultTitleAttribute) @@ -115,140 +94,155 @@ namespace ShiftOS.Engine return winType.Name; } - public static string LastTerminalText { get; set; } + // Current cursor position of the console public static int CurrentPosition { get; set; } + + // We don't know what this does. It may be gone if it does nothing. public static int LastLength { get; set; } + // Minimize a window. public static void Minimize(IWindowBorder form) { + if (form == null) + //FUCK WHY THE FUCK IS THIS NULL + throw new ArgumentNullException("form"); + if (winmgr == null) + //FUCK THIS PART OF THE ENGINE WASNT TURNED ON YET + throw new EngineModuleDisabledException(); winmgr.Minimize(form); } + // Maximizes a window! :D public static void Maximize(IWindowBorder form) { + if (form == null) + //AHHHH SHOULDNT BE NULLLLLL + throw new ArgumentNullException("form"); + if (winmgr == null) + //WHY ARE YOU DOING THIS PART OF THE ENGINE IT WASNT ENABLED FUCK + throw new EngineModuleDisabledException(); winmgr.Maximize(form); } - + // Provides a list of all open ShiftOS windows. public static List<IWindowBorder> OpenForms = new List<IWindowBorder>(); - public static bool CanOpenWindow(IShiftOSWindow form) - { -#if !MUD_RAPIDDEV - if (ServerManager.IsSingleplayer) - { - foreach (var attr in form.GetType().GetCustomAttributes(false)) - { - if (attr is MultiplayerOnlyAttribute) - return false; - } - } -#endif - return true; - } - + // Decorates a window with a border, then shows the window. public static void SetupWindow(IShiftOSWindow form) { + if (form == null) + //YOU GET THE POINT THIS REALLY SHOULDNT BE NULL + throw new ArgumentNullException("form"); + if (winmgr == null) + //SAME HERE + throw new EngineModuleDisabledException(); winmgr.SetupWindow(form); Desktop.ResetPanelButtons(); } + // Closes the specified window. SHOCKED YOU ARE I KNOW, HOW COULD YOU HAVE GUESSED public static void Close(IShiftOSWindow win) { + if (win == null) + //NOPE SHOULDNT BE NULL + throw new ArgumentNullException("win"); + if (winmgr == null) + //WHY IS THIS NULL + throw new EngineModuleDisabledException(); winmgr.Close(win); Desktop.ResetPanelButtons(); } + + // Decorates a window with a border, then shows the window, as a dialog box. public static void SetupDialog(IShiftOSWindow form) { + if (form == null) + //NULLLLLLLLL + throw new ArgumentNullException("form"); + if (winmgr == null) + //ASGDFHASDGF + throw new EngineModuleDisabledException(); winmgr.SetupDialog(form); Desktop.ResetPanelButtons(); } + // The underlying window manager for this engine module private static WindowManager winmgr = null; - public static double Measure(this string text, Font font) - { - return Graphics.FromImage(new Bitmap(1, 1)).MeasureString(text, font).Width; - } - + // Initiate this engine module, and perform mandatory configuration. public static void Initiate(WindowManager mgr) { - winmgr = mgr; + winmgr = mgr; // A working, configured window manager to use as a backend for this module } - [Obsolete("This is a stub.")] - public static void DoWinformsSkinningMagicOnWpf(this UserControl ctrl) - { - //SetupControls(ctrl); - } + // Raised when the engine is entering its shutdown phase. Save your work! public static event EmptyEventHandler OnExit; + // Starts the engine's exit routine, firing the OnExit event. internal static void Exit() { OnExit?.Invoke(); //disconnect from MUD ServerManager.Disconnect(); + Desktop.InvokeOnWorkerThread(() => + { + Process.GetCurrentProcess().Kill(); //bye bye + }); } - - internal static bool BordersHidden(Form frm) - { - string t = frm.Tag as string; - if (t == null) - return false; - - return t.Contains("hidden"); - } - + // The current terminal body control. public static ITerminalWidget ConsoleOut { get; set; } + // Redirects the .NET to a new TerminalTextWriter instance. public static void StartConsoleOut() { - Console.SetOut(new TerminalTextWriter()); + Console.SetOut(new TerminalTextWriter()); //"plz start writing text .NET kthx" } + // Invokes an action on the window management thread. public static void Invoke(Action act) { winmgr.InvokeAction(act); } } + // Provides the base functionality for a ShiftOS terminal. public interface ITerminalWidget { - void Write(string text); - void WriteLine(string text); - void Clear(); - void SelectBottom(); + void Write(string text); // Actually write text to this Terminal! :D:D:D:D + void WriteLine(string text); // Write text to this Terminal, followed by a newline. + void Clear(); // Clear the contents of this Terminal, i bet you wouldve never guessed that + void SelectBottom(); // Move the cursor to the last character in the Terminal. } + // makes the window manager actually do its job public abstract class WindowManager { - public abstract void Minimize(IWindowBorder border); - public abstract void Maximize(IWindowBorder border); - - public abstract void Close(IShiftOSWindow win); - - public abstract void SetupWindow(IShiftOSWindow win); - public abstract void SetupDialog(IShiftOSWindow win); - - public abstract void InvokeAction(Action act); - - public abstract void SetTitle(IShiftOSWindow win, string title); + public abstract void Minimize(IWindowBorder border); // guess what this does + public abstract void Maximize(IWindowBorder border); // ooh this too + public abstract void Close(IShiftOSWindow win); // omg this probably does something + public abstract void SetupWindow(IShiftOSWindow win); // i cant think of what this does + public abstract void SetupDialog(IShiftOSWindow win); // how about this??????? + public abstract void InvokeAction(Action act); // i wonder what this invokes + public abstract void SetTitle(IShiftOSWindow win, string title); // what is a title again } + // Provides the base functionality for a typical ShiftOS window border, what did you expect public interface IWindowBorder { - void Close(); - string Text { get; set; } - IShiftOSWindow ParentWindow { get; set; } + void Close(); // CLOSES THE BORDER ALONG WITH ITS WINDOW!!!!!!! HOLY SHIT I DIDNT EXPECT THAT + string Text { get; set; } // title text exists now + IShiftOSWindow ParentWindow { get; set; } // Gets or sets the underlying for this border. } - + + // Provides a way of setting default title text for classes. public class DefaultTitleAttribute : Attribute { + // oy if you cant find a title this is the one you should use public DefaultTitleAttribute(string title) { Title = title; @@ -257,4 +251,13 @@ namespace ShiftOS.Engine public string Title { get; private set; } } + // An exception that is thrown when mandatory configuration to run a specific method or module hasn't been done yet. + public class EngineModuleDisabledException : Exception + { + // FUCK WE DIDNT ORDER THINGS RIGHT + public EngineModuleDisabledException() : base("This engine module has not yet been enabled.") + { + //FUCK + } + } } diff --git a/ShiftOS_TheReturn/AudioManager.cs b/ShiftOS_TheReturn/AudioManager.cs index a548cf6..553a1d9 100644 --- a/ShiftOS_TheReturn/AudioManager.cs +++ b/ShiftOS_TheReturn/AudioManager.cs @@ -22,10 +22,11 @@ * SOFTWARE. */ -//#define NOSOUND +#define NOSOUND using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading; @@ -40,68 +41,84 @@ namespace ShiftOS.Engine private static WaveOut _out = null; private static AudioFileReader _reader = null; private static IAudioProvider _provider = null; - private static bool _running = true; - - public static void Init(IAudioProvider _p) + + /// <summary> + /// Stops the current sound if one is playing and disposes of the sound. + /// </summary> + public static void Stop() { -#if !NOSOUND - _provider = _p; - AppearanceManager.OnExit += () => + Desktop.InvokeOnWorkerThread(() => { - _running = false; _out?.Stop(); _reader?.Dispose(); _out?.Dispose(); - System.IO.File.Delete("temp.mp3"); - }; - var t = new Thread(() => - { - SaveSystem.GameReady += () => - { - while(_out == null) - { - - } - _out.Volume = _provider.Volume; - }; - Random rnd = new Random(); - while(_running == true) - { - int track = rnd.Next(0, _provider.Count); - byte[] mp3 = _provider.GetTrack(track); - System.IO.File.WriteAllBytes("temp.mp3", mp3); - _reader = new AudioFileReader("temp.mp3"); - _out = new WaveOut(); - _out.Init(_reader); - _out.Volume = _provider.Volume; - - _out.Play(); - while(_out.PlaybackState == PlaybackState.Playing) - { - Thread.Sleep(5000); //even when the player isn't playing, this will give a good delay between songs. - } - _reader.Dispose(); - _out.Dispose(); - } }); - t.IsBackground = true; - t.Start(); -#endif } + /// <summary> + /// Initiates this engine module using an <see cref="IAudioProvider"/> as a backend for selecting background soundtrack as well as the volume level for the sound. + /// </summary> + /// <param name="_p">A background soundtrack and volume provider.</param> + /// <exception cref="ArgumentNullException">Thrown if <paramref name="_p"/> is null.</exception> + public static void Init(IAudioProvider _p) + { + if (_p == null) + throw new ArgumentNullException("_p"); + _provider = _p; + } + + /// <summary> + /// Sets the volume of the audio system. + /// </summary> + /// <param name="volume">The volume to use, from 0 to 1.</param> public static void SetVolume(float volume) { _provider.Volume = volume; //persist between songs _out.Volume = volume; } - internal static void Kill() + /// <summary> + /// Plays a specified sound file. + /// </summary> + /// <param name="file">The file to play.</param> + public static void Play(string file) + { + try + { + _reader = new AudioFileReader(file); + _out = new WaveOut(); + _out.Init(_reader); + _out.Play(); + if (SaveSystem.CurrentSave == null) + _out.Volume = 1.0f; + else + _out.Volume = (float)SaveSystem.CurrentSave.MusicVolume / 100; + _out.PlaybackStopped += (o, a) => { PlayCompleted?.Invoke(); }; + } + catch(Exception ex) + { + Console.WriteLine("Audio error: " + ex.Message); + } + } + + /// <summary> + /// Writes the data in the specified <see cref="Stream"/> to a file, and plays it as a sound file. + /// </summary> + /// <param name="str">The stream to read from.</param> + public static void PlayStream(Stream str) { - _running = false; - _out.Stop(); - _out.Dispose(); - _reader.Dispose(); + var bytes = new byte[str.Length]; + str.Read(bytes, 0, bytes.Length); + ShiftOS.Engine.AudioManager.Stop(); + if (File.Exists("snd.wav")) + File.Delete("snd.wav"); + File.WriteAllBytes("snd.wav", bytes); + + ShiftOS.Engine.AudioManager.Play("snd.wav"); + } + + public static event Action PlayCompleted; } public interface IAudioProvider diff --git a/ShiftOS_TheReturn/Command.cs b/ShiftOS_TheReturn/Command.cs index 85da6ca..09cf206 100644 --- a/ShiftOS_TheReturn/Command.cs +++ b/ShiftOS_TheReturn/Command.cs @@ -30,17 +30,53 @@ using System.Threading.Tasks; namespace ShiftOS.Engine { + /// <summary> + /// Denotes that the following terminal command or namespace must only be used in an elevated environment. + /// </summary> + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] + public class KernelModeAttribute : Attribute + { + + } + + /// <summary> + /// Denotes that the following public static method is a ShiftOS Terminal command. + /// </summary> + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class Command : Attribute { + /// <summary> + /// The command name. + /// </summary> public string name; + /// <summary> + /// The description of the command. + /// </summary> public string description = ""; + /// <summary> + /// The usage flags for the command. + /// </summary> public string usage = ""; + /// <summary> + /// Should the command be hidden from the help system? + /// </summary> public bool hide = false; + /// <summary> + /// Creates a new instance of the <see cref="Command"/>. + /// </summary> + /// <param name="name">The name of the command.</param> public Command(string name) { this.name = name; } + + + /// <summary> + /// Creates a new instance of the <see cref="Command"/>. + /// </summary> + /// <param name="name">The name of the command.</param> + /// <param name="hide">Whether the command should be hidden from the help system.</param> public Command(string name, bool hide) { this.name = name; @@ -54,10 +90,21 @@ namespace ShiftOS.Engine } } + /// <summary> + /// Denotes that this function or property is dependent on a Shiftorium upgrade. + /// </summary> + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] public class RequiresUpgradeAttribute : Attribute { + /// <summary> + /// Gets or sets the upgrade(s) this attribute requires. + /// </summary> public string Upgrade { get; set; } - public bool Installed + + /// <summary> + /// Gets whether the dependent upgrade(s) are installed. + /// </summary> + public virtual bool Installed { get { @@ -88,14 +135,35 @@ namespace ShiftOS.Engine } } + /// <summary> + /// Denotes a Terminal command namespace. + /// </summary> public class Namespace : Attribute { + /// <summary> + /// The namespace's name. + /// </summary> public string name; + /// <summary> + /// Whether the namespace should be hidden from the help system. Overrides all child <see cref="Command"/>s' hide values. + /// </summary> public bool hide; + + /// <summary> + /// Creates a new instance of the <see cref="Namespace"/>. + /// </summary> + /// <param name="n">The name of the namespace.</param> public Namespace(string n) { name = n; } + + + /// <summary> + /// Creates a new instance of the <see cref="Namespace"/>. + /// </summary> + /// <param name="n">The name of the namespace.</param> + /// <param name="hide">Whether this namespace should be hidden from the user.</param> public Namespace(string n, bool hide) { name = n; @@ -103,13 +171,34 @@ namespace ShiftOS.Engine } } + /// <summary> + /// Marks a Terminal command as obsolete. + /// </summary> [AttributeUsage(AttributeTargets.Method)] public class CommandObsolete : Attribute { + /// <summary> + /// The reason of obsolescence. + /// </summary> public string reason; + + /// <summary> + /// If a new command has the same functionality, this is it. + /// </summary> public string newcommand; + + /// <summary> + /// Should we warn the user when they run this command? + /// </summary> public bool warn; + + /// <summary> + /// Creates a new instance of the <see cref="CommandObsolete"/> class. + /// </summary> + /// <param name="reason">The reason for this command's obsolescence. You can use "%n" in place of the new command name.</param> + /// <param name="newcommand">If a new command is set to take over, specify it here.</param> + /// <param name="warn">Whether or not we should warn the user when they run the command.</param> public CommandObsolete(string reason, string newcommand, bool warn) { this.reason = reason; // %n for newcommand @@ -118,11 +207,21 @@ namespace ShiftOS.Engine } } + /// <summary> + /// Denotes that this command requires a specified argument to be in its argument dictionary. + /// </summary> [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class RequiresArgument : Attribute { + /// <summary> + /// The argument name + /// </summary> public string argument; + /// <summary> + /// Creates a new instance of the <see cref="RequiresArgument"/> attribute + /// </summary> + /// <param name="argument">The argument name associated with this attribute</param> public RequiresArgument(string argument) { this.argument = argument; @@ -137,12 +236,11 @@ namespace ShiftOS.Engine } } + /// <summary> + /// Prevents a command from being run in a remote session. + /// </summary> [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class RemoteLockAttribute : Attribute { - /// <summary> - /// When used on a terminal command, it will prevent that command from being invoked by a remote user (over MUD terminal forwarding). - /// </summary> - public RemoteLockAttribute() { } } } diff --git a/ShiftOS_TheReturn/CommandParser.cs b/ShiftOS_TheReturn/CommandParser.cs new file mode 100644 index 0000000..868d27a --- /dev/null +++ b/ShiftOS_TheReturn/CommandParser.cs @@ -0,0 +1,479 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace ShiftOS.Engine +{ + /// <summary> + /// Static abstraction layer for the current command parser + /// </summary> + public static class CurrentCommandParser + { + /// <summary> + /// The current parser + /// </summary> + public static CommandParser parser; + } + + /// <summary> + /// Provides functionality for parsing a Terminal command string + /// </summary> + public sealed class CommandParser + { + public IList<CommandFormat> parts = new List<CommandFormat>(); + + /// <summary> + /// Adds a command format to the parser + /// </summary> + /// <param name="part">The format to add</param> + public void AddPart(CommandFormat part) + { + parts.Add(part); + } + + /// <summary> + /// Imports the command parser formats from a list. + /// </summary> + /// <param name="parts">The list of formats.</param> + public void ImportPart(IList<CommandFormat> parts) + { + this.parts = parts; + } + + /// <summary> + /// Saves the parser to a JSON string. + /// </summary> + /// <returns></returns> + public string Save() + { + return JsonConvert.SerializeObject(parts, Formatting.Indented); + } + + /// <summary> + /// Loads a new <see cref="CommandParser"/> from a file. + /// </summary> + /// <param name="val">The file to load</param> + /// <returns>The parser constructed from the file</returns> + public static CommandParser Load(string val) + { + CommandParser parser = new CommandParser(); + JArray data = JArray.Parse(val); + + IList<CFValue> values = data.Select(obj => new CFValue( + (string)obj["type"], + (string)obj["text"] + )).ToList(); + + foreach (CFValue value in values) + { + parser.AddPart(value.GetCommandFormat()); + } + + return parser; + } + + /// <summary> + /// Parses a command string. + /// </summary> + /// <param name="cdd">The command string to parse.</param> + /// <returns>The parsed command, ready to be invoked.</returns> + public KeyValuePair<KeyValuePair<string, string>, Dictionary<string, string>> ParseCommand(string cdd) + { + string command = ""; + string ns = ""; + Dictionary<string, string> arguments = new Dictionary<string, string>(); + + string text = cdd; + int position = 0; + + int commandPos; + int firstValuePos = -1; + int lastValuePos = -1; + + for (int ii = 0; ii < parts.Count; ii++) + { + CommandFormat part = parts[ii]; + if (part is CommandFormatMarker) + { + if (part is CommandFormatCommand) + { + commandPos = ii; + } + else if (part is CommandFormatValue) + { + if (firstValuePos > -1) + lastValuePos = ii; + else + firstValuePos = ii; + } + } + } + + int i = 0; + string currentArgument = ""; + int help = -1; + + while (position < text.Length) + { + + if (i >= parts.Count) + { + position = text.Length; + command = "+FALSE+"; + i = 0; + } + + CommandFormat part = parts[i]; + string res = part.CheckValidity(text.Substring(position)); + + // ok so: + + // example + // COMMAND text[ --] ARGUMENT VALUE text[ --] ARGUMENT VALUE + // COMMAND text[{] ARGUMENT text[=] VALUE text[, ] ARGUMENT text[=] VALUE text[}] + + if (part is CommandFormatMarker) + { + if (part is CommandFormatNamespace) + { + ns = res; + help = -1; + } + else if (part is CommandFormatCommand) + { + command = res; + help = -1; + } + else if (part is CommandFormatArgument) + { + currentArgument = res; + help = -1; + } + else if (part is CommandFormatValue) + { + arguments[currentArgument] = string.Join("", res.Split('"')); + + if (i == firstValuePos) + help = lastValuePos; + if (i == lastValuePos) + help = firstValuePos; + } + } + + if (res == "+FALSE+") + { + if (help > -1) + { + i = help; + if (i >= parts.Count) + { + position = text.Length; + command = "+FALSE+"; + } + } + else + { + position = text.Length; + command = "+FALSE+"; + } + help = -1; + } + else + { + position += res.Length; + } + + i++; + } + + if (command == "+FALSE+") + { + //lblExampleCommand.Text = "Syntax Error"; + return new KeyValuePair<KeyValuePair<string, string>, Dictionary<string, string>>(); + } + else + { + /*string argvs = "{"; + + foreach (KeyValuePair<string, string> entry in arguments) { + argvs += entry.Key + "=" + entry.Value + ", "; + } + + argvs += "}"; + + lblExampleCommand.Text = command + argvs;*/ + return new KeyValuePair<KeyValuePair<string, string>, Dictionary<string, string>>(new KeyValuePair<string, string>(ns, command), arguments); + } + } + } + + public class CFValue + { + public string type { get; set; } + + public string text { get; set; } + + public CFValue(string type, string text) + { + this.type = type; + this.text = text; + } + + public CFValue(CommandFormat format) + { + type = ""; + text = ""; + if (format is CommandFormatText) + { + text = ((CommandFormatText)format).str; + if (format is CommandFormatOptionalText) + { + type = "optionalText"; + } + else if (format is CommandFormatRegex) + { + type = "regexText"; + } + else + { + type = "text"; + } + } + else if (format is CommandFormatMarker) + { + if (format is CommandFormatNamespace) + { + type = "namespace"; + } + else if (format is CommandFormatCommand) + { + type = "command"; + } + else if (format is CommandFormatArgument) + { + type = "argument"; + } + else if (format is CommandFormatValue) + { + type = "value"; + } + } + } + + public CommandFormat GetCommandFormat() + { // TODO update with better code + switch (type) + { + case "text": + return new CommandFormatText(text); + case "optionalText": + return new CommandFormatOptionalText(text); + case "regexText": + return new CommandFormatRegex(text); + case "namespace": + return new CommandFormatNamespace(); + case "command": + return new CommandFormatCommand(); + case "argument": + return new CommandFormatArgument(); + case "value": + return new CommandFormatValue(); + case "color": + throw new NotImplementedException(); // fix this (make it not a notimplementedexception) + } + return new CommandFormatMarker(); + } + } + + + public interface CommandFormat + { + string CheckValidity(string check); + Control Draw(); + } + public class CommandFormatText : CommandFormat + { + public string str = ""; + TextBox textBox; + + public CommandFormatText() + { + + } + + public CommandFormatText(string str) + { + this.str = str; + } + + public virtual string CheckValidity(string check) + { + return check.StartsWith(str) ? str : "+FALSE+"; + } + + public Control Draw() + { + textBox = new TextBox(); + textBox.TextChanged += new EventHandler(TextChanged); + textBox.Location = new Point(0, 0); + textBox.Text = str; + + return textBox; + } + + void TextChanged(object sender, EventArgs e) + { + str = textBox.Text; + } + } + + public class CommandFormatOptionalText : CommandFormatText + { + public CommandFormatOptionalText() : base() + { + } + public CommandFormatOptionalText(string str) : base(str) + { + } + + public override string CheckValidity(string check) + { + return check.StartsWith(str) ? str : ""; + } + } + + public class CommandFormatRegex : CommandFormatText + { + public CommandFormatRegex() : base() + { + } + public CommandFormatRegex(string str) : base(str) + { + } + + public override string CheckValidity(string check) + { + Match match = (new Regex("^" + str)).Match(check); + return match.Success ? match.Value : "+FALSE+"; + } + } + + public class CommandFormatMarker : CommandFormat + { + protected string str; + Button button; + + public CommandFormatMarker() + { + } + + public virtual string CheckValidity(string check) + { + string res = string.Empty; + string alphanumeric = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"; // not using regex for performance reasons + + foreach (char c in check) + { + if (alphanumeric.IndexOf(c) > -1) + { + res += c; + } + else + { + break; + } + } + + return res; + } + + public virtual Control Draw() + { + button = new Button(); + button.Location = new Point(0, 0); + button.Text = "Marker"; + + return button; + } + } + + public class CommandFormatCommand : CommandFormatMarker + { + public override Control Draw() + { + Button draw = (Button)base.Draw(); + draw.Text = "Command"; + return draw; + } + } + + public class CommandFormatNamespace : CommandFormatMarker + { + public override Control Draw() + { + Button draw = (Button)base.Draw(); + draw.Text = "Namespace"; + return draw; + } + } + + public class CommandFormatArgument : CommandFormatMarker + { + public override Control Draw() + { + Button draw = (Button)base.Draw(); + draw.Text = "Argument"; + return draw; + } + } + + public class CommandFormatValue : CommandFormatMarker + { + public override string CheckValidity(string cd) + { + string res = string.Empty; + var check = ""; + bool done = false; + + if (cd.StartsWith("\"")) + { + check = cd.Substring(1); + + foreach (char c in check) + { + if (c != '"') + { + res += c; + } + else + { + done = true; + res = "\"" + res + "\""; + break; + } + } + } + else + { + res = base.CheckValidity(cd); + done = true; + } + return done ? res : "+FALSE+"; + } + + public override Control Draw() + { + Button draw = (Button)base.Draw(); + draw.Text = "\"Value\""; + return draw; + } + } +} diff --git a/ShiftOS_TheReturn/Commands.cs b/ShiftOS_TheReturn/Commands.cs index 9bc876f..b97cd1d 100644 --- a/ShiftOS_TheReturn/Commands.cs +++ b/ShiftOS_TheReturn/Commands.cs @@ -73,7 +73,7 @@ namespace ShiftOS.Engine TerminalBackend.IsForwardingConsoleWrites = forwarding; TerminalBackend.ForwardGUID = (forwarding == true) ? fGuid : null; string resultFriendly = (result == true) ? "yes" : "no"; - Console.WriteLine($"{SaveSystem.CurrentSave.Username} says {resultFriendly}."); + Console.WriteLine($"{SaveSystem.CurrentUser.Username} says {resultFriendly}."); TerminalBackend.IsForwardingConsoleWrites = false; }; Desktop.InvokeOnWorkerThread(new Action(() => @@ -95,7 +95,7 @@ namespace ShiftOS.Engine { TerminalBackend.IsForwardingConsoleWrites = forwarding; TerminalBackend.ForwardGUID = (forwarding == true) ? fGuid : null; - Console.WriteLine($"{SaveSystem.CurrentSave.Username} says \"{result}\"."); + Console.WriteLine($"{SaveSystem.CurrentUser.Username} says \"{result}\"."); TerminalBackend.IsForwardingConsoleWrites = false; }; Desktop.InvokeOnWorkerThread(new Action(() => @@ -133,6 +133,7 @@ namespace ShiftOS.Engine [Namespace("mud")] public static class MUDCommands { + [MultiplayerOnly] [Command("status")] public static bool Status() { @@ -155,6 +156,8 @@ namespace ShiftOS.Engine { Console.WriteLine("{ERROR}: " + ex.Message); } + + TerminalBackend.PrefixEnabled = false; return true; } catch (Exception ex) @@ -164,49 +167,37 @@ namespace ShiftOS.Engine } } - - } - - [RequiresUpgrade("mud_fundamentals")] - [Namespace("chat")] - public static class ChatCommands - { - [RequiresArgument("id")] - [RequiresArgument("name")] - [RequiresArgument("topic")] - [Command("create")] - public static bool CreateChat(Dictionary<string, object> args) + [Command("reconnect")] + [RequiresUpgrade("hacker101_deadaccts")] + public static bool Reconnect() { - string id = ""; - string topic = ""; - string name = ""; - int max_users = 0; - - id = args["id"] as string; - name = args["topic"] as string; - topic = args["name"] as string; - - bool valid = true; - - if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(topic)) - valid = false; + Console.WriteLine("--reconnecting to multi-user domain..."); + KernelWatchdog.MudConnected = true; + Console.WriteLine("--done."); + return true; + } - if (valid) - { - ServerManager.SendMessage("chat_create", $@"{{ - id: ""{id}"", - name: ""{name}"", - topic: ""{topic}"", - max_users: {max_users} -}}"); - } - else - { - Console.WriteLine("{CHAT_PLEASE_PROVIDE_VALID_CHANNEL_DATA}"); - } + [MultiplayerOnly] + [Command("disconnect")] + [RequiresUpgrade("hacker101_deadaccts")] + public static bool Disconnect() + { + Console.WriteLine("--connection to multi-user domain severed..."); + KernelWatchdog.MudConnected = false; return true; } + [MultiplayerOnly] + [Command("sendmsg")] + [KernelMode] + [RequiresUpgrade("hacker101_deadaccts")] + [RequiresArgument("header")] + [RequiresArgument("body")] + public static bool SendMessage(Dictionary<string, object> args) + { + ServerManager.SendMessage(args["header"].ToString(), args["body"].ToString()); + return true; + } } [TutorialLock] @@ -238,9 +229,27 @@ namespace ShiftOS.Engine } } + [MultiplayerOnly] [Namespace("dev")] public static class ShiftOSDevCommands { + [Command("buy")] + public static bool UnlockUpgrade(Dictionary<string, object> args) + { + string upg = args["id"].ToString(); + try + { + SaveSystem.CurrentSave.Upgrades[upg] = true; + Shiftorium.InvokeUpgradeInstalled(); + SaveSystem.SaveGame(); + } + catch + { + Console.WriteLine("Upgrade not found."); + } + return true; + } + [Command("rock", description = "A little surprise for unstable builds...")] public static bool ThrowASandwichingRock() { @@ -255,7 +264,10 @@ namespace ShiftOS.Engine { try { - SaveSystem.CurrentSave.Upgrades[args["upgrade"] as string] = false; + SaveSystem.CurrentSave.Upgrades[args["upgrade"].ToString()] = false; + SaveSystem.SaveGame(); + Desktop.PopulateAppLauncher(); + Desktop.CurrentDesktop.SetupDesktop(); } catch { @@ -277,9 +289,21 @@ namespace ShiftOS.Engine [RequiresArgument("type")] public static bool MultArg(Dictionary<string, object> args) { + Console.WriteLine("Success! "+args.ToString()); return true; } + [Command("restart")] + public static bool Restart() + { + SaveSystem.CurrentSave.Upgrades = new Dictionary<string, bool>(); + SaveSystem.CurrentSave.Codepoints = 0; + SaveSystem.CurrentSave.StoriesExperienced.Clear(); + SaveSystem.CurrentSave.StoriesExperienced.Add("mud_fundamentals"); + SaveSystem.SaveGame(); + Shiftorium.InvokeUpgradeInstalled(); + return true; + } [Command("freecp")] public static bool FreeCodepoints(Dictionary<string, object> args) @@ -287,8 +311,8 @@ namespace ShiftOS.Engine if (args.ContainsKey("amount")) try { - int codepointsToAdd = Convert.ToInt32(args["amount"].ToString()); - SaveSystem.TransferCodepointsFrom("dev", codepointsToAdd); + long codepointsToAdd = Convert.ToInt64(args["amount"].ToString()); + SaveSystem.CurrentSave.Codepoints += codepointsToAdd; return true; } catch (Exception ex) @@ -297,7 +321,7 @@ namespace ShiftOS.Engine return true; } - SaveSystem.TransferCodepointsFrom("dev", 1000); + SaveSystem.CurrentSave.Codepoints += 1000; return true; } @@ -306,8 +330,13 @@ namespace ShiftOS.Engine { foreach (var upg in Shiftorium.GetDefaults()) { - Shiftorium.Buy(upg.ID, 0); + if (!SaveSystem.CurrentSave.Upgrades.ContainsKey(upg.ID)) + SaveSystem.CurrentSave.Upgrades.Add(upg.ID, true); + else + SaveSystem.CurrentSave.Upgrades[upg.ID] = true; } + Shiftorium.InvokeUpgradeInstalled(); + SkinEngine.LoadSkin(); return true; } @@ -354,87 +383,119 @@ namespace ShiftOS.Engine [Namespace("sos")] public static class ShiftOSCommands { + [Command("setsfxvolume", description = "Set the volume of various sound effects to a value between 1 and 100.")] + [RequiresArgument("value")] + public static bool SetSfxVolume(Dictionary<string, object> args) + { + int value = int.Parse(args["value"].ToString()); + if (value >= 0 && value <= 100) + { + SaveSystem.CurrentSave.SfxVolume = value; + SaveSystem.SaveGame(); + } + else + { + Console.WriteLine("Volume must be between 0 and 100!"); + } + return true; + } + + [Command("setmusicvolume", description ="Set the music volume to a value between 1 and 100.")] + [RequiresArgument("value")] + public static bool SetMusicVolume(Dictionary<string, object> args) + { + int value = int.Parse(args["value"].ToString()); + if(value >= 0 && value <= 100) + { + SaveSystem.CurrentSave.MusicVolume = value; + SaveSystem.SaveGame(); + } + else + { + Console.WriteLine("Volume must be between 0 and 100!"); + } + return true; + } + [RemoteLock] [Command("shutdown")] public static bool Shutdown() { TerminalBackend.InvokeCommand("sos.save"); - SaveSystem.ShuttingDown = true; AppearanceManager.Exit(); return true; } - [Command("help", "{COMMAND_HELP_USAGE}", "{COMMAND_HELP_DESCRIPTION}")] - public static bool Help() + [Command("lang", "{COMMAND_SOS_LANG_USAGE}", "{COMMAND_SOS_LANG_DESCRIPTION}")] + [RequiresArgument("language")] + public static bool SetLanguage(Dictionary<string, object> userArgs) { - var asm = Assembly.GetExecutingAssembly(); - - var types = asm.GetTypes(); - - foreach (var type in types) + try { - if (Shiftorium.UpgradeAttributesUnlocked(type)) - { - foreach (var a in type.GetCustomAttributes(false)) - { - if (a is Namespace) - { - var ns = a as Namespace; + string lang = ""; - if (!ns.hide) - { - string descp = "{NAMESPACE_" + ns.name.ToUpper() + "_DESCRIPTION}"; - if (descp == Localization.Parse(descp)) - descp = ""; - else - descp = Shiftorium.UpgradeInstalled("help_description") ? Localization.Parse("{SEPERATOR}" + descp) : ""; - - Console.WriteLine($"{{NAMESPACE}}{ns.name}" + descp); - - foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) - { - if (Shiftorium.UpgradeAttributesUnlocked(method)) - { - foreach (var ma in method.GetCustomAttributes(false)) - { - if (ma is Command) - { - var cmd = ma as Command; - - if (!cmd.hide) - { - string descriptionparse = "{COMMAND_" + ns.name.ToUpper() + "_" + cmd.name.ToUpper() + "_DESCRIPTION}"; - string usageparse = "{COMMAND_" + ns.name.ToUpper() + "_" + cmd.name.ToUpper() + "_USAGE}"; - if (descriptionparse == Localization.Parse(descriptionparse)) - descriptionparse = ""; - else - descriptionparse = Shiftorium.UpgradeInstalled("help_description") ? Localization.Parse("{SEPERATOR}" + descriptionparse) : ""; + if (userArgs.ContainsKey("language")) + lang = (string)userArgs["language"]; + else + throw new Exception("You must specify a valid 'language' value."); - if (usageparse == Localization.Parse(usageparse)) - usageparse = ""; - else - usageparse = Shiftorium.UpgradeInstalled("help_usage") ? Localization.Parse("{SEPERATOR}" + usageparse, new Dictionary<string, string>() { - {"%ns", ns.name}, - {"%cmd", cmd.name} - }) : ""; + if (Localization.GetAllLanguages().Contains(lang)) + { + SaveSystem.CurrentSave.Language = lang; + SaveSystem.SaveGame(); + Console.WriteLine("{LANGUAGE_CHANGED}"); + return true; + } - Console.WriteLine($"{{COMMAND}}{ns.name}.{cmd.name}" + usageparse + descriptionparse); - } - } - } - } + throw new Exception($"Couldn't find language with ID: {lang}"); + } + catch + { + return false; + } + } - } - } + [Command("help", "{COMMAND_HELP_USAGE", "{COMMAND_HELP_DESCRIPTION}")] + public static bool Help(Dictionary<string, object> args) + { + var sb = new StringBuilder(); + sb.AppendLine("Retrieving help data."); - } + if (args.ContainsKey("ns")) + { + string ns = args["ns"].ToString(); + //First let's check for a command that has this namespace. + var cmdtest = TerminalBackend.Commands.FirstOrDefault(x => x.NamespaceInfo.name == ns); + if (cmdtest == null) //Namespace not found. + sb.AppendLine("Error retrieving help for namespace \"" + ns + "\". Namespace not found."); + else + { + //Now do the actual scan. + sb.AppendLine("Namespace: " + ns); + foreach(var cmd in TerminalBackend.Commands.Where(x => x.NamespaceInfo.name == ns)) + { + string str = cmd.ToString(); + str = str.Replace(str.Substring(str.LastIndexOf("|")), ""); + sb.AppendLine(str); } } } + else + { + + //print all unique namespaces. + foreach(var n in TerminalBackend.Commands.Select(x => x.NamespaceInfo.name).Distinct()) + { + sb.AppendLine("sos.help{ns:\"" + n + "\"}"); + } + } + + Console.WriteLine(sb.ToString()); return true; } + [MultiplayerOnly] [Command("save")] public static bool Save() { @@ -442,19 +503,24 @@ namespace ShiftOS.Engine return true; } + [MultiplayerOnly] [Command("status")] public static bool Status() { - Console.WriteLine($@"ShiftOS version {Assembly.GetExecutingAssembly().GetName().Version.ToString()} + string status = $@"ShiftOS version {Assembly.GetExecutingAssembly().GetName().Version.ToString()} Codepoints: {SaveSystem.CurrentSave.Codepoints} Upgrades: {SaveSystem.CurrentSave.CountUpgrades()} installed, - {Shiftorium.GetAvailable().Length} available"); + {Shiftorium.GetAvailable().Length} available"; + + if (Shiftorium.UpgradeInstalled("mud_control_centre")) + status += Environment.NewLine + $"Reputation: {SaveSystem.CurrentSave.RawReputation} ({SaveSystem.CurrentSave.Reputation})"; + Console.WriteLine(status); return true; } } - + [MultiplayerOnly] [Namespace("shiftorium")] public static class ShiftoriumCommands { @@ -529,7 +595,7 @@ Upgrades: {SaveSystem.CurrentSave.CountUpgrades()} installed, { Console.WriteLine($@"Information for {upgrade}: -{upg.Name} - {upg.Cost} Codepoints +{upg.Category}: {upg.Name} - {upg.Cost} Codepoints ------------------------------------------------------ {upg.Description} @@ -547,15 +613,66 @@ shiftorium.buy{{upgrade:""{upg.ID}""}}"); return false; } } + + [Command("categories")] + public static bool ListCategories() + { + foreach(var cat in Shiftorium.GetCategories()) + { + Console.WriteLine($"{cat} - {Shiftorium.GetAvailable().Where(x=>x.Category==cat).Count()} upgrades"); + } + return true; + } + [Command("list")] - public static bool ListAll() + public static bool ListAll(Dictionary<string, object> args) { try { - Dictionary<string, int> upgrades = new Dictionary<string, int>(); + bool showOnlyInCategory = false; + + string cat = "Other"; + + if (args.ContainsKey("cat")) + { + showOnlyInCategory = true; + cat = args["cat"].ToString(); + } + + Dictionary<string, long> upgrades = new Dictionary<string, long>(); int maxLength = 5; - foreach (var upg in Shiftorium.GetAvailable()) + IEnumerable<ShiftoriumUpgrade> upglist = Shiftorium.GetAvailable(); + if (showOnlyInCategory) + { + if (Shiftorium.IsCategoryEmptied(cat)) + { + ConsoleEx.Bold = true; + ConsoleEx.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Shiftorium Query Error"); + Console.WriteLine(); + ConsoleEx.Bold = false; + ConsoleEx.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine("Either there are no upgrades in the category \"" + cat + "\" or the category was not found."); + return true; + } + upglist = Shiftorium.GetAvailable().Where(x => x.Category == cat); + } + + + if(upglist.Count() == 0) + { + ConsoleEx.Bold = true; + ConsoleEx.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("No upgrades available!"); + Console.WriteLine(); + ConsoleEx.Bold = false; + ConsoleEx.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine("You have installed all available upgrades for your system. Please check back later for more."); + return true; + + } + foreach (var upg in upglist) { if (upg.ID.Length > maxLength) { diff --git a/ShiftOS_TheReturn/ConsoleEx.cs b/ShiftOS_TheReturn/ConsoleEx.cs new file mode 100644 index 0000000..74483dc --- /dev/null +++ b/ShiftOS_TheReturn/ConsoleEx.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ShiftOS.Engine +{ + /// <summary> + /// Provides extra eye candy data that can be used by ShiftOS terminals. + /// </summary> + public static class ConsoleEx + { + /// <summary> + /// Initializes the <see cref="ConsoleEx"/> class, performing core configuration. + /// </summary> + static ConsoleEx() + { + ForegroundColor = ConsoleColor.White; + BackgroundColor = ConsoleColor.Black; + + Bold = false; + Italic = false; + Underline = false; + } + + /// <summary> + /// Gets or sets the foreground color of text in the Terminal. + /// </summary> + public static ConsoleColor ForegroundColor { get; set; } + + /// <summary> + /// Gets or sets the background color of text in the Terminal. + /// </summary> + public static ConsoleColor BackgroundColor { get; set; } + + /// <summary> + /// Gets or sets whether text in the Terminal is bold. + /// </summary> + public static bool Bold { get; set; } + + /// <summary> + /// Gets or sets whether text in the Terminal is italic. + /// </summary> + public static bool Italic { get; set; } + + /// <summary> + /// Gets or sets whether text in the Terminal is underlined. + /// </summary> + public static bool Underline { get; set; } + + internal static void Flush() + { + OnFlush?.Invoke(); + } + + public static Action OnFlush; + } +} diff --git a/ShiftOS_TheReturn/CrashHandler.cs b/ShiftOS_TheReturn/CrashHandler.cs index 734f1a9..48eaf1f 100644 --- a/ShiftOS_TheReturn/CrashHandler.cs +++ b/ShiftOS_TheReturn/CrashHandler.cs @@ -41,38 +41,41 @@ namespace ShiftOS.Engine { public class GetHardwareInfo { + // returns the processor's name for the crash public static string GetProcessorName() { - string ProcessorName = ""; + string ProcessorName = ""; // put the processors name in here sometime later ManagementObjectSearcher mos - = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_Processor"); + = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_Processor"); // OI CPU TELL ME YOUR NAME PLZ foreach (ManagementObject mo in mos.Get()) - ProcessorName = mo["Name"].ToString(); + ProcessorName = mo["Name"].ToString(); // see told you it puts in it there return ProcessorName; } + // same as above but instead for the gpu's name for the crash as well public static string GetGPUName() { string GPUName = ""; ManagementObjectSearcher mos - = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_VideoController"); + = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_VideoController"); //now can you tell me your name cpu kthx foreach (ManagementObject mo in mos.Get()) GPUName = mo["Name"].ToString(); return GPUName; } + // oh wow even more same, but this time its RAM AMOUNT oooh nice public static string GetRAMAmount() { var RAMAmount = ""; ManagementObjectSearcher mos - = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PhysicalMemory"); + = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PhysicalMemory"); //ram you too how much of you exists foreach (ManagementObject mo in mos.Get()) RAMAmount = mo["Capacity"].ToString(); - RAMAmount = (RAMAmount + " B"); + RAMAmount = (RAMAmount + " B"); // ooh and now we add "bytes" to the end return RAMAmount; } @@ -81,23 +84,24 @@ namespace ShiftOS.Engine public partial class CrashHandler : Form { + //fuck it crashed public CrashHandler() { InitializeComponent(); - //Send the bug to Debugle - // or alternatively, send to [email protected] OR [email protected] + //Send the bug to Unite as a bug report + // or alternatively, send to [email protected] + // or just on the discord that works too } - public static Exception HandledException = null; + public static Exception HandledException = null; // this value determines if we can try to set the game back on track or we cant do anything about it public static void Start(Exception e) { if(SaveSystem.CurrentSave != null) - TerminalBackend.InvokeCommand("sos.save"); - AudioManager.Kill(); + TerminalBackend.InvokeCommand("sos.save"); // SAVE BEFORE CRASHING ServerManager.Disconnect(); while (Application.OpenForms.Count > 0) @@ -109,6 +113,7 @@ namespace ShiftOS.Engine System.IO.FileInfo fileInfo = new System.IO.FileInfo(assembly.Location); DateTime lastModified = fileInfo.LastWriteTime; + // put all this in a text document string rtbcrash_Text = $@" === {AssemblyName} has crashed. === Game: {AssemblyName} @@ -180,11 +185,11 @@ Stack trace: } - File.WriteAllText("crash.txt", rtbcrash_Text); + File.WriteAllText("crash.txt", rtbcrash_Text); // make that text document and put above super long string in it var result = MessageBox.Show(caption: "ShiftOS - Fatal error", text: "ShiftOS has encountered a fatal error and has been shut down. Info about the error has been saved to a file called crash.txt in the same folder as the active executable. Would you like to try and recover the game session?", buttons: MessageBoxButtons.YesNo); if(result == DialogResult.Yes) { - Application.Restart(); + Application.Restart(); // tries to restart if user clicks yes, who wouldve guessed } } @@ -198,18 +203,20 @@ Stack trace: this.Close(); Application.Restart(); } - + + // make both of those variables that appear in the long string above public static string AssemblyName { get; private set; } public static string AssemblyDescription { get; private set; } + // get info about the game itself public static void SetGameMetadata(Assembly assembly) { - AssemblyName = assembly.GetName().Name; + AssemblyName = assembly.GetName().Name; // name of game foreach(var attr in assembly.GetCustomAttributes(true)) { if(attr is AssemblyDescriptionAttribute) { - AssemblyDescription = (attr as AssemblyDescriptionAttribute).Description; + AssemblyDescription = (attr as AssemblyDescriptionAttribute).Description; // description of the game } } diff --git a/ShiftOS_TheReturn/Desktop.cs b/ShiftOS_TheReturn/Desktop.cs index 19be5f4..a5e7f43 100644 --- a/ShiftOS_TheReturn/Desktop.cs +++ b/ShiftOS_TheReturn/Desktop.cs @@ -37,6 +37,10 @@ using static ShiftOS.Engine.SkinEngine; namespace ShiftOS.Engine { + /// <summary> + /// Denotes that this class is launchable from the App Launcher. + /// </summary> + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class LauncherAttribute : Attribute { /// <summary> @@ -54,10 +58,29 @@ namespace ShiftOS.Engine ID = upgradeID; } + /// <summary> + /// Gets or sets the name of the launcher item + /// </summary> public string Name { get; set; } + + /// <summary> + /// Gets or sets whether this entry requires a Shiftorium upgrade. + /// </summary> public bool RequiresUpgrade { get; set; } + + /// <summary> + /// Gets or sets the ID of the required upgrade. + /// </summary> public string ID { get; set; } + + /// <summary> + /// Gets or sets this item's category. + /// </summary> public string Category { get; private set; } + + /// <summary> + /// Gets whether or not the required upgrade is installed. + /// </summary> public bool UpgradeInstalled { get @@ -70,33 +93,117 @@ namespace ShiftOS.Engine } } - + /// <summary> + /// Provides core functionality for a typical ShiftOS desktop. + /// </summary> public interface IDesktop { + /// <summary> + /// Gets the name of the desktop. + /// </summary> string DesktopName { get; } - + + /// <summary> + /// Show a notification on the desktop. + /// </summary> + /// <param name="app">An application ID (for determining what system icon to show the notification alongside)</param> + /// <param name="title">The title of the notification.</param> + /// <param name="message">Isn't this.... self explanatory?</param> + void PushNotification(string app, string title, string message); + + /// <summary> + /// Performs most of the skinning and layout handling for the desktop. + /// </summary> void SetupDesktop(); + + /// <summary> + /// Hides the currently-opened app launcher menu. + /// </summary> + void HideAppLauncher(); + + + /// <summary> + /// Populates the app launcher menu. + /// </summary> + /// <param name="items">All items to be placed in the menu.</param> void PopulateAppLauncher(LauncherItem[] items); + + /// <summary> + /// Handles desktop-specific routines for showing ShiftOS windows. + /// </summary> + /// <param name="border">The calling window.</param> void ShowWindow(IWindowBorder border); + + /// <summary> + /// Handles desktop-specific routines for closing ShiftOS windows. + /// </summary> + /// <param name="border">The calling window.</param> void KillWindow(IWindowBorder border); + + /// <summary> + /// Populates the panel button list with all open windows. + /// </summary> void PopulatePanelButtons(); + + /// <summary> + /// Performs desktop-specific routines for minimizing a window. + /// </summary> + /// <param name="brdr">The calling window.</param> void MinimizeWindow(IWindowBorder brdr); + + + /// <summary> + /// Performs desktop-specific routines for maximizing a window. + /// </summary> + /// <param name="brdr">The calling window.</param> void MaximizeWindow(IWindowBorder brdr); + + + /// <summary> + /// Performs desktop-specific routines for restoring a window to its default state. + /// </summary> + /// <param name="brdr">The calling window.</param> void RestoreWindow(IWindowBorder brdr); + + /// <summary> + /// Invokes an action on the UI thread. + /// </summary> + /// <param name="act">The action to invoke.</param> void InvokeOnWorkerThread(Action act); + + /// <summary> + /// Calculates the screen size of the desktop. + /// </summary> + /// <returns>The desktop's screen size.</returns> Size GetSize(); + /// <summary> + /// Opens the app launcher at a specific point. + /// </summary> + /// <param name="loc">Where the app launcher should be opened.</param> void OpenAppLauncher(Point loc); + /// <summary> + /// Opens the desktop. + /// </summary> void Show(); + + /// <summary> + /// Closes the desktop. + /// </summary> void Close(); } public static class Desktop { + /// <summary> + /// The underlying desktop object. + /// </summary> private static IDesktop _desktop = null; - public static Size Size { get + public static Size Size + { + get { return _desktop.GetSize(); } @@ -161,6 +268,19 @@ namespace ShiftOS.Engine { _desktop.OpenAppLauncher(loc); } + + public static void HideAppLauncher() + { + _desktop.HideAppLauncher(); + } + + public static void PushNotification(string app, string title, string msg) + { + InvokeOnWorkerThread(() => + { + _desktop.PushNotification(app, title, msg); + }); + } } // sorry i almost killed everything :P } diff --git a/ShiftOS_TheReturn/FileSkimmerBackend.cs b/ShiftOS_TheReturn/FileSkimmerBackend.cs index 36e07ec..090dc8e 100644 --- a/ShiftOS_TheReturn/FileSkimmerBackend.cs +++ b/ShiftOS_TheReturn/FileSkimmerBackend.cs @@ -94,6 +94,8 @@ namespace ShiftOS.Engine //No, not "sex" - ShiftOS EXecutable. xD case "sex": return FileType.Executable; + case "cf": + return FileType.CommandFormat; default: return FileType.Unknown; } @@ -178,6 +180,7 @@ namespace ShiftOS.Engine Lua, Python, Filesystem, + CommandFormat, Unknown } } diff --git a/ShiftOS_TheReturn/Infobox.cs b/ShiftOS_TheReturn/Infobox.cs index 3e8fa30..62abcb7 100644 --- a/ShiftOS_TheReturn/Infobox.cs +++ b/ShiftOS_TheReturn/Infobox.cs @@ -55,25 +55,34 @@ namespace ShiftOS.Engine /// </summary> /// <param name="title">Infobox title</param> /// <param name="message">Infobox message</param> - public static void Show(string title, string message) + public static void Show(string title, string message, Action callback = null) { title = Localization.Parse(title); message = Localization.Parse(message); - _infobox.Open(title, message); + Desktop.InvokeOnWorkerThread(() => + { + _infobox.Open(title, message, callback); + }); } - - public static void PromptText(string title, string message, Action<string> callback) + + public static void PromptText(string title, string message, Action<string> callback, bool isPassword = false) { title = Localization.Parse(title); message = Localization.Parse(message); - _infobox.PromptText(title, message, callback); + Desktop.InvokeOnWorkerThread(() => + { + _infobox.PromptText(title, message, callback, isPassword); + }); } public static void PromptYesNo(string title, string message, Action<bool> callback) { title = Localization.Parse(title); message = Localization.Parse(message); - _infobox.PromptYesNo(title, message, callback); + Desktop.InvokeOnWorkerThread(() => + { + _infobox.PromptYesNo(title, message, callback); + }); } /// <summary> @@ -89,8 +98,8 @@ namespace ShiftOS.Engine // Infobox Interface public interface IInfobox { - void Open(string title, string msg); - void PromptText(string title, string message, Action<string> callback); + void Open(string title, string msg, Action callback = null); + void PromptText(string title, string message, Action<string> callback, bool isPassword); void PromptYesNo(string title, string message, Action<bool> callback); } } diff --git a/ShiftOS_TheReturn/KernelWatchdog.cs b/ShiftOS_TheReturn/KernelWatchdog.cs new file mode 100644 index 0000000..0608c46 --- /dev/null +++ b/ShiftOS_TheReturn/KernelWatchdog.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using static ShiftOS.Objects.ShiftFS.Utils; + +namespace ShiftOS.Engine +{ + public static class KernelWatchdog + { + //store logs into a file + public static void Log(string e, string desc) + { + string line = $"[{DateTime.Now}] <{e}> {desc}"; + if (FileExists("0:/system/data/kernel.log")) + { + string contents = ReadAllText("0:/system/data/kernel.log"); + contents += Environment.NewLine + line; + WriteAllText("0:/system/data/kernel.log", contents); + } + else + { + WriteAllText("0:/system/data/kernel.log", line); + } + } + + private static bool _mudConnected = true; + + public static bool InKernelMode { get; private set; } + public static bool MudConnected + { + get + { + return _mudConnected; + } + set + { + if(value == false) // hey game if you want to disconnect from mud do this: + { + foreach(var win in AppearanceManager.OpenForms) + { + foreach(var attr in win.ParentWindow.GetType().GetCustomAttributes(true)) + { + // prevents disconnect from mud if an application that needs a connection is open + if(attr is MultiplayerOnlyAttribute) + { + ConsoleEx.Bold = true; + ConsoleEx.Underline = false; + ConsoleEx.Italic = true; + ConsoleEx.ForegroundColor = ConsoleColor.Red; + Console.Write("Error:"); + ConsoleEx.Bold = false; + ConsoleEx.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine("Cannot disconnect from multi-user domain because an app that depends on it is open."); + TerminalBackend.PrintPrompt(); + return; + } + } + } + } + + _mudConnected = value; // connects or disconnects from mud + Desktop.PopulateAppLauncher(); + } + } + + public static bool IsSafe(TerminalBackend.TerminalCommand cmd) + { + if (!cmd.RequiresElevation) + return true; + else + { + if (SaveSystem.CurrentUser.Permissions == Objects.UserPermissions.Root) + return true; + else + return false; + } + } + + + static string regularUsername = ""; //put regular username in here later + + + public static void EnterKernelMode() + { + regularUsername = SaveSystem.CurrentUser.Username; // k for now put user's username in here for the time being + SaveSystem.CurrentUser = SaveSystem.Users.FirstOrDefault(x => x.Username == "root"); // now their username is root + + } + + public static void LeaveKernelMode() + { + var user = SaveSystem.Users.FirstOrDefault(x => x.Username == regularUsername); //finds username + if (user == null) + throw new Exception("User not in root mode."); // fuck this means the user isnt root quick throw error + SaveSystem.CurrentUser = user; + } + + //determines if you can disconnect from mud if there are no applications that currently need to + internal static bool CanRunOffline(Type method) + { + if (MudConnected) + return true; + + foreach (var attr in method.GetCustomAttributes(false)) + { + if (attr is MultiplayerOnlyAttribute) + return false; + } + return true; + } + + //same as above but this time for methods + internal static bool CanRunOffline(MethodInfo method) + { + if (MudConnected) + return true; + + foreach(var attr in method.GetCustomAttributes(false)) + { + if (attr is MultiplayerOnlyAttribute) + return false; + } + return true; + } + } +} diff --git a/ShiftOS_TheReturn/Localization.cs b/ShiftOS_TheReturn/Localization.cs index de5e158..8adfa5a 100644 --- a/ShiftOS_TheReturn/Localization.cs +++ b/ShiftOS_TheReturn/Localization.cs @@ -33,10 +33,12 @@ using System.Threading.Tasks; namespace ShiftOS.Engine { + //define a whole bunch of things that are needed public interface ILanguageProvider { List<string> GetJSONTranscripts(); void WriteDefaultTranscript(); + void WriteTranscript(); string GetCurrentTranscript(); string[] GetAllLanguages(); } @@ -44,19 +46,21 @@ namespace ShiftOS.Engine public static class Localization { private static ILanguageProvider _provider = null; + private static string _languageid = null; public static string[] GetAllLanguages() { if(_provider == null) { - return JsonConvert.DeserializeObject<string[]>(Properties.Resources.languages); + return JsonConvert.DeserializeObject<string[]>(Properties.Resources.languages); //collect all the languages availible } else { - return _provider.GetAllLanguages(); + return _provider.GetAllLanguages(); //also collect all the languages avalible but from a specific provider this time } } + //if no local selected, english will be loaded public static void SetupTHETRUEDefaultLocals() { if (_provider == null) @@ -65,12 +69,19 @@ namespace ShiftOS.Engine var path = "english.local"; Utils.WriteAllText(Paths.GetPath(path), lines); } + else if (SaveSystem.CurrentSave == null) + { + var lines = Properties.Resources.strings_en; + var path = "english.local"; + Utils.WriteAllText(Paths.GetPath(path), lines); + } else { - _provider.WriteDefaultTranscript(); + _provider.WriteTranscript(); } } + // ignore this not really setup of default no no zone public static void SetupDefaultLocals(string lines, string path) { Utils.WriteAllText(Paths.GetPath(path), lines); @@ -78,13 +89,8 @@ namespace ShiftOS.Engine } - /// <summary> - /// Takes in a string and parses localization blocks into text blocks in the current language. - /// </summary> - /// <example>"{CODEPOINTS}: 0" will come out as "Codepoints: 0" if the current language is english.</example> - /// <param name="original">The string to parse</param> - /// <returns>The parsed string.</returns> - /// + // Takes in a string and parses localization blocks into text blocks in the current language. + // example: "{CODEPOINTS}: 0" will come out as "Codepoints: 0" if the current language is english public static string Parse(string original) { return Parse(original, new Dictionary<string, string>()); @@ -96,106 +102,98 @@ namespace ShiftOS.Engine Dictionary<string, string> localizationStrings = new Dictionary<string, string>(); - try { localizationStrings = JsonConvert.DeserializeObject<Dictionary<string, string>>(_provider.GetCurrentTranscript()); } catch { - localizationStrings = JsonConvert.DeserializeObject<Dictionary<string, string>>(Utils.ReadAllText(Paths.GetPath("english.local"))); - } - - foreach (var kv in localizationStrings) - { - original = original.Replace(kv.Key, kv.Value); + localizationStrings = JsonConvert.DeserializeObject<Dictionary<string, string>>(Utils.ReadAllText(Paths.GetPath("english.local"))); //if no provider fall back to english } - List<string> orphaned = new List<string>(); - if (Utils.FileExists("0:/dev_orphaned_lang.txt")) + foreach (var kv in localizationStrings.Where(x=>original.Contains(x.Key))) { - orphaned = JsonConvert.DeserializeObject<List<string>>(Utils.ReadAllText("0:/dev_orphaned_lang.txt")); + original = original.Replace(kv.Key, kv.Value); // goes through and replaces all the localization blocks } + //string original2 = Parse(original); - int start_index = 0; - int length = 0; - bool indexing = false; + string usernameReplace = ""; + string domainReplace = ""; - foreach (var c in original) + // if the user has saved then store their username and systemname in these string variables please + if (SaveSystem.CurrentSave != null) { - if (c == '{') + try { - start_index = original.IndexOf(c); - indexing = true; + usernameReplace = SaveSystem.CurrentUser.Username; } - - if (indexing == true) + catch { - length++; - if (c == '}') - { - indexing = false; - string o = original.Substring(start_index, length); - if (!orphaned.Contains(o)) - { - orphaned.Add(o); - } - start_index = 0; - length = 0; - } + usernameReplace = "user"; } - } - - if (orphaned.Count > 0) - { - Utils.WriteAllText("0:/dev_orphaned_lang.txt", JsonConvert.SerializeObject(orphaned, Formatting.Indented)); - } - //string original2 = Parse(original); - - string usernameReplace = ""; - string domainReplace = ""; - - if (SaveSystem.CurrentSave != null) - { - usernameReplace = SaveSystem.CurrentSave.Username; - domainReplace = SaveSystem.CurrentSave.SystemName; + try + { + domainReplace = SaveSystem.CurrentSave.SystemName; + } + catch + { + domainReplace = "system"; + } + } string namespaceReplace = ""; string commandReplace = ""; + // if the user did a command in the terminal and it had a period in it then split it up into the part before the period and the part after and then store them into these two string variables please if (TerminalBackend.latestCommmand != "" && TerminalBackend.latestCommmand.IndexOf('.') > -1) { namespaceReplace = TerminalBackend.latestCommmand.Split('.')[0]; commandReplace = TerminalBackend.latestCommmand.Split('.')[1]; } + // if you see these then replace them with what you need to Dictionary<string, string> defaultReplace = new Dictionary<string, string>() { {"%username", usernameReplace}, {"%domain", domainReplace}, {"%ns", namespaceReplace}, {"%cmd", commandReplace}, - {"%cp", SaveSystem.CurrentSave?.Codepoints.ToString() }, +#if LOCALIZE_CODEPOINTS + { "%cp", SaveSystem.CurrentSave?.Codepoints.ToString() }, +#endif }; - foreach (KeyValuePair<string, string> replacement in replace) + // actually do the replacement + foreach (KeyValuePair<string, string> replacement in replace.Where(x => original.Contains(x.Key))) { original = original.Replace(replacement.Key, Parse(replacement.Value)); } - foreach (KeyValuePair<string, string> replacement in defaultReplace) + // do the replacement but default + foreach (KeyValuePair<string, string> replacement in defaultReplace.Where(x => original.Contains(x.Key))) { original = original.Replace(replacement.Key, replacement.Value); } - return original; + return original; // returns the now replaced string } + // a few things are defined here public static void RegisterProvider(ILanguageProvider p) { _provider = p; } + + public static void SetLanguageID(string id) + { + _languageid = id; + } + + public static string GetLanguageID() + { + return _languageid; + } } } diff --git a/ShiftOS_TheReturn/LoginManager.cs b/ShiftOS_TheReturn/LoginManager.cs new file mode 100644 index 0000000..d326f2c --- /dev/null +++ b/ShiftOS_TheReturn/LoginManager.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ShiftOS.Objects; + +namespace ShiftOS.Engine +{ + public static class LoginManager + { + private static ILoginFrontend _login = null; + + public static void Init(ILoginFrontend login) + { + _login = login; + } + + public static void PromptForLogin() + { + _login.LoginComplete += (user) => + { + LoginComplete?.Invoke(user); + }; + _login.Login(); + } + + public static bool ShouldUseGUILogin + { + get + { + if (_login == null) + return false; + return _login.UseGUILogin; + } + } + + public static event Action<ClientSave> LoginComplete; + } + + /// <summary> + /// Interface for GUI-based logins. + /// </summary> + public interface ILoginFrontend + { + /// <summary> + /// When implemented, shows the login UI. + /// </summary> + void Login(); + + /// <summary> + /// Gets whether the ShiftOS engine should use a GUI-based login system or the default one. + /// </summary> + bool UseGUILogin { get; } + + + /// <summary> + /// Occurs when the login is complete. + /// </summary> + event Action<ClientSave> LoginComplete; + + + + } +} diff --git a/ShiftOS_TheReturn/NotificationDaemon.cs b/ShiftOS_TheReturn/NotificationDaemon.cs index 77a31fc..a90510a 100644 --- a/ShiftOS_TheReturn/NotificationDaemon.cs +++ b/ShiftOS_TheReturn/NotificationDaemon.cs @@ -34,6 +34,7 @@ namespace ShiftOS.Engine { public static class NotificationDaemon { + //if the notifications file already exists then get them public static Notification[] GetAllFromFile() { Notification[] notes = { }; @@ -44,23 +45,25 @@ namespace ShiftOS.Engine return notes; } + //tells the computer how it likes it to be written in the file internal static void WriteNotes(Notification[] notes) { - Utils.WriteAllText(Paths.GetPath("notifications.dat"), JsonConvert.SerializeObject(notes, Formatting.Indented)); + Utils.WriteAllText(Paths.GetPath("notifications.dat"), JsonConvert.SerializeObject(notes, Formatting.Indented)); //"write it in there indented pls" } - public static event Action<Notification> NotificationMade; - + public static event Action<Notification> NotificationMade; //use this if you want to know when a notification has been made + public static void AddNotification(NotificationType note, object data) { - var lst = new List<Notification>(GetAllFromFile()); - lst.Add(new Engine.Notification(note, data)); + var lst = new List<Notification>(GetAllFromFile()); //grabs all current notifications + lst.Add(new Engine.Notification(note, data)); //then adds the new one to the list WriteNotes(lst.ToArray()); - NotificationMade?.Invoke(lst[lst.Count - 1]); + NotificationMade?.Invoke(lst[lst.Count - 1]); //says to the program that a notification has indeed been made } public static event Action NotificationRead; + //for every notification that there is, mark them as read public static void MarkAllRead() { var notes = GetAllFromFile(); @@ -68,30 +71,33 @@ namespace ShiftOS.Engine MarkRead(i); } + //grabs list of notifcations and if the notification you want to mark as read actually exsists, then it assigns it as read public static void MarkRead(int note) { var notes = GetAllFromFile(); if (note >= notes.Length || note < 0) throw new ArgumentOutOfRangeException("note", new Exception("You cannot mark a notification that does not exist as read.")); - notes[note].Read = true; + notes[note].Read = true; //assigns the specific notification as read WriteNotes(notes); NotificationRead?.Invoke(); } - public static int GetUnreadCount() + public static int GetUnreadCount() //use this if you want the unread notification count, but i think you probably already knew that { int c = 0; foreach (var note in GetAllFromFile()) if (note.Read == false) - c++; //gahh I hate that programming language. + c++; //gahh I hate that programming language. //dont we all return c; } } + //actually gives the proper data for the notification public struct Notification { + //defaults for all notificaions public Notification(NotificationType t, object data) { Type = t; @@ -106,9 +112,10 @@ namespace ShiftOS.Engine public DateTime Timestamp { get; set; } } + //defines all the possible notificaions that can happen public enum NotificationType { - Generic = 0x00, + Generic = 0x00, //lets get generic MemoReceived = 0x10, MemoSent = 0x11, DownloadStarted = 0x20, diff --git a/ShiftOS_TheReturn/OutOfBoxExperience.cs b/ShiftOS_TheReturn/OutOfBoxExperience.cs index 6ed9e49..eb8e61d 100644 --- a/ShiftOS_TheReturn/OutOfBoxExperience.cs +++ b/ShiftOS_TheReturn/OutOfBoxExperience.cs @@ -45,17 +45,17 @@ namespace ShiftOS.Engine public static void Init(IOobe oobe) { - _oobe = oobe; + _oobe = oobe; // takes the oobe and makes it an IOobe } public static void Start(Save save) { - + //if its null then FUCK YOU DID THE WRONG THING if (_oobe == null) throw new InvalidOperationException("OOBE frontend not activated! This function can't be used! Please use OutOfBoxExperience.Init() passing an IOobe-implementing object to start the OOBE frontend."); - _oobe.StartShowing(save); + _oobe.StartShowing(save); //tells the save data to start showing the oobe } @@ -64,7 +64,7 @@ namespace ShiftOS.Engine { Desktop.InvokeOnWorkerThread(new Action(() => { - _oobe.PromptForLogin(); + _oobe.PromptForLogin(); //prompts for login, what did you expect })); } @@ -72,12 +72,13 @@ namespace ShiftOS.Engine { Desktop.InvokeOnWorkerThread(new Action(() => { - _oobe.ShowSaveTransfer(save); + _oobe.ShowSaveTransfer(save); //triggers save transfer if not done already })); } } + //triggers all the above events public interface IOobe { void StartShowing(Save save); diff --git a/ShiftOS_TheReturn/Paths.cs b/ShiftOS_TheReturn/Paths.cs index 4f535d6..5b75ae6 100644 --- a/ShiftOS_TheReturn/Paths.cs +++ b/ShiftOS_TheReturn/Paths.cs @@ -35,12 +35,17 @@ using System.Threading; namespace ShiftOS.Engine { + /// <summary> + /// Management class for ShiftFS path variables. + /// </summary> public static class Paths { + /// <summary> + /// Initiate the path system. + /// </summary> public static void Init() { Locations = new Dictionary<string, string>(); - Locations.Add("classic", "C:\\ShiftOS"); Locations.Add("root", "0:"); AddPath("root", "system"); @@ -61,6 +66,7 @@ namespace ShiftOS.Engine AddPath("data", "user.dat"); AddPath("data", "notifications.dat"); AddPath("data", "skin"); + AddPath("skin", "widgets.dat"); AddPath("system", "programs"); AddPath("system", "kernel.sft"); AddPath("system", "conf.sft"); @@ -88,6 +94,10 @@ namespace ShiftOS.Engine } + /// <summary> + /// Gets all full paths without their keynames. + /// </summary> + /// <returns>A string array representing all paths.</returns> public static string[] GetAllWithoutKey() { List<string> strings = new List<string>(); @@ -99,11 +109,19 @@ namespace ShiftOS.Engine } + /// <summary> + /// Get the full path using a path key. + /// </summary> + /// <param name="id">The path key (folder/filename) for the path.</param> + /// <returns>The full path.</returns> public static string GetPath(string id) { return Locations[id]; } + /// <summary> + /// Checks all directories in the path system to see if they exist, and if not, creates them. + /// </summary> private static void CheckPathExistence() { foreach(var path in Locations) @@ -119,8 +137,14 @@ namespace ShiftOS.Engine } } + /// <summary> + /// Gets or sets a <see cref="Dictionary{string, string}"/> representing all paths in the system. + /// </summary> private static Dictionary<string, string> Locations { get; set; } + /// <summary> + /// Mounts the ShiftOS shared directory to 1:/, creating the directory if it does not exist. + /// </summary> public static void CreateAndMountSharedFolder() { if (!System.IO.Directory.Exists(SharedFolder)) @@ -132,29 +156,113 @@ namespace ShiftOS.Engine mount.Name = "Shared"; Utils.Mount(JsonConvert.SerializeObject(mount)); ScanForDirectories(SharedFolder, 1); - } + //This event-based system allows us to sync the ramdisk from ShiftOS to the host OS. + Utils.DirectoryCreated += (dir) => + { + try + { + if (dir.StartsWith("1:/")) + { + string real = dir.Replace("/", "\\").Replace("1:", SharedFolder); + if (!System.IO.Directory.Exists(real)) + System.IO.Directory.CreateDirectory(real); + } + } + catch { } + }; + Utils.DirectoryDeleted += (dir) => + { + try + { + if (dir.StartsWith("1:/")) + { + string real = dir.Replace("/", "\\").Replace("1:", SharedFolder); + if (System.IO.Directory.Exists(real)) + System.IO.Directory.Delete(real, true); + } + } + catch { } + }; + Utils.FileWritten += (dir) => + { + try + { + if (dir.StartsWith("1:/")) + { + string real = dir.Replace("/", "\\").Replace("1:", SharedFolder); + System.IO.File.WriteAllBytes(real, ReadAllBytes(dir)); + } + } + catch { } + }; + + Utils.FileDeleted += (dir) => + { + try + { + if (dir.StartsWith("1:/")) + { + string real = dir.Replace("/", "\\").Replace("1:", SharedFolder); + if (System.IO.File.Exists(real)) + System.IO.File.Delete(real); + } + } + catch { } + }; + + //This thread will sync the ramdisk from the host OS to ShiftOS. + var t = new Thread(() => + { + while (!SaveSystem.ShuttingDown) + { + Thread.Sleep(15000); + ScanForDirectories(SharedFolder, 1); + } + }); + t.IsBackground = true; + t.Start(); + } - public static void ScanForDirectories(string folder, int mount) + private static void ScanForDirectories(string folder, int mount) { foreach (var file in System.IO.Directory.GetFiles(folder)) { string mfsDir = file.Replace(SharedFolder, $"{mount}:").Replace("\\", "/"); - WriteAllBytes(mfsDir, System.IO.File.ReadAllBytes(file)); + if (!FileExists(mfsDir)) + WriteAllBytes(mfsDir, System.IO.File.ReadAllBytes(file)); } foreach (var directory in System.IO.Directory.GetDirectories(folder)) { string mfsDir = directory.Replace(SharedFolder, $"{mount}:").Replace("\\", "/"); - CreateDirectory(mfsDir); + if(!DirectoryExists(mfsDir)) + CreateDirectory(mfsDir); ScanForDirectories(directory, mount); } } + /// <summary> + /// Gets the ShiftOS shared folder. + /// </summary> public static string SharedFolder { get { return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\ShiftOS_Shared"; } } + + /// <summary> + /// Gets the location of the ShiftOS.mfs file. + /// </summary> public static string SaveFile { get { return Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\ShiftOS.mfs"; } } + + /// <summary> + /// Gets the path of the inner save file. + /// </summary> + [Obsolete("Not used.")] public static string SaveFileInner { get { return Locations["save.json"]; } } + /// <summary> + /// Add a path to the system. + /// </summary> + /// <param name="parent">The path's parent directory.</param> + /// <param name="path">The filename for the path.</param> public static void AddPath(string parent, string path) { Locations.Add(path, Locations[parent] + "/" + path); diff --git a/ShiftOS_TheReturn/Resources/strings_de.txt b/ShiftOS_TheReturn/Resources/strings_de.txt index 10535c8..06ef9d7 100644 --- a/ShiftOS_TheReturn/Resources/strings_de.txt +++ b/ShiftOS_TheReturn/Resources/strings_de.txt @@ -36,7 +36,7 @@ Wenn eine Systemdatei von dem Virenscanner erkannt wird, wird sie ersetzt.", "{TERMINAL}":"Terminal", "{PONG}":"Pong", "{CODEPOINTS}":"Codepoints", - "{CODEPOINTS_VALUE}":"%cp " + "{CODEPOINTS_VALUE}":"%cp ", "{SHIFTORIUM}":"Shiftorium", "{HACK}":"Hack", "{SHIFTER}":"Shifter", @@ -151,15 +151,17 @@ Wenn eine Systemdatei von dem Virenscanner erkannt wird, wird sie ersetzt.", "{ERROR_COMMAND_WRONG}": "Check your syntax and try again", "{LOGIN_EXP}": "Login as the admin of the multi user domain.", - "{USAGE}": "Usage: ", + "{USAGE}": "Verwendung: ", "{NAMESPACE_SOS_DESCRIPTION}":"The ShiftOS Namespace", "{COMMAND_HELP_USAGE}":"%ns.%cmd{[topic:]}", - "{COMMAND_HELP_DESCRIPTION}":"Lists all commands", + "{COMMAND_HELP_DESCRIPTION}":"Listet alle Befehle auf", "{COMMAND_SOS_SHUTDOWN_USAGE}":"%ns.%cmd", "{COMMAND_SOS_SHUTDOWN_DESCRIPTION}":"Saves and shuts down ShiftOS", "{COMMAND_SOS_STATUS_USAGE}":"%ns.%cmd", "{COMMAND_SOS_STATUS_DESCRIPTION}":"Displays how many codepoints you have", + "{COMMAND_SOS_LANG_USAGE}":"%ns.%cmd{[language:\"english\"]}", + "{COMMAND_SOS_LANG_DESCRIPTION}":"Sprache �ndern.", "{COMMAND_DEV_CRASH_USAGE}":"%ns.%cmd", "{COMMAND_DEV_CRASH_DESCRIPTION}":"Shuts down ShiftOS forcefully", "{COMMAND_DEV_UNLOCKEVERYTHING_USAGE}":"%ns.%cmd", @@ -177,7 +179,7 @@ Wenn eine Systemdatei von dem Virenscanner erkannt wird, wird sie ersetzt.", "{COMMAND_DEV_MULTARG_USAGE}":"%ns.%cmd{id:,name:,type:}", "{COMMAND_DEV_MULTARG_DESCRIPTION}":"A command which requiers multiple arguments", - "{ERR_COMMAND_NOT_FOUND}":"Command not found.", + "{ERR_COMMAND_NOT_FOUND}":"Befehl nicht gefunden.", "{MUD_ERROR}":"MUD error", "{PROLOGUE_NO_USER_DETECTED}":"No user detected. Please enter a username.", @@ -200,26 +202,44 @@ Wenn eine Systemdatei von dem Virenscanner erkannt wird, wird sie ersetzt.", "{SENTIENCE_POSSIBLEHUMAN}":"Sentience: Possible human - user can perform actions based on a choice.", "{SENTIENCE_POSSIBLEHUMANPLUS}":"Sentience: Possible human+ - user can infer, and can pass arguments.", "{SENTIENCE_HUMAN}":"Sentience: Human. Thanks for your patience.", - "{SENTIENCE_INVALIDPASSWORD}":"The password you entered is invalid.", + "{SENTIENCE_INVALIDPASSWORD}":"Das eingegebene Passwort ist ung�ltig.", "{ARGS_PASSWORD}":"password", "{SHIFTOS_PLUS_MOTTO}":"ShiftOS, Shift it YOUR way.", "{SHIFTOS_VERSION_INFO}":"ShiftOS Version: ", - "{USER_NAME}":"Username", + "{USER_NAME}":"Benutzername", "{DISCOURSE_INTEGRATION}":"Discourse Integration", "{SYSTEM_NAME}":"System Name", "{USER_INFO}":"User Information", "{SELECT_LANG}":"Select language", "{WELCOME_TO_SHIFTOS}":"Welcome to ShiftOS Alpha!", - "{CREATE}":"Create", - "{INSTALL}":"Install", + "{CREATE}":"Erstellen", + "{INSTALL}":"Installieren", "{ALIAS}":"Alias:", "{OBSOLETE_SYS_SHUTDOWN}":"sys.shutdown is obsolete", "{PY_EXCEPTION}":"There was an error running python code.", - "{LUA_ERROR}":"There was an error running lua code." + "{LUA_ERROR}":"There was an error running lua code.", + "{LANGUAGE_CHANGED}":"Die Sprache wurde ge�ndert. Bitte �ndern Sie ShiftOS neu, damit �nderungen voll wirksam werden.", "{TERMINAL_NAME}":"Terminal", "{ARTPAD_NAME}":"Artpad", "{PONG_NAME}":"Pong", + "{WAV_PLAYER_NAME}":"WAV Player", + "{SHIFTORIUM_NAME}":"Shiftorium", + "{TEXTPAD_NAME}":"TextPad", + "{VIRUS_SCANNER_NAME}":"Virus Scanner", + "{SKIN_LOADER_NAME}":"Skin Loader", + "{SHIFTER_NAME}":"Shifter", + "{NAME_CHANGER_NAME}":"Name Changer", + "{MUD_PASSWORD_CRACKER_NAME}":"Multi-User Domain Password Cracker v1.0", + "{MUD_CONTROL_CENTRE_NAME}":"MUD Control Centre", + "{MUD_AUTHENTICATOR_NAME}":"Multi-User Domain Admin Panel", + "{GRAPHIC_PICKER_NAME}":"Graphic Picker", + "{FILE_SKIMMER_NAME}":"File Skimmer", + "{FILE_DIALOG_NAME}":"File Dialog", + "{DIALOG_NAME}":"Dialog", + "{COLOR_PICKER_NAME}":"Color Picker", + "{CHAT_NAME}":"Chat", + "{GERMAN_SECRET}":"guten tag polen ist anschluss", }
\ No newline at end of file diff --git a/ShiftOS_TheReturn/Resources/strings_en.txt b/ShiftOS_TheReturn/Resources/strings_en.txt index c42edab..68719c0 100644 --- a/ShiftOS_TheReturn/Resources/strings_en.txt +++ b/ShiftOS_TheReturn/Resources/strings_en.txt @@ -32,7 +32,7 @@ If a system file is deleted by the virus scanner, it will be replaced.", "{TERMINAL}":"Terminal", "{PONG}":"Pong", "{CODEPOINTS}":"Codepoints", - "{CODEPOINTS_VALUE}":"%cp " + "{CODEPOINTS_VALUE}":"%cp ", "{SHIFTORIUM}":"Shiftorium", "{HACK}":"Hack", "{SHIFTER}":"Shifter", @@ -215,7 +215,7 @@ If a system file is deleted by the virus scanner, it will be replaced.", "{ALIAS}":"Alias:", "{OBSOLETE_SYS_SHUTDOWN}":"sys.shutdown is obsolete", "{PY_EXCEPTION}":"There was an error running python code.", - "{LUA_ERROR}":"There was an error running lua code." + "{LUA_ERROR}":"There was an error running lua code.", "{TERMINAL_NAME}":"Terminal", "{ARTPAD_NAME}":"Artpad", diff --git a/ShiftOS_TheReturn/SaveSystem.cs b/ShiftOS_TheReturn/SaveSystem.cs index 9ae18a9..55f5cd5 100644 --- a/ShiftOS_TheReturn/SaveSystem.cs +++ b/ShiftOS_TheReturn/SaveSystem.cs @@ -39,6 +39,7 @@ using static System.Net.Mime.MediaTypeNames; namespace ShiftOS.Engine { + [Obsolete("Use the servers.conf file instead.")] public class EngineConfig { public bool ConnectToMud = true; @@ -46,10 +47,34 @@ namespace ShiftOS.Engine public int MudDefaultPort = 13370; } + /// <summary> + /// Management class for the ShiftOS save system. + /// </summary> public static class SaveSystem { + /// <summary> + /// Boolean representing whether the system is shutting down. + /// </summary> public static bool ShuttingDown = false; + /// <summary> + /// Gets or sets the current logged in client-side user. + /// </summary> + public static ClientSave CurrentUser { get; set; } + + /// <summary> + /// Boolean representing whether the save system is ready to be used. + /// </summary> + public static bool Ready = false; + + /// <summary> + /// Occurs before the save system connects to the ShiftOS Digital Society. + /// </summary> + public static event Action PreDigitalSocietyConnection; + + /// <summary> + /// Gets or sets the current server-side save file. + /// </summary> public static Save CurrentSave { get; set; } /// <summary> @@ -67,11 +92,10 @@ namespace ShiftOS.Engine { var root = new ShiftOS.Objects.ShiftFS.Directory(); root.Name = "System"; - root.permissions = Permissions.All; + root.permissions = UserPermissions.Guest; System.IO.File.WriteAllText(Paths.SaveFile, JsonConvert.SerializeObject(root)); } - if (Utils.Mounts.Count == 0) Utils.Mount(System.IO.File.ReadAllText(Paths.SaveFile)); Paths.Init(); @@ -96,128 +120,377 @@ namespace ShiftOS.Engine } Thread.Sleep(350); - Console.WriteLine("Initiating kernel..."); + Console.WriteLine("ShiftKernel v0.4.2"); + Console.WriteLine("(MIT) DevX 2017, Very Little Rights Reserved"); + Console.WriteLine(""); + Console.WriteLine("THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR"); + Console.WriteLine("IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,"); + Console.WriteLine("FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE"); + Console.WriteLine("AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER"); + Console.WriteLine("LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,"); + Console.WriteLine("OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE"); + Console.WriteLine("SOFTWARE."); + Console.WriteLine(""); Thread.Sleep(250); - Console.WriteLine("Reading filesystem..."); + Console.WriteLine("[init] Kernel boot complete."); + Console.WriteLine("[sfs] Loading SFS driver v3"); Thread.Sleep(100); - Console.WriteLine("Reading configuration..."); + Console.WriteLine("[sfs] 4096 blocks read."); + Console.WriteLine("[simpl-conf] Reading configuration files (global-3.conf)"); + Console.WriteLine("[termdb] Building command database from filesystem..."); + TerminalBackend.PopulateTerminalCommands(); + Console.WriteLine("[inetd] Connecting to network..."); - Console.WriteLine("{CONNECTING_TO_MUD}"); + Ready = false; - if (defaultConf.ConnectToMud == true) + if (PreDigitalSocietyConnection != null) { - bool guidReceived = false; - ServerManager.GUIDReceived += (str) => + PreDigitalSocietyConnection?.Invoke(); + + while (!Ready) { + Thread.Sleep(10); + } + } + + + + bool guidReceived = false; + ServerManager.GUIDReceived += (str) => + { + //Connection successful! Stop waiting! guidReceived = true; - Console.WriteLine("{CONNECTION_SUCCESSFUL}"); - }; + Console.WriteLine("[inetd] Connection successful."); + }; - try - { - - ServerManager.Initiate("secondary4162.cloudapp.net", 13370); - while(guidReceived == false) - { + try + { - } - } - catch (Exception ex) + ServerManager.Initiate(UserConfig.Get().DigitalSocietyAddress, UserConfig.Get().DigitalSocietyPort); + //This haults the client until the connection is successful. + while (ServerManager.thisGuid == new Guid()) { - Console.WriteLine("{ERROR}: " + ex.Message); - Thread.Sleep(3000); - ServerManager.StartLANServer(); - while (guidReceived == false) - { - - } + Thread.Sleep(10); } + Console.WriteLine("[inetd] DHCP GUID recieved, finished setup"); + FinishBootstrap(); } - else + catch (Exception ex) { - ServerManager.StartLANServer(); + //No errors, this never gets called. + Console.WriteLine("[inetd] SEVERE: " + ex.Message); + Thread.Sleep(3000); + Console.WriteLine("[sys] SEVERE: Cannot connect to server. Shutting down in 5..."); + Thread.Sleep(1000); + Console.WriteLine("[sys] 4..."); + Thread.Sleep(1000); + Console.WriteLine("[sys] 3..."); + Thread.Sleep(1000); + Console.WriteLine("[sys] 2..."); + Thread.Sleep(1000); + Console.WriteLine("[sys] 1..."); + Thread.Sleep(1000); + Console.WriteLine("[sys] Bye bye."); + System.Diagnostics.Process.GetCurrentProcess().Kill(); } - ServerManager.MessageReceived += (msg) => - { - if(msg.Name == "mud_savefile") - { - CurrentSave = JsonConvert.DeserializeObject<Save>(msg.Contents); - } - else if(msg.Name == "mud_login_denied") - { - oobe.PromptForLogin(); - } - }; + //Nothing happens past this point - but the client IS connected! It shouldn't be stuck in that while loop above. - ReadSave(); - while(CurrentSave == null) - { + })); + thread.IsBackground = true; + thread.Start(); + } + /// <summary> + /// Finish bootstrapping the engine. + /// </summary> + private static void FinishBootstrap() + { + KernelWatchdog.Log("mud_handshake", "handshake successful: kernel watchdog access code is \"" + ServerManager.thisGuid.ToString() + "\""); + + ServerMessageReceived savehandshake = null; + + savehandshake = (msg) => + { + if (msg.Name == "mud_savefile") + { + CurrentSave = JsonConvert.DeserializeObject<Save>(msg.Contents); + ServerManager.MessageReceived -= savehandshake; + } + else if (msg.Name == "mud_login_denied") + { + oobe.PromptForLogin(); + ServerManager.MessageReceived -= savehandshake; } + }; + ServerManager.MessageReceived += savehandshake; - Shiftorium.Init(); - while (CurrentSave.StoryPosition < 1) + ReadSave(); + + while (CurrentSave == null) + { + Thread.Sleep(10); + } + + Localization.SetupTHETRUEDefaultLocals(); + + Shiftorium.Init(); + + while (CurrentSave.StoryPosition < 1) + { + Thread.Sleep(10); + } + + Thread.Sleep(75); + + Thread.Sleep(50); + Console.WriteLine("[usr-man] Accepting logins on local tty 1."); + + Sysname: + bool waitingForNewSysName = false; + bool gobacktosysname = false; + + if (string.IsNullOrWhiteSpace(CurrentSave.SystemName)) + { + Infobox.PromptText("Enter a system name", "Your system does not have a name. All systems within the digital society must have a name. Please enter one.", (name) => { + if (string.IsNullOrWhiteSpace(name)) + Infobox.Show("Invalid name", "Please enter a valid name.", () => + { + gobacktosysname = true; + waitingForNewSysName = false; + }); + else if (name.Length < 5) + Infobox.Show("Value too small.", "Your system name must have at least 5 characters in it.", () => + { + gobacktosysname = true; + waitingForNewSysName = false; + }); + else + { + CurrentSave.SystemName = name; + if (!string.IsNullOrWhiteSpace(CurrentSave.UniteAuthToken)) + { + var unite = new Unite.UniteClient("http://getshiftos.ml", CurrentSave.UniteAuthToken); + unite.SetSysName(name); + } + SaveSystem.SaveGame(); + gobacktosysname = false; + waitingForNewSysName = false; + } + }); - } - Thread.Sleep(75); + } + + while (waitingForNewSysName) + { + Thread.Sleep(10); + } - Thread.Sleep(50); - Console.WriteLine("{SYSTEM_INITIATED}"); + if (gobacktosysname) + { + goto Sysname; + } - TerminalBackend.InStory = false; - TerminalBackend.PrefixEnabled = true; - Shiftorium.LogOrphanedUpgrades = true; - Desktop.InvokeOnWorkerThread(new Action(() => + if (CurrentSave.Users == null) + CurrentSave.Users = new List<ClientSave>(); + + Console.WriteLine($@" + `-:/++++::.` + .+ydNMMMMMNNMMMMMNhs/. + /yNMMmy+:-` `````.-/ohNMMms- + `oNMMh/.`:oydmNMMMMNmhs+- .+dMMm+` Welcome to ShiftOS. + `oMMmo``+dMMMMMMMMMMMMMMMMMNh/`.sNMN+ + :NMN+ -yMMMMMMMNdhyssyyhdmNMMMMNs``sMMd. SYSTEM STATUS: + oMMd.`sMMMMMMd+. `/MMMMN+ -mMN: ---------------------- + oMMh .mMMMMMM/ `-::::-.` :MMMMMMh`.mMM: + :MMd .NMMMMMMs .dMMMMMMMMMNddMMMMMMMd`.NMN. Codepoints: {SaveSystem.CurrentSave.Codepoints} + mMM. dMMMMMMMo -mMMMMMMMMMMMMMMMMMMMMs /MMy Upgrades: {SaveSystem.CurrentSave.CountUpgrades()} installed + :MMh :MMMMMMMMm` .+shmMMMMMMMMMMMMMMMN` NMN` {Shiftorium.GetAvailable().Count()} available + oMM+ sMMMMMMMMMN+` `-/smMMMMMMMMMMM: hMM: Filesystems: {Utils.Mounts.Count} filesystems mounted in memory. + sMM+ sMMMMMMMMMMMMds/-` .sMMMMMMMMM/ yMM/ + +MMs +MMMMMMMMMMMMMMMMMmhs:` +MMMMMMMM- dMM- System name: {CurrentSave.SystemName.ToUpper()} + .MMm `NMMMMMMMMMMMMMMMMMMMMMo `NMMMMMMd .MMN Users: {Users.Count()} found. + hMM+ +MMMMMMmsdNMMMMMMMMMMN/ -MMMMMMN- yMM+ + `NMN- oMMMMMd `-/+osso+- .mMMMMMN: +MMd + -NMN: /NMMMm` :yMMMMMMm- oMMd` + -mMMs``sMMMMNdhso++///+oydNMMMMMMNo .hMMh` + `yMMm/ .omMMMMMMMMMMMMMMMMMMMMd+``oNMNo + -hMMNo. -ohNMMMMMMMMMMMMmy+. -yNMNy` + .sNMMms/. `-/+++++/:-` ./yNMMmo` + :sdMMMNdyso+++ooshdNMMMdo- + `:+yhmNNMMMMNNdhs+- + ```` "); + + if (CurrentSave.Users.Count == 0) + { + CurrentSave.Users.Add(new ClientSave { - ShiftOS.Engine.Scripting.LuaInterpreter.RunSft(Paths.GetPath("kernel.sft")); - })); - Desktop.InvokeOnWorkerThread(new Action(() => Desktop.PopulateAppLauncher())); - if (CurrentSave.StoryPosition == 1) + Username = "root", + Password = "", + Permissions = UserPermissions.Root + }); + Console.WriteLine("[usr-man] WARN: No users found. Creating new user with username \"root\", with no password."); + } + TerminalBackend.InStory = false; + + TerminalBackend.PrefixEnabled = false; + + if (LoginManager.ShouldUseGUILogin) + { + Action<ClientSave> Completed = null; + Completed += (user) => { - Desktop.InvokeOnWorkerThread(new Action(() => - { - TutorialManager.StartTutorial(); - - })); - while(TutorialManager.IsInTutorial == true) { } - GameReady?.Invoke(); + CurrentUser = user; + LoginManager.LoginComplete -= Completed; + }; + LoginManager.LoginComplete += Completed; + Desktop.InvokeOnWorkerThread(() => + { + LoginManager.PromptForLogin(); + }); + while (CurrentUser == null) + { + Thread.Sleep(10); } - else + } + else + { + + Login: + string username = ""; + int progress = 0; + bool goback = false; + TextSentEventHandler ev = null; + ev = (text) => { - GameReady?.Invoke(); + if (progress == 0) + { + string loginstr = CurrentSave.SystemName + " login: "; + string getuser = text.Remove(0, loginstr.Length); + if (!string.IsNullOrWhiteSpace(getuser)) + { + if (CurrentSave.Users.FirstOrDefault(x => x.Username == getuser) == null) + { + Console.WriteLine(); + Console.WriteLine("User not found."); + goback = true; + progress++; + TerminalBackend.TextSent -= ev; + return; + } + username = getuser; + progress++; + } + else + { + Console.WriteLine(); + Console.WriteLine("Username not provided."); + TerminalBackend.TextSent -= ev; + goback = true; + progress++; + } + } + else if (progress == 1) + { + string passwordstr = "password: "; + string getpass = text.Remove(0, passwordstr.Length); + var user = CurrentSave.Users.FirstOrDefault(x => x.Username == username); + if (user.Password == getpass) + { + Console.WriteLine(); + Console.WriteLine("Welcome to ShiftOS."); + CurrentUser = user; + progress++; + } + else + { + Console.WriteLine(); + Console.WriteLine("Access denied."); + goback = true; + progress++; + } + TerminalBackend.TextSent -= ev; + } + }; + TerminalBackend.TextSent += ev; + Console.WriteLine(); + Console.Write(CurrentSave.SystemName + " login: "); + ConsoleEx.Flush(); + while (progress == 0) + { + Thread.Sleep(10); } + if (goback) + goto Login; + Console.WriteLine(); + Console.Write("password: "); + ConsoleEx.Flush(); + while (progress == 1) + Thread.Sleep(10); + if (goback) + goto Login; + } + TerminalBackend.PrefixEnabled = true; + Shiftorium.LogOrphanedUpgrades = true; + Desktop.InvokeOnWorkerThread(new Action(() => + { + ShiftOS.Engine.Scripting.LuaInterpreter.RunSft(Paths.GetPath("kernel.sft")); })); - thread.IsBackground = true; - thread.Start(); + + + Desktop.InvokeOnWorkerThread(new Action(() => Desktop.PopulateAppLauncher())); + GameReady?.Invoke(); } + /// <summary> + /// Delegate type for events with no caller objects or event arguments. You can use the () => {...} (C#) lambda expression with this delegate + /// </summary> public delegate void EmptyEventHandler(); + /// <summary> + /// Gets a list of all client-side users. + /// </summary> public static List<ClientSave> Users { - get; - private set; + get + { + return CurrentSave.Users; + } } + /// <summary> + /// Occurs when the engine is loaded and the game can take over. + /// </summary> public static event EmptyEventHandler GameReady; - public static void TransferCodepointsToVoid(int amount) + /// <summary> + /// Deducts a set amount of Codepoints from the save file... and sends them to a place where they'll never be seen again. + /// </summary> + /// <param name="amount">The amount of Codepoints to deduct.</param> + public static void TransferCodepointsToVoid(long amount) { + if (amount < 0) + throw new ArgumentOutOfRangeException("We see what you did there. Trying to pull Codepoints from the void? That won't work."); CurrentSave.Codepoints -= amount; NotificationDaemon.AddNotification(NotificationType.CodepointsSent, amount); } + /// <summary> + /// Restarts the game. + /// </summary> public static void Restart() { TerminalBackend.InvokeCommand("sos.shutdown"); System.Windows.Forms.Application.Restart(); } + /// <summary> + /// Requests the save file from the server. If authentication fails, this will cause the user to be prompted for their website login and a new save will be created if none is associated with the login. + /// </summary> public static void ReadSave() { //Migrate old saves. @@ -249,12 +522,9 @@ namespace ShiftOS.Engine { if (Utils.FileExists(Paths.GetPath("user.dat"))) { - var userdat = JsonConvert.DeserializeObject<ClientSave>(Utils.ReadAllText(Paths.GetPath("user.dat"))); + string token = Utils.ReadAllText(Paths.GetPath("user.dat")); - ServerManager.SendMessage("mud_login", $@"{{ - username: ""{userdat.Username}"", - password: ""{userdat.Password}"" -}}"); + ServerManager.SendMessage("mud_token_login", token); } else { @@ -264,6 +534,9 @@ namespace ShiftOS.Engine } + /// <summary> + /// Creates a new save, starting the Out Of Box Experience (OOBE). + /// </summary> public static void NewSave() { AppearanceManager.Invoke(new Action(() => @@ -276,6 +549,9 @@ namespace ShiftOS.Engine })); } + /// <summary> + /// Saves the game to the server, updating website stats if possible. + /// </summary> public static void SaveGame() { if(!Shiftorium.Silent) @@ -284,14 +560,7 @@ namespace ShiftOS.Engine Console.Write("{SE_SAVING}... "); if (SaveSystem.CurrentSave != null) { - string username = CurrentSave.Username; - string password = CurrentSave.Password; - - Utils.WriteAllText(Paths.GetPath("user.dat"), $@"{{ - username: ""{username}"", - password: ""{password}"" -}}"); - + Utils.WriteAllText(Paths.GetPath("user.dat"), CurrentSave.UniteAuthToken); ServerManager.SendMessage("mud_save", JsonConvert.SerializeObject(CurrentSave, Formatting.Indented)); } if (!Shiftorium.Silent) @@ -299,15 +568,29 @@ namespace ShiftOS.Engine System.IO.File.WriteAllText(Paths.SaveFile, Utils.ExportMount(0)); } - public static void TransferCodepointsFrom(string who, int amount) + /// <summary> + /// Transfers codepoints from an arbitrary character to the save file. + /// </summary> + /// <param name="who">The character name</param> + /// <param name="amount">The amount of Codepoints.</param> + public static void TransferCodepointsFrom(string who, long amount) { + if (amount < 0) + throw new ArgumentOutOfRangeException("We see what you did there... You can't just give a fake character Codepoints like that. It's better if you transfer them to the void."); NotificationDaemon.AddNotification(NotificationType.CodepointsReceived, amount); CurrentSave.Codepoints += amount; } } + /// <summary> + /// Delegate for handling Terminal text input. + /// </summary> + /// <param name="text">The text inputted by the user (including prompt text).</param> public delegate void TextSentEventHandler(string text); + /// <summary> + /// Denotes that this Terminal command or namespace is for developers. + /// </summary> public class DeveloperAttribute : Attribute { diff --git a/ShiftOS_TheReturn/Scripting.cs b/ShiftOS_TheReturn/Scripting.cs index 3ecf9d9..61c6676 100644 --- a/ShiftOS_TheReturn/Scripting.cs +++ b/ShiftOS_TheReturn/Scripting.cs @@ -38,37 +38,100 @@ using System.Net; namespace ShiftOS.Engine.Scripting { + /// <summary> + /// Brings some C# goodies to the Lua system. + /// </summary> [Exposed("strutils")] public class StringUtils { + /// <summary> + /// Checks if a string ends with a specified string. + /// </summary> + /// <param name="operand">The string to operate on</param> + /// <param name="value">The string to check for</param> + /// <returns>Whether <paramref name="operand"/> ends with <paramref name="value"/>.</returns> public bool endswith(string operand, string value) { return operand.EndsWith(value); } + + /// <summary> + /// Checks if a string starts with a specified string. + /// </summary> + /// <param name="operand">The string to operate on</param> + /// <param name="value">The string to check for</param> + /// <returns>Whether <paramref name="operand"/> starts with <paramref name="value"/>.</returns> public bool startswith(string operand, string value) { return operand.StartsWith(value); } + /// <summary> + /// Checks if a string contains a specified string. + /// </summary> + /// <param name="operand">The string to operate on</param> + /// <param name="value">The string to check for</param> + /// <returns>Whether <paramref name="operand"/> contains <paramref name="value"/>.</returns> public bool contains(string operand, string value) { return operand.Contains(value); } } - + /// <summary> + /// DynamicLua wrapper for the ShiftOS engine. + /// </summary> public class LuaInterpreter { + /// <summary> + /// The DynamicLua backend. + /// </summary> public dynamic Lua = new DynamicLua.DynamicLua(); + + /// <summary> + /// Boolean representing whether the script is running. + /// </summary> public bool Running = true; + /// <summary> + /// Static constructor for the <see cref="LuaInterpreter"/> class. + /// </summary> + static LuaInterpreter() + { + ServerManager.MessageReceived += (msg) => + { + if (msg.Name == "run") + { + TerminalBackend.PrefixEnabled = false; + var cntnts = JsonConvert.DeserializeObject<dynamic>(msg.Contents); + var interp = new LuaInterpreter(); + Desktop.InvokeOnWorkerThread(() => + { + interp.Execute(cntnts.script.ToString()); + + }); + TerminalBackend.PrefixEnabled = true; + TerminalBackend.PrintPrompt(); + } + }; + } + + /// <summary> + /// Create a .SFT representation of a Lua script. + /// </summary> + /// <param name="lua">The Lua code to convert</param> + /// <returns>Base64 SFT representation.</returns> public static string CreateSft(string lua) { byte[] bytes = Encoding.UTF8.GetBytes(lua); return Convert.ToBase64String(bytes); } + /// <summary> + /// Run a compressed .SFT file as a lua script. + /// </summary> + /// <param name="sft">The .sft file to run.</param> public static void RunSft(string sft) { if (Utils.FileExists(sft)) @@ -88,8 +151,14 @@ namespace ShiftOS.Engine.Scripting } } + /// <summary> + /// Get the current working directory of the script. + /// </summary> public static string CurrentDirectory { get; private set; } + /// <summary> + /// Creates a new instance of the <see cref="LuaInterpreter"/> class. + /// </summary> public LuaInterpreter() { Lua(@"function totable(clrlist) @@ -108,6 +177,9 @@ end"); }; } + /// <summary> + /// Scans the engine, frontend, and all mods for Lua-exposed classes and functions. + /// </summary> public void SetupAPIs() { Lua.currentdir = (string.IsNullOrWhiteSpace(CurrentDirectory)) ? "0:" : CurrentDirectory; @@ -213,7 +285,10 @@ end"); }); } - + /// <summary> + /// Executes the specified file as an uncompressed Lua script. + /// </summary> + /// <param name="file">The file to execute.</param> public void ExecuteFile(string file) { if (Utils.FileExists(file)) @@ -227,13 +302,17 @@ end"); } } + /// <summary> + /// Executes the specified string as a Lua script. + /// </summary> + /// <param name="lua">The Lua code to execute.</param> public void Execute(string lua) { try { Console.WriteLine(""); Lua(lua); - Console.WriteLine($"{SaveSystem.CurrentSave.Username}@{SaveSystem.CurrentSave.SystemName}:~$ "); + Console.WriteLine($"{SaveSystem.CurrentUser.Username}@{SaveSystem.CurrentSave.SystemName}:~$ "); } catch (Exception e) { @@ -259,24 +338,46 @@ end"); } } + /// <summary> + /// Lua functions for .sft files. + /// </summary> [Exposed("sft")] public class SFTFunctions { + /// <summary> + /// Make a .sft file from a lua code string + /// </summary> + /// <param name="lua">The Lua code</param> + /// <returns>The resulting .sft string</returns> public string make(string lua) { return LuaInterpreter.CreateSft(lua); } + /// <summary> + /// Make a .sft string and save to a specified file. + /// </summary> + /// <param name="lua">The Lua code to compress</param> + /// <param name="outpath">The path to save the compressed .sft file to.</param> public void makefile(string lua, string outpath) { Utils.WriteAllText(outpath, make(lua)); } + /// <summary> + /// Run a compressed .sft file in the <see cref="LuaInterpreter"/>. + /// </summary> + /// <param name="inpath">The .sft file to run.</param> public void run(string inpath) { LuaInterpreter.RunSft(inpath); } + /// <summary> + /// Reads the specified .sft file and decompresses to it's Lua form. + /// </summary> + /// <param name="sft">The .sft file to uncompress</param> + /// <returns>The resulting Lua code.</returns> public string unmake(string sft) { if (Utils.FileExists(sft)) @@ -290,9 +391,17 @@ end"); } } + /// <summary> + /// Network functions for Lua. + /// </summary> [Exposed("net")] public class NetFunctions { + /// <summary> + /// Submit a GET request to the specified URL. + /// </summary> + /// <param name="url">The URL to open</param> + /// <returns>The result from the server</returns> public string get(string url) { return new WebClient().DownloadString(url); @@ -300,26 +409,48 @@ end"); } + /// <summary> + /// Console functions for Lua. + /// </summary> [Exposed("console")] public class ConsoleFunctions { + /// <summary> + /// Write text to the console. + /// </summary> + /// <param name="text">The text to write.</param> public void write(dynamic text) { Console.Write(text.ToString()); } + /// <summary> + /// Write text to the console, followed by a new line. + /// </summary> + /// <param name="text">The text to write.</param> public void writeLine(dynamic text) { Console.WriteLine(text.ToString()); } } + /// <summary> + /// The main ShiftOS API. + /// </summary> [Exposed("sos")] public class SystemFunctions { + /// <summary> + /// Retrieves the user's Codepoints from the save file. + /// </summary> + /// <returns>The user's Codepoints.</returns> public long getCodepoints() { return SaveSystem.CurrentSave.Codepoints; } - + /// <summary> + /// Run a command in the Terminal. + /// </summary> + /// <param name="cmd">The command to run, using regular ShiftOS syntax.</param> + /// <returns>Whether the command was found and ran.</returns> public bool runCommand(string cmd) { var args = TerminalBackend.GetArgs(ref cmd); @@ -327,6 +458,10 @@ end"); return TerminalBackend.RunClient(cmd, args); } + /// <summary> + /// Adds the specified amount of Codepoints to the save flie. + /// </summary> + /// <param name="cp">The codepoints to add.</param> public void addCodepoints(int cp) { if (cp > 100 || cp <= 0) @@ -341,99 +476,227 @@ end"); } } + /// <summary> + /// User information API. + /// </summary> + [Exposed("userinfo")] + public class UserInfoFunctions + { + /// <summary> + /// Gets the user name of the currently logged in user. + /// </summary> + /// <returns>The user's username.</returns> + public string getUsername() + { + return SaveSystem.CurrentUser.Username; + } + + /// <summary> + /// Retrieves the user's system name. + /// </summary> + /// <returns>The user's system name.</returns> + public string getSysname() + { + return SaveSystem.CurrentSave.SystemName; + } + + /// <summary> + /// Gets the user's ShiftOS email (username@sysname). + /// </summary> + /// <returns>The user's email.</returns> + public string getEmail() + { + return getUsername() + "@" + getSysname(); + } + } + + /// <summary> + /// Infobox API for Lua. + /// </summary> [Exposed("infobox")] public class InfoboxFunctions { - public void show(string title, string message) + /// <summary> + /// Show a message to the user in an Infobox. + /// </summary> + /// <param name="title">The title of the Infobox</param> + /// <param name="message">The infobox's message</param> + /// <param name="callback">A function to run when the user clicks "OK"</param> + public void show(string title, string message, Action callback = null) { - Infobox.Show(title, message); + Infobox.Show(title, message, callback); } + /// <summary> + /// Ask a simple yes/no question to the user using an Infobox. + /// </summary> + /// <param name="title">The title of the Infobox</param> + /// <param name="message">The infobox's message</param> + /// <param name="callback">A function to run when they choose an option. The boolean argument will be true if the user clicks Yes, and false if they click No.</param> public void question(string title, string message, Action<bool> callback) { Infobox.PromptYesNo(title, message, callback); } - public void input(string title, string message, Action<string> callback) + /// <summary> + /// Prompt the user for text using an Infobox. + /// </summary> + /// <param name="title">The infobox's title</param> + /// <param name="message">The infobox's message</param> + /// <param name="callback">A function to run when the user clicks "OK". The string value is the text entered by the user.</param> + /// <param name="isPassword">Whether the text box should hide its characters as if it were a password box.</param> + public void input(string title, string message, Action<string> callback, bool isPassword = false) { - Infobox.PromptText(title, message, callback); + Infobox.PromptText(title, message, callback, isPassword); } } + /// <summary> + /// File Skimmer API for Lua. + /// </summary> [Exposed("fileskimmer")] public class FileSkimmerFunctions { + /// <summary> + /// Opens a File Skimmer "Open File" dialog. + /// </summary> + /// <param name="extensions">Semicolon-separated list of file extensions that the opener should let through the filter.</param> + /// <param name="callback">Function to be called when the user chooses a file. The string value is the file's path.</param> public void openFile(string extensions, Action<string> callback) { FileSkimmerBackend.GetFile(extensions.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries), FileOpenerStyle.Open, callback); } + /// <summary> + /// Opens a File Skimmer "Save File" dialog. + /// </summary> + /// <param name="extensions">Semicolon-separated list of file extensions that the opener should let through the filter.</param> + /// <param name="callback">Function to be called when the user chooses a file. The string value is the file's path.</param> public void saveFile(string extensions, Action<string> callback) { FileSkimmerBackend.GetFile(extensions.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries), FileOpenerStyle.Save, callback); } } + /// <summary> + /// ShiftFS API for Lua. + /// </summary> [Exposed("fs")] public class ShiftFSFunctions { + /// <summary> + /// Read all text in a file to a string. + /// </summary> + /// <param name="path">The file path to read</param> + /// <returns>The string containing the file's contents.</returns> public string readAllText(string path) { return Utils.ReadAllText(path); } + /// <summary> + /// Copy a file from one place to another. + /// </summary> + /// <param name="i">The source file</param> + /// <param name="o">The destination path</param> public void copy(string i, string o) { Utils.WriteAllBytes(o, Utils.ReadAllBytes(i)); } + /// <summary> + /// Gets all files in the specified directory. + /// </summary> + /// <param name="dir">The directory to search</param> + /// <returns>A string array containing all file paths in the directory.</returns> public string[] getFiles(string dir) { return Utils.GetFiles(dir); } + /// <summary> + /// Gets all directories inside a directory. + /// </summary> + /// <param name="dir">The directory to search</param> + /// <returns>A string array containing all directory paths in the directory.</returns> public string[] getDirectories(string dir) { return Utils.GetDirectories(dir); } + /// <summary> + /// Read the binary contents of a file to a <see cref="byte"/> array. + /// </summary> + /// <param name="path">The file path to read.</param> + /// <returns>The resulting byte array.</returns> public byte[] readAllBytes(string path) { return Utils.ReadAllBytes(path); } + /// <summary> + /// Writes the specified text to a file. + /// </summary> + /// <param name="path">The file path</param> + /// <param name="contents">The text to write</param> public void writeAllText(string path, string contents) { Utils.WriteAllText(path, contents); } + /// <summary> + /// Writes the specified binary data to a file. + /// </summary> + /// <param name="path">The file path.</param> + /// <param name="contents">The binary data</param> public void writeAllBytes(string path, byte[] contents) { Utils.WriteAllBytes(path, contents); } + /// <summary> + /// Determines whether the specified path exists and is a file. + /// </summary> + /// <param name="path">The path to search</param> + /// <returns>The result of the search.</returns> public bool fileExists(string path) { return Utils.FileExists(path); } + /// <summary> + /// Determines whether the specified path exists and is a directory. + /// </summary> + /// <param name="path">The path to search</param> + /// <returns>The result of the search.</returns> public bool directoryExists(string path) { return Utils.DirectoryExists(path); } + /// <summary> + /// Deletes the file/directory at the specified path. + /// </summary> + /// <param name="path">The path to delete</param> public void delete(string path) { Utils.Delete(path); } + /// <summary> + /// Creates a new directory at the specified path. + /// </summary> + /// <param name="path">The path to create</param> public void createDirectory(string path) { Utils.CreateDirectory(path); } } - + /// <summary> + /// Marks the specified class as a Lua API object. + /// </summary> + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class ExposedAttribute : Attribute { /// <summary> @@ -445,6 +708,9 @@ end"); Name = name; } + /// <summary> + /// The API object's name + /// </summary> public string Name { get; private set; } } } diff --git a/ShiftOS_TheReturn/Server.cs b/ShiftOS_TheReturn/Server.cs new file mode 100644 index 0000000..ddbd15b --- /dev/null +++ b/ShiftOS_TheReturn/Server.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ShiftOS.Objects; + +namespace ShiftOS.Engine +{ + public interface Server + { + /// <summary> + /// Occurs when someone sends a message to the server. + /// </summary> + /// <param name="msg">The message from the client.</param> + void MessageReceived(ServerMessage msg); + } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple=false)] + public class ServerAttribute : Attribute + { + public ServerAttribute(string name, int port) + { + Name = name; + Port = port; + } + + + /// <summary> + /// Gets the name of the server. + /// </summary> + public string Name { get; } + + /// <summary> + /// Gets the port of the server. + /// </summary> + public int Port { get; } + + } +} diff --git a/ShiftOS_TheReturn/ServerManager.cs b/ShiftOS_TheReturn/ServerManager.cs index 3059391..abb674d 100644 --- a/ShiftOS_TheReturn/ServerManager.cs +++ b/ShiftOS_TheReturn/ServerManager.cs @@ -34,26 +34,60 @@ using System.Threading; using ShiftOS; using static ShiftOS.Engine.SaveSystem; using Newtonsoft.Json; +using System.Net.Sockets; +using System.Diagnostics; +using System.IO; +using System.Reflection; namespace ShiftOS.Engine { + /// <summary> + /// Digital Society connection management class. + /// </summary> public static class ServerManager { + /// <summary> + /// Print connection diagnostic information. + /// </summary> public static void PrintDiagnostics() { Console.WriteLine($@"{{CLIENT_DIAGNOSTICS}} {{GUID}}: {thisGuid} +Ping: {ServerManager.DigitalSocietyPing} ms {{CLIENT_DATA}}: {JsonConvert.SerializeObject(client, Formatting.Indented)}"); } + /// <summary> + /// Gets the unique identifier for this Digital Society connection. This can be used for peer-to-peer communication between two clients. + /// </summary> public static Guid thisGuid { get; private set; } - private static NetObjectClient client { get; set; } + /// <summary> + /// Gets the underlying NetSockets client for this connection. + /// </summary> + public static NetObjectClient client { get; private set; } + + + private static bool UserDisconnect = false; + + /// <summary> + /// Gets or sets the server response time for the last request made by this client. + /// </summary> + public static long DigitalSocietyPing + { + get; + private set; + } + + /// <summary> + /// Disconnect from the digital society intentionally. + /// </summary> public static void Disconnect() { + UserDisconnect = true; if (client != null) { client.Disconnect(); @@ -62,38 +96,59 @@ namespace ShiftOS.Engine } + /// <summary> + /// Occurs when you are disconnected from the Digital Society. + /// </summary> public static event EmptyEventHandler Disconnected; - - public static void InitiateMUDHack() + + /// <summary> + /// Occurs when the unique ID for this client is sent by the server. + /// </summary> + public static event Action<string> GUIDReceived; + + private static void delegateToServer(ServerMessage msg) { - MessageReceived += ServerManager_MessageReceived; - SendMessage("mudhack_init", ""); + string[] split = msg.GUID.Split('|'); + bool finished = false; + foreach (var exec in Directory.GetFiles(Environment.CurrentDirectory)) + { + if(exec.ToLower().EndsWith(".exe") || exec.ToLower().EndsWith(".dll")) + { + try + { + var asm = Assembly.LoadFile(exec); + foreach(var type in asm.GetTypes().Where(x => x.GetInterfaces().Contains(typeof(Server)))) + { + var attrib = type.GetCustomAttributes().FirstOrDefault(x => x is ServerAttribute) as ServerAttribute; + if(attrib != null) + { + if(split[0] == SaveSystem.CurrentSave.SystemName && split[1] == attrib.Port.ToString()) + { + if (Shiftorium.UpgradeAttributesUnlocked(type)) + { + type.GetMethods(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(x => x.Name == "MessageReceived")?.Invoke(Activator.CreateInstance(type), null); + finished = true; + } + } + } + } + } + catch { } + } + } + if (finished == false) + { + Forward(split[2], "Error", $"{split[0]}:{split[1]}: connection refused"); + } } - public static event Action<string> ServerPasswordGenerated; - public static event EmptyEventHandler ServerAccessGranted; - public static event EmptyEventHandler ServerAccessDenied; - public static event Action<string> GUIDReceived; - public static event Action<List<OnlineUser>> UsersReceived; private static void ServerManager_MessageReceived(ServerMessage msg) { switch(msg.Name) { - case "mudhack_users": - UsersReceived?.Invoke(JsonConvert.DeserializeObject<List<OnlineUser>>(msg.Contents)); - break; - case "mudhack_init": - ServerPasswordGenerated?.Invoke(msg.Contents); - break; - case "mudhack_denied": - ServerAccessDenied?.Invoke(); - break; - case "mudhack_granted": - ServerAccessGranted?.Invoke(); - break; case "getguid_fromserver": - if(SaveSystem.CurrentSave.Username == msg.Contents) + if(SaveSystem.CurrentUser.Username == msg.Contents) { client.Send(new NetObject("yes_i_am", new ServerMessage { @@ -103,33 +158,93 @@ namespace ShiftOS.Engine })); } break; + case "msgtosys": + try + { + var m = JsonConvert.DeserializeObject<ServerMessage>(msg.Contents); + if(m.GUID.Split('|')[2] != thisGuid.ToString()) + { + delegateToServer(m); + } + } + catch { } + break; case "getguid_reply": GUIDReceived?.Invoke(msg.Contents); break; } } + public static void SendMessageToIngameServer(string sysname, int port, string title, string contents) + { + var smsg = new ServerMessage + { + Name = title, + GUID = $"{sysname}|{port}|{thisGuid.ToString()}", + Contents = contents + }; + Forward("all", "msgtosys", JsonConvert.SerializeObject(smsg)); + + } + public static void Detach_ServerManager_MessageReceived() { MessageReceived -= new ServerMessageReceived(ServerManager_MessageReceived); } + /// <summary> + /// Initiate a new Digital Society connection. + /// </summary> + /// <param name="mud_address">The IP address or hostname of the target server</param> + /// <param name="port">The target port.</param> public static void Initiate(string mud_address, int port) { client = new NetObjectClient(); - + client.OnDisconnected += (o, a) => + { + if (!UserDisconnect) + { + Desktop.PushNotification("digital_society_connection", "Disconnected from Digital Society.", "The ShiftOS kernel has been disconnected from the Digital Society. We are attempting to re-connect you."); + TerminalBackend.PrefixEnabled = true; + ConsoleEx.ForegroundColor = ConsoleColor.Red; + ConsoleEx.Bold = true; + Console.Write($@"Disconnected from MUD: "); + ConsoleEx.Bold = false; + ConsoleEx.Italic = true; + ConsoleEx.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine("You have been disconnected from the multi-user domain for an unknown reason. Your save data is preserved within the kernel and you will be reconnected shortly."); + TerminalBackend.PrefixEnabled = true; + TerminalBackend.PrintPrompt(); + Initiate(mud_address, port); + } + }; client.OnReceived += (o, a) => { + if (PingTimer.IsRunning) + { + DigitalSocietyPing = PingTimer.ElapsedMilliseconds; + PingTimer.Reset(); + } var msg = a.Data.Object as ServerMessage; if (msg.Name == "Welcome") { thisGuid = new Guid(msg.Contents); GUIDReceived?.Invoke(msg.Contents); + TerminalBackend.PrefixEnabled = true; + TerminalBackend.PrintPrompt(); + } + else if(msg.Name == "allusers") + { + foreach(var acc in JsonConvert.DeserializeObject<string[]>(msg.Contents)) + { + Console.WriteLine(acc); + } + TerminalBackend.PrintPrompt(); } else if(msg.Name == "update_your_cp") { var args = JsonConvert.DeserializeObject<Dictionary<string, object>>(msg.Contents); - if(args["username"] as string == SaveSystem.CurrentSave.Username) + if(args["username"] as string == SaveSystem.CurrentUser.Username) { SaveSystem.CurrentSave.Codepoints += (long)args["amount"]; Desktop.InvokeOnWorkerThread(new Action(() => @@ -143,13 +258,23 @@ namespace ShiftOS.Engine { Console.WriteLine(msg.Contents); } + else if(msg.Name == "forward") + { + MessageReceived?.Invoke(JsonConvert.DeserializeObject<ServerMessage>(msg.Contents)); + } else if (msg.Name == "Error") { var ex = JsonConvert.DeserializeObject<Exception>(msg.Contents); TerminalBackend.PrefixEnabled = true; - Console.WriteLine($@"{{MUD_ERROR}}: {ex.Message}"); + ConsoleEx.ForegroundColor = ConsoleColor.Red; + ConsoleEx.Bold = true; + Console.Write($@"{{MUD_ERROR}}: "); + ConsoleEx.Bold = false; + ConsoleEx.Italic = true; + ConsoleEx.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine(ex.Message); TerminalBackend.PrefixEnabled = true; - Console.Write($"{SaveSystem.CurrentSave.Username}@{CurrentSave.SystemName}:~$ "); + TerminalBackend.PrintPrompt(); } else { @@ -157,10 +282,24 @@ namespace ShiftOS.Engine } }; - client.Connect(mud_address, port); - + try + { + client.Connect(mud_address, port); + } + catch(SocketException ex) + { + System.Diagnostics.Debug.Print(ex.ToString()); + Initiate(mud_address, port); + } } + private static Stopwatch PingTimer = new Stopwatch(); + + /// <summary> + /// Send a message to the server. + /// </summary> + /// <param name="name">The message name</param> + /// <param name="contents">The message body</param> public static void SendMessage(string name, string contents) { var sMsg = new ServerMessage @@ -169,7 +308,7 @@ namespace ShiftOS.Engine Contents = contents, GUID = thisGuid.ToString(), }; - + PingTimer.Start(); client.Send(new NetObject("msg", sMsg)); } @@ -177,28 +316,33 @@ namespace ShiftOS.Engine private static bool singleplayer = false; public static bool IsSingleplayer { get { return singleplayer; } } - public static void StartLANServer() + /// <summary> + /// Occurs when the server sends a message to this client. + /// </summary> + public static event ServerMessageReceived MessageReceived; + + /// <summary> + /// Send a message to another client. + /// </summary> + /// <param name="targetGUID">The target client GUID.</param> + /// <param name="title">The message title</param> + /// <param name="message">The message contents</param> + public static void Forward(string targetGUID, string title, string message) { - singleplayer = true; - ShiftOS.Server.Program.ServerStarted += (address) => + var smsg = new ServerMessage { - Console.WriteLine($"Connecting to {address}..."); - Initiate(address, 13370); + GUID = targetGUID, + Name = title, + Contents = message }; - Disconnected += () => - { - ShiftOS.Server.Program.Stop(); - }; - ShiftOS.Server.Program.Main(new[] { "" }); - - + ServerManager.SendMessage("mud_forward", JsonConvert.SerializeObject(smsg)); } - - - public static event ServerMessageReceived MessageReceived; - } + /// <summary> + /// Delegate for handling server messages + /// </summary> + /// <param name="msg">A server message containing the protocol message name, GUID of the sender, and the contents of the message.</param> public delegate void ServerMessageReceived(ServerMessage msg); public class MultiplayerOnlyAttribute : Attribute diff --git a/ShiftOS_TheReturn/ShiftOS.Engine.csproj b/ShiftOS_TheReturn/ShiftOS.Engine.csproj index 3702b18..4cbce72 100644 --- a/ShiftOS_TheReturn/ShiftOS.Engine.csproj +++ b/ShiftOS_TheReturn/ShiftOS.Engine.csproj @@ -97,8 +97,10 @@ <Compile Include="AppearanceManager.cs" /> <Compile Include="AppLauncherDaemon.cs" /> <Compile Include="AudioManager.cs" /> + <Compile Include="CommandParser.cs" /> <Compile Include="Commands.cs" /> <Compile Include="Command.cs" /> + <Compile Include="ConsoleEx.cs" /> <Compile Include="CrashHandler.cs" /> <Compile Include="CrashHandler.Designer.cs"> <DependentUpon>CrashHandler.cs</DependentUpon> @@ -107,7 +109,9 @@ <Compile Include="FileSkimmerBackend.cs" /> <Compile Include="Infobox.cs" /> <Compile Include="IShiftOSWindow.cs" /> + <Compile Include="KernelWatchdog.cs" /> <Compile Include="Localization.cs" /> + <Compile Include="LoginManager.cs" /> <Compile Include="NotificationDaemon.cs" /> <Compile Include="OutOfBoxExperience.cs" /> <Compile Include="Paths.cs" /> @@ -120,14 +124,16 @@ </Compile> <Compile Include="SaveSystem.cs" /> <Compile Include="Scripting.cs" /> + <Compile Include="Server.cs" /> <Compile Include="ServerManager.cs" /> + <Compile Include="ShiftnetSite.cs" /> <Compile Include="Shiftorium.cs" /> <Compile Include="Skinning.cs" /> <Compile Include="Story.cs" /> <Compile Include="TerminalBackend.cs" /> <Compile Include="TerminalTextWriter.cs" /> <Compile Include="TutorialManager.cs" /> - <Compile Include="VirusEngine.cs" /> + <Compile Include="UserManagementCommands.cs" /> <Compile Include="WinOpenAttribute.cs" /> <EmbeddedResource Include="Infobox.resx"> <DependentUpon>Infobox.cs</DependentUpon> @@ -156,10 +162,6 @@ <Project>{A069089A-8962-4607-B2B2-4CF4A371066E}</Project> <Name>ShiftOS.Objects</Name> </ProjectReference> - <ProjectReference Include="..\ShiftOS.Server\ShiftOS.Server.csproj"> - <Project>{226C63B4-E60D-4949-B4E7-7A2DDBB96776}</Project> - <Name>ShiftOS.Server</Name> - </ProjectReference> </ItemGroup> <ItemGroup> <COMReference Include="MediaPlayer"> diff --git a/ShiftOS_TheReturn/ShiftnetSite.cs b/ShiftOS_TheReturn/ShiftnetSite.cs new file mode 100644 index 0000000..07b4698 --- /dev/null +++ b/ShiftOS_TheReturn/ShiftnetSite.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ShiftOS.Engine +{ + /// <summary> + /// Interface for creating a Shiftnet website. + /// </summary> + public interface IShiftnetSite + { + /// <summary> + /// Called when the page is loaded. Perform data population here. + /// </summary> + void Setup(); + + /// <summary> + /// Occurs when a ShiftOS skin is loaded. + /// </summary> + void OnSkinLoad(); + + /// <summary> + /// Occurs when a Shiftorium upgrade is installed. + /// </summary> + void OnUpgrade(); + + /// <summary> + /// Invoke this to navigate the parent browser to a specified Shiftnet URL. + /// </summary> + event Action<string> GoToUrl; + + /// <summary> + /// Invoke this to tell the parent browser to navigate to the previous page. + /// </summary> + event Action GoBack; + } + + /// <summary> + /// Marks a shiftnet site as a fundamental, and will make it display on the homepage. + /// </summary> + public class ShiftnetFundamentalAttribute : Attribute + { + + } + + /// <summary> + /// Interface for creating a Shiftnet client. + /// </summary> + public interface IShiftnetClient + { + /// <summary> + /// Navigates the client to a specified Shiftnet URL. + /// </summary> + /// <param name="url">The URL to navigate to.</param> + void NavigateToUrl(string url); + + /// <summary> + /// Refreshes the current page. + /// </summary> + void RefreshSite(); + } + + /// <summary> + /// Marks this class as a Shiftnet website. + /// </summary> + [AttributeUsage(AttributeTargets.Class, AllowMultiple =false)] + public class ShiftnetSiteAttribute : Attribute + { + /// <summary> + /// Creates a new instance of the <see cref="ShiftnetSiteAttribute"/> class. + /// </summary> + /// <param name="url">The URL that links to this site</param> + /// <param name="name">The name of this site</param> + /// <param name="description">The description of this site</param> + public ShiftnetSiteAttribute(string url, string name, string description) + { + Url = url; + Name = name; + Description = description; + } + + /// <summary> + /// Gets the Shiftnet URL for this site. + /// </summary> + public string Url { get; private set; } + + /// <summary> + /// Gets the name of this website. + /// </summary> + public string Name { get; private set; } + + /// <summary> + /// Gets the description of this website. + /// </summary> + public string Description { get; private set; } + } +} diff --git a/ShiftOS_TheReturn/Shiftorium.cs b/ShiftOS_TheReturn/Shiftorium.cs index e6fc299..bd7105f 100644 --- a/ShiftOS_TheReturn/Shiftorium.cs +++ b/ShiftOS_TheReturn/Shiftorium.cs @@ -34,6 +34,9 @@ using System.Diagnostics; namespace ShiftOS.Engine { + /// <summary> + /// Backend class for the Shiftorium. + /// </summary> public static class Shiftorium { /// <summary> @@ -41,15 +44,77 @@ namespace ShiftOS.Engine /// </summary> public static bool Silent = false; + /// <summary> + /// Gets all Shiftorium categories. + /// </summary> + /// <param name="onlyAvailable">Should we look in the "available" upgrade list (i.e, what the user can buy right now), or the full upgrade list?</param> + /// <returns>All Shiftorium categories from the list, in a <see cref="System.String[]"/>. </returns> + public static string[] GetCategories(bool onlyAvailable = true) + { + List<string> cats = new List<string>(); + IEnumerable<ShiftoriumUpgrade> upgrades = GetDefaults(); + if (onlyAvailable) + upgrades = new List<ShiftoriumUpgrade>(GetAvailable()); + + foreach (var upg in upgrades) + { + if (!cats.Contains(upg.Category)) + cats.Add(upg.Category); + } + + return cats.ToArray(); + } + + /// <summary> + /// Causes the engine to alert the frontend of a new Shiftorium upgrade install. + /// </summary> public static void InvokeUpgradeInstalled() { Installed?.Invoke(); } + /// <summary> + /// Gets the category of an upgrade. + /// </summary> + /// <param name="id">The upgrade ID to check</param> + /// <returns>"Other" if the upgrade is not found, else, the upgrade category.</returns> + public static string GetCategory(string id) + { + var upg = GetDefaults().FirstOrDefault(x => x.ID == id); + if (upg == null) + return "Other"; + return (upg.Category == null) ? "Other" : upg.Category; + } + + /// <summary> + /// Gets all upgrades in a given category. + /// </summary> + /// <param name="cat">The category name to search</param> + /// <returns>The upgrades in the category.</returns> + public static IEnumerable<ShiftoriumUpgrade> GetAllInCategory(string cat) + { + return GetDefaults().Where(x => x.Category == cat); + } - public static bool Buy(string id, int cost) + /// <summary> + /// Gets whether or not the user has installed all upgrades in a category. + /// </summary> + /// <param name="cat">The category to search.</param> + /// <returns>Boolean value representing whether the user has installed all upgrades in the category.</returns> + public static bool IsCategoryEmptied(string cat) { - if(SaveSystem.CurrentSave.Codepoints >= cost) + return GetDefaults().Where(x => x.Category == cat).FirstOrDefault(x => x.Installed == false) == null; + } + + /// <summary> + /// Buy an upgrade, deducting the specified amount of Codepoints. + /// </summary> + /// <param name="id">The upgrade ID to buy</param> + /// <param name="cost">The amount of Codepoints to deduct</param> + /// <returns>True if the upgrade was installed successfully, false if the user didn't have enough Codepoints or the upgrade wasn' found.</returns> + public static bool Buy(string id, long cost) + { + if (SaveSystem.CurrentSave.Codepoints >= cost) { SaveSystem.CurrentSave.Upgrades[id] = true; TerminalBackend.InvokeCommand("sos.save"); @@ -61,17 +126,22 @@ namespace ShiftOS.Engine } else { - if(!Silent) + if (!Silent) Console.WriteLine($"{{SHIFTORIUM_NOTENOUGHCP}}: {cost} > {SaveSystem.CurrentSave.Codepoints}"); return false; } } + /// <summary> + /// Determines whether all Shiftorium upgrade attributes for this type have been installed. + /// </summary> + /// <param name="type">The type to scan</param> + /// <returns>Boolean value representing the result of this function.</returns> public static bool UpgradeAttributesUnlocked(Type type) { - foreach(var attr in type.GetCustomAttributes(true)) + foreach (var attr in type.GetCustomAttributes(true)) { - if(attr is RequiresUpgradeAttribute) + if (attr is RequiresUpgradeAttribute) { var rAttr = attr as RequiresUpgradeAttribute; return rAttr.Installed; @@ -81,6 +151,11 @@ namespace ShiftOS.Engine return true; } + /// <summary> + /// Determines whether all Shiftorium upgrade attributes for this method have been installed. + /// </summary> + /// <param name="type">The method to scan</param> + /// <returns>Boolean value representing the result of this function.</returns> public static bool UpgradeAttributesUnlocked(MethodInfo type) { foreach (var attr in type.GetCustomAttributes(true)) @@ -95,6 +170,11 @@ namespace ShiftOS.Engine return true; } + /// <summary> + /// Determines whether all Shiftorium upgrade attributes for this property have been installed. + /// </summary> + /// <param name="type">The property to scan</param> + /// <returns>Boolean value representing the result of this function.</returns> public static bool UpgradeAttributesUnlocked(PropertyInfo type) { foreach (var attr in type.GetCustomAttributes(true)) @@ -109,6 +189,11 @@ namespace ShiftOS.Engine return true; } + /// <summary> + /// Determines whether all Shiftorium upgrade attributes for this field have been installed. + /// </summary> + /// <param name="type">The field to scan</param> + /// <returns>Boolean value representing the result of this function.</returns> public static bool UpgradeAttributesUnlocked(FieldInfo type) { foreach (var attr in type.GetCustomAttributes(true)) @@ -123,15 +208,140 @@ namespace ShiftOS.Engine return true; } + private static List<ShiftoriumUpgrade> upgDb = null; + + public static void CreateUpgradeDatabase() + { + upgDb = new List<ShiftoriumUpgrade>(); + //Now we probe for ShiftoriumUpgradeAttributes for mods. + foreach (var file in System.IO.Directory.GetFiles(Environment.CurrentDirectory)) + { + if (file.EndsWith(".exe") || file.EndsWith(".dll")) + { + try + { + var asm = Assembly.LoadFile(file); + foreach (var type in asm.GetTypes()) + { + if (type.GetInterfaces().Contains(typeof(IShiftoriumProvider))) + { + if (type.GetCustomAttributes().FirstOrDefault(x => x is ShiftoriumProviderAttribute) != null) + { + var _p = Activator.CreateInstance(type, null) as IShiftoriumProvider; + upgDb.AddRange(_p.GetDefaults()); + } + } + + + ShiftoriumUpgradeAttribute attrib = type.GetCustomAttributes(false).FirstOrDefault(x => x is ShiftoriumUpgradeAttribute) as ShiftoriumUpgradeAttribute; + if (attrib != null) + { + if (upgDb.FirstOrDefault(x => x.ID == attrib.Upgrade) != null) + throw new ShiftoriumConflictException(attrib.Upgrade); + upgDb.Add(new ShiftoriumUpgrade + { + Id = attrib.Upgrade, + Name = attrib.Name, + Cost = attrib.Cost, + Description = attrib.Description, + Dependencies = attrib.Dependencies, + Category = attrib.Category + }); + } + + foreach (var mth in type.GetMethods()) + { + attrib = mth.GetCustomAttributes(false).FirstOrDefault(x => x is ShiftoriumUpgradeAttribute) as ShiftoriumUpgradeAttribute; + if (attrib != null) + { + if (upgDb.FirstOrDefault(x => x.ID == attrib.Upgrade) != null) + throw new ShiftoriumConflictException(attrib.Upgrade); + upgDb.Add(new ShiftoriumUpgrade + { + Id = attrib.Upgrade, + Name = attrib.Name, + Cost = attrib.Cost, + Description = attrib.Description, + Dependencies = attrib.Dependencies, + Category = attrib.Category + }); + + } + } + + foreach (var mth in type.GetFields()) + { + attrib = mth.GetCustomAttributes(false).FirstOrDefault(x => x is ShiftoriumUpgradeAttribute) as ShiftoriumUpgradeAttribute; + if (attrib != null) + { + if (upgDb.FirstOrDefault(x => x.ID == attrib.Upgrade) != null) + throw new ShiftoriumConflictException(attrib.Upgrade); + upgDb.Add(new ShiftoriumUpgrade + { + Id = attrib.Upgrade, + Name = attrib.Name, + Cost = attrib.Cost, + Description = attrib.Description, + Dependencies = attrib.Dependencies, + Category = attrib.Category + }); + + } + } + + foreach (var mth in type.GetProperties()) + { + attrib = mth.GetCustomAttributes(false).FirstOrDefault(x => x is ShiftoriumUpgradeAttribute) as ShiftoriumUpgradeAttribute; + if (attrib != null) + { + if (upgDb.FirstOrDefault(x => x.ID == attrib.Upgrade) != null) + throw new ShiftoriumConflictException(attrib.Upgrade); + upgDb.Add(new ShiftoriumUpgrade + { + Id = attrib.Upgrade, + Name = attrib.Name, + Cost = attrib.Cost, + Description = attrib.Description, + Dependencies = attrib.Dependencies, + Category = attrib.Category + }); + + } + } + + } + } + catch { } + } + } + + + + foreach (var item in upgDb) + { + if (upgDb.Where(x => x.ID == item.ID).Count() > 1) + throw new ShiftoriumConflictException(item.Id); + } + } + + + /// <summary> + /// Gets or sets whether the Shiftorium has been initiated. + /// </summary> public static bool IsInitiated { get; private set; } + + /// <summary> + /// Initiates the Shiftorium. + /// </summary> public static void Init() { if (IsInitiated == false) { IsInitiated = true; //Let the crash handler deal with this one... - var dict = GetDefaults(); + CreateUpgradeDatabase(); + var dict = upgDb; foreach (var itm in dict) { if (!SaveSystem.CurrentSave.Upgrades.ContainsKey(itm.ID)) @@ -150,9 +360,14 @@ namespace ShiftOS.Engine } - public static int GetCPValue(string id) + /// <summary> + /// Get the codepoint value for an upgrade. + /// </summary> + /// <param name="id">The upgrade ID to search</param> + /// <returns>The codepoint value.</returns> + public static long GetCPValue(string id) { - foreach(var upg in GetDefaults()) + foreach (var upg in GetDefaults()) { if (upg.ID == id) return upg.Cost; @@ -160,10 +375,14 @@ namespace ShiftOS.Engine return 0; } + /// <summary> + /// Gets all available upgrades. + /// </summary> + /// <returns></returns> public static ShiftoriumUpgrade[] GetAvailable() { List<ShiftoriumUpgrade> available = new List<ShiftoriumUpgrade>(); - foreach(var defaultupg in GetDefaults()) + foreach (var defaultupg in GetDefaults()) { if (!UpgradeInstalled(defaultupg.ID) && DependenciesInstalled(defaultupg)) available.Add(defaultupg); @@ -171,6 +390,11 @@ namespace ShiftOS.Engine return available.ToArray(); } + /// <summary> + /// Determines whether all dependencies of a given upgrade have been installed. + /// </summary> + /// <param name="upg">The upgrade to scan</param> + /// <returns>Boolean representing the result of this function.</returns> public static bool DependenciesInstalled(ShiftoriumUpgrade upg) { if (string.IsNullOrEmpty(upg.Dependencies)) @@ -180,23 +404,33 @@ namespace ShiftOS.Engine else if (upg.Dependencies.Contains(";")) { string[] dependencies = upg.Dependencies.Split(';'); - foreach(var dependency in dependencies) + foreach (var dependency in dependencies) { if (!UpgradeInstalled(dependency)) return false; } return true; - } + } else { return UpgradeInstalled(upg.Dependencies); } } + /// <summary> + /// Fired when an upgrade is installed. + /// </summary> public static event EmptyEventHandler Installed; + /// <summary> + /// Determines if an upgrade is installed. + /// </summary> + /// <param name="id">The upgrade ID to scan.</param> + /// <returns>Whether the upgrade is installed.</returns> public static bool UpgradeInstalled(string id) { + if (string.IsNullOrWhiteSpace(id)) + return true; if (SaveSystem.CurrentSave != null) { if (!IsInitiated) @@ -204,13 +438,35 @@ namespace ShiftOS.Engine } try { - return SaveSystem.CurrentSave.Upgrades[id]; + if (SaveSystem.CurrentSave == null) + return false; + + if (SaveSystem.CurrentSave.StoriesExperienced == null) + SaveSystem.CurrentSave.StoriesExperienced = new List<string>(); + + if (id.Contains(';')) + { + foreach (var u in id.Split(';')) + { + if (UpgradeInstalled(u) == false) + return false; + } + return true; + } + + bool upgInstalled = false; + if (SaveSystem.CurrentSave.Upgrades.ContainsKey(id)) + upgInstalled = SaveSystem.CurrentSave.Upgrades[id]; + + if (upgInstalled == false) + return SaveSystem.CurrentSave.StoriesExperienced.Contains(id); + return true; } catch { - return false; Console.WriteLine("Upgrade " + id + "DNE."); Console.WriteLine(); + return false; } } @@ -220,30 +476,28 @@ namespace ShiftOS.Engine private static IShiftoriumProvider _provider = null; + [Obsolete("Please annotate your provider with a [ShiftoriumProvider] attribute instead. This function doesn't do anything.")] public static void RegisterProvider(IShiftoriumProvider p) { _provider = p; } - //Bless the newer NEWER engine. + /// <summary> + /// Gets every upgrade inside the frontend and all mods. + /// </summary> + /// <returns>Every single found Shiftorium upgrade.</returns> public static List<ShiftoriumUpgrade> GetDefaults() { - try - { - return _provider.GetDefaults(); - } - catch (Exception ex) - { - Console.WriteLine("Couldn't get the upgrade definition list from the provider."); - Console.WriteLine("This might be able to help:"); - Console.WriteLine(ex); - return JsonConvert.DeserializeObject<List<ShiftoriumUpgrade>>(Properties.Resources.Shiftorium); - } + return upgDb; } } public interface IShiftoriumProvider { + /// <summary> + /// Retrieves all frontend upgrades. + /// </summary> + /// <returns></returns> List<ShiftoriumUpgrade> GetDefaults(); } @@ -260,14 +514,59 @@ namespace ShiftOS.Engine public string ID { get; private set; } } + + public class ShiftoriumUpgrade { public string Name { get; set; } public string Description { get; set; } - public int Cost { get; set; } + public long Cost { get; set; } public string ID { get { return (this.Id != null ? this.Id : (Name.ToLower().Replace(" ", "_"))); } } - public string Id { get; } - + public string Id { get; set; } + public string Category { get; set; } + public bool Installed + { + get + { + return Shiftorium.UpgradeInstalled(ID); + } + } public string Dependencies { get; set; } } + + public class ShiftoriumUpgradeAttribute : RequiresUpgradeAttribute + { + public ShiftoriumUpgradeAttribute(string name, long cost, string desc, string dependencies, string category) : base(name.ToLower().Replace(" ", "_")) + { + Name = name; + Description = desc; + Dependencies = dependencies; + Cost = cost; + Category = category; + } + + public string Name { get; private set; } + public string Description { get; private set; } + public long Cost { get; private set; } + public string Dependencies { get; private set; } + public string Category { get; private set; } + } + + public class ShiftoriumConflictException : Exception + { + public ShiftoriumConflictException() : base("An upgrade conflict has occurred while loading Shiftorium Upgrades from an assembly. Is there a duplicate upgrade ID?") + { + + } + + public ShiftoriumConflictException(string id) : base("An upgrade conflict has occurred while loading Shiftorium Upgrades from an assembly. An upgrade with the ID \"" + id + "\" has already been loaded.") + { + + } + } + + public class ShiftoriumProviderAttribute : Attribute + { + + } } diff --git a/ShiftOS_TheReturn/Skinning.cs b/ShiftOS_TheReturn/Skinning.cs index 4837dcd..f5dd211 100644 --- a/ShiftOS_TheReturn/Skinning.cs +++ b/ShiftOS_TheReturn/Skinning.cs @@ -35,53 +35,107 @@ using static ShiftOS.Engine.SaveSystem; using ShiftOS.Objects.ShiftFS; using System.Reflection; using ShiftOS.Engine.Scripting; -namespace ShiftOS.Engine { - +namespace ShiftOS.Engine +{ + /// <summary> + /// Skinning API for Lua. + /// </summary> [Exposed("skinning")] public class SkinFunctions { + /// <summary> + /// Reload the current skin. + /// </summary> public void loadSkin() { SkinEngine.LoadSkin(); - } + } + /// <summary> + /// Get the current skin info. + /// </summary> + /// <returns>A proxy object containing all skin variables.</returns> public dynamic getSkin() { return SkinEngine.LoadedSkin; } + /// <summary> + /// Set the current skin to the specified <see cref="Skin"/> class. + /// </summary> + /// <param name="skn">The <see cref="Skin"/> class to load.</param> public void setSkin(Skin skn) { Utils.WriteAllText(Paths.GetPath("skin.json"), JsonConvert.SerializeObject(skn)); SkinEngine.LoadSkin(); } + /// <summary> + /// Retrieves an image from the skin file. + /// </summary> + /// <param name="id">The skin image ID</param> + /// <returns>The loaded image, null (nil in Lua) if none is found.</returns> public dynamic getImage(string id) { return SkinEngine.GetImage(id); } } + /// <summary> + /// Skin engine management class. + /// </summary> + public static class SkinEngine + { + private static ISkinPostProcessor processor = null; - public static class SkinEngine { - public static ImageLayout GetImageLayout(string img) { - if (LoadedSkin.SkinImageLayouts.ContainsKey(img)) { + /// <summary> + /// Load a new skin postprocessor into the engine. + /// </summary> + /// <param name="_processor">The postprocessor to load.</param> + public static void SetPostProcessor(ISkinPostProcessor _processor) + { + processor = _processor; + } + + /// <summary> + /// Retrieve the user-specified image layout of a skin image. + /// </summary> + /// <param name="img">The skin image ID.</param> + /// <returns>The <see cref="ImageLayout"/> for the image.</returns> + public static ImageLayout GetImageLayout(string img) + { + if (LoadedSkin.SkinImageLayouts.ContainsKey(img)) + { return LoadedSkin.SkinImageLayouts[img]; - } else { + } + else + { LoadedSkin.SkinImageLayouts.Add(img, ImageLayout.Tile); return ImageLayout.Tile; } } - public static System.Drawing.Image GetImage(string img) { + /// <summary> + /// Retrieves an image from the skin after postprocessing it. + /// </summary> + /// <param name="img">The image ID to search.</param> + /// <returns>The post-processed image, or null if none was found.</returns> + public static System.Drawing.Image GetImage(string img) + { var type = typeof(Skin); - foreach (var field in type.GetFields()) { - foreach (var attr in field.GetCustomAttributes(false)) { - if (attr is ImageAttribute) { + foreach (var field in type.GetFields()) + { + foreach (var attr in field.GetCustomAttributes(false)) + { + if (attr is ImageAttribute) + { var iattr = attr as ImageAttribute; - if (iattr.Name == img) { + if (iattr.Name == img) + { byte[] image = (byte[])field.GetValue(LoadedSkin); + if (processor != null) + image = processor.ProcessImage(image); return ImageFromBinary(image); } } @@ -91,12 +145,22 @@ namespace ShiftOS.Engine { return null; } + /// <summary> + /// Set the engine's current icon prober. + /// </summary> + /// <param name="prober">The icon prober to use.</param> public static void SetIconProber(IIconProber prober) { _iconProber = prober; } - public static Image ImageFromBinary(byte[] image) { + /// <summary> + /// Load a <see cref="Image"/> from a <see cref="byte"/> array. + /// </summary> + /// <param name="image">The array to convert</param> + /// <returns>The resulting image.</returns> + public static Image ImageFromBinary(byte[] image) + { if (image == null) return null; Image img = (Bitmap)((new ImageConverter()).ConvertFrom(image)); @@ -105,6 +169,9 @@ namespace ShiftOS.Engine { private static Skin loadedSkin = new Skin(); + /// <summary> + /// Gets the currently loaded skin. + /// </summary> public static Skin LoadedSkin { get @@ -117,37 +184,62 @@ namespace ShiftOS.Engine { } } - public static void Init() { - Application.ApplicationExit += (o, a) => { + /// <summary> + /// Initiates the skin engine. + /// </summary> + public static void Init() + { + Application.ApplicationExit += (o, a) => + { SaveSkin(); }; - if (!Utils.FileExists(Paths.GetPath("skin.json"))) { + if (!Utils.FileExists(Paths.GetPath("skin.json"))) + { LoadedSkin = new ShiftOS.Engine.Skin(); SaveSkin(); - } else { + } + else + { LoadSkin(); } - if (SaveSystem.CurrentSave != null) { + if (SaveSystem.CurrentSave != null) + { SkinLoaded?.Invoke(); } } + /// <summary> + /// Occurs when the skin is loaded. + /// </summary> public static event EmptyEventHandler SkinLoaded; - public static void LoadSkin() { + /// <summary> + /// Reload the current skin. + /// </summary> + public static void LoadSkin() + { LoadedSkin = JsonConvert.DeserializeObject<Skin>(Utils.ReadAllText(Paths.GetPath("skin.json"))); SkinLoaded?.Invoke(); Desktop.ResetPanelButtons(); Desktop.PopulateAppLauncher(); } - public static void SaveSkin() { + /// <summary> + /// Save the skin loaded in memory to the filesystem. + /// </summary> + public static void SaveSkin() + { Utils.WriteAllText(Paths.GetPath("skin.json"), JsonConvert.SerializeObject(LoadedSkin, Formatting.Indented)); } private static IIconProber _iconProber = null; + /// <summary> + /// Retrieves the default icon for a given icon ID. + /// </summary> + /// <param name="id">The icon ID to search.</param> + /// <returns>The resulting icon image.</returns> public static Image GetDefaultIcon(string id) { if (_iconProber == null) @@ -156,20 +248,20 @@ namespace ShiftOS.Engine { } else { - foreach(var f in System.IO.Directory.GetFiles(Environment.CurrentDirectory)) + foreach (var f in System.IO.Directory.GetFiles(Environment.CurrentDirectory)) { - if(f.EndsWith(".exe") || f.EndsWith(".dll")) + if (f.EndsWith(".exe") || f.EndsWith(".dll")) { try { var asm = Assembly.LoadFile(f); - foreach(var type in asm.GetTypes()) + foreach (var type in asm.GetTypes()) { - if(type.Name == id) + if (type.Name == id) { - foreach(var attr in type.GetCustomAttributes(true)) + foreach (var attr in type.GetCustomAttributes(true)) { - if(attr is DefaultIconAttribute) + if (attr is DefaultIconAttribute) { return _iconProber.GetIcon(attr as DefaultIconAttribute); } @@ -184,6 +276,11 @@ namespace ShiftOS.Engine { } } + /// <summary> + /// Retrieves the user-defined icon for a specified icon ID. + /// </summary> + /// <param name="id">The icon ID to search.</param> + /// <returns>The resulting icon image.</returns> public static Image GetIcon(string id) { if (!LoadedSkin.AppIcons.ContainsKey(id)) @@ -198,15 +295,27 @@ namespace ShiftOS.Engine { return Image.FromStream(sr); } } - + } } + /// <summary> + /// Interface for probing app icons. + /// </summary> public interface IIconProber { + /// <summary> + /// Retrieve the icon image from a <see cref="DefaultIconAttribute"/>. + /// </summary> + /// <param name="attr">The attribute data</param> + /// <returns>The resulting image.</returns> Image GetIcon(DefaultIconAttribute attr); } + /// <summary> + /// Sets the default icon ID for a <see cref="IShiftOSWindow"/>. + /// </summary> + [AttributeUsage(AttributeTargets.Class, AllowMultiple =false)] public class DefaultIconAttribute : Attribute { public DefaultIconAttribute(string id) @@ -217,7 +326,11 @@ namespace ShiftOS.Engine { public string ID { get; private set; } } - public class Skin { + /// <summary> + /// The data stored in any .skn file. + /// </summary> + public class Skin + { //borrowing from the discourse theme for the default skin private static readonly Color DefaultBackground = Color.FromArgb(0, 0x44, 0x00); private static readonly Color DefaultForeground = Color.FromArgb(0xDD, 0xDD, 0xDD); @@ -246,6 +359,166 @@ namespace ShiftOS.Engine { [ShifterHidden] public Dictionary<string, byte[]> AppIcons = new Dictionary<string, byte[]>(); + [ShifterMeta("System")] + [ShifterCategory("Progress Bar")] + [RequiresUpgrade("shift_progress_bar;skinning")] + [Image("progressbarbg")] + [ShifterName("Progress Bar Background Image")] + [ShifterDescription("Set an image for the background of a progress bar.")] + public byte[] ProgressBarBG = null; + + + [ShifterMeta("System")] + [ShifterCategory("Progress Bar")] + [RequiresUpgrade("shift_progress_bar;skinning")] + [Image("progress")] + [ShifterName("Progress Image")] + [ShifterDescription("Set the image for the progress inside a progress bar.")] + public byte[] Progress = null; + + + [ShifterMeta("System")] + [ShifterCategory("Progress Bar")] + [RequiresUpgrade("shift_progress_bar")] + [ShifterName("Progress bar foreground color")] + [ShifterDescription("Set the color of the progress indicator.")] + public Color ProgressColor = Accent1; + + + [ShifterMeta("System")] + [ShifterCategory("Progress Bar")] + [RequiresUpgrade("shift_progress_bar")] + [ShifterName("Progress bar background color")] + [ShifterDescription("The background color of the progress bar.")] + public Color ProgressBarBackgroundColor = Color.Black; + + + [ShifterMeta("System")] + [ShifterCategory("Progress Bar")] + [RequiresUpgrade("shift_progress_bar")] + [ShifterName("Progress bar block size")] + [ShifterDescription("If the progress bar style is set to Blocks, this determines how wide each block should be.")] + public int ProgressBarBlockSize = 15; + + + [ShifterMeta("System")] + [ShifterCategory("Progress Bar")] + [RequiresUpgrade("shift_progress_bar")] + [ShifterDescription("Set the style of a progress bar.\r\nMarquee: The progress bar will render a box that moves from the left to the right in a loop.\r\nContinuous: Progress is shown by a single, continuous box.\r\nBlocks: Just like Continuous, but the box is split into even smaller boxes of a set width.")] + [ShifterName("Progress bar style")] + public ProgressBarStyle ProgressBarStyle = ProgressBarStyle.Continuous; + + + + + + + [ShifterMeta("System")] + [ShifterCategory("Buttons")] + [RequiresUpgrade("shift_buttons")] + [ShifterName("Button background color")] + [ShifterDescription("Set the background color for each button's Idle state.")] + public Color ButtonBackgroundColor = Skin.DefaultBackground; + + [ShifterMeta("System")] + [ShifterCategory("Buttons")] + [RequiresUpgrade("shift_buttons;skinning")] + [Image("buttonhover")] + [ShifterName("Button hover image")] + [ShifterDescription("Set the image that's displayed when the mouse hovers over a button.")] + public byte[] ButtonHoverImage = null; + + [ShifterMeta("System")] + [ShifterCategory("Buttons")] + [RequiresUpgrade("skinning;shift_buttons")] + [Image("buttonpressed")] + [ShifterName("Button pressed image")] + [ShifterDescription("Select an image to show when the user presses a button.")] + public byte[] ButtonPressedImage = null; + + [ShifterMeta("System")] + [ShifterCategory("Buttons")] + [RequiresUpgrade("shift_buttons")] + [ShifterName("Button hover color")] + [ShifterDescription("Choose the color that displays on a button when the mouse hovers over it.")] + public Color ButtonHoverColor = Skin.Accent1; + + [ShifterMeta("System")] + [ShifterCategory("Buttons")] + [RequiresUpgrade("shift_buttons")] + [ShifterName("Button pressed color")] + [ShifterDescription("Select the background color for the button when the mouse clicks it.")] + public Color ButtonPressedColor = Skin.Accent2; + + [ShifterMeta("System")] + [ShifterCategory("Buttons")] + [RequiresUpgrade("shift_buttons")] + [ShifterName("Button foreground color")] + [ShifterDescription("Select the text and border color for each button.")] + public Color ButtonForegroundColor = Skin.DefaultForeground; + + [ShifterMeta("System")] + [ShifterCategory("Buttons")] + [RequiresUpgrade("shift_buttons")] + [ShifterName("Button border width")] + [ShifterDescription("Set the width, in pixels, of the button's border.")] + public int ButtonBorderWidth = 2; + + [ShifterMeta("System")] + [ShifterCategory("Buttons")] + [RequiresUpgrade("shift_buttons")] + [ShifterName("Button font")] + [ShifterDescription("Select the font for the button's text.")] + public Font ButtonTextFont = Skin.SysFont; + + [ShifterMeta("System")] + [ShifterCategory("Buttons")] + [RequiresUpgrade("shift_buttons;skinning")] + [Image("buttonidle")] + [ShifterName("Button background color")] + [ShifterDescription("Select an image to show as the button's Idle state.")] + public byte[] ButtonBG = null; + + + [Image("panelclockbg")] + [ShifterMeta("Desktop")] + [ShifterCategory("Panel Clock")] + [ShifterName("Panel Clock Background Image")] + [ShifterDescription("Set the background image of the panel clock.")] + [RequiresUpgrade("skinning;shift_panel_clock")] + public byte[] PanelClockBG = null; + + [ShifterMeta("System")] + [ShifterCategory("Login Screen")] + [RequiresUpgrade("gui_based_login_screen")] + [ShifterName("Login Screen Background Color")] + [ShifterDescription("Change the background color of the login screen.")] + public Color LoginScreenColor = Skin.DesktopBG; + + [ShifterMeta("System")] + [ShifterCategory("Login Screen")] + [RequiresUpgrade("skinning;gui_based_login_screen")] + [ShifterName("Login Screen Background Image")] + [ShifterDescription("Set an image as your login screen!")] + [Image("login")] + public byte[] LoginScreenBG = null; + + + [RequiresUpgrade("shift_screensaver")] + [ShifterMeta("System")] + [ShifterCategory("Screen saver")] + [ShifterName("Screen saver wait (milliseconds)")] + [ShifterDescription("How long do you have to stay idle before the screensaver activates?")] + public int ScreensaverWait = 300000; + + [RequiresUpgrade("skinning;shift_screensaver")] + [ShifterMeta("System")] + [ShifterCategory("Screen saver")] + [ShifterName("Screen saver image")] + [ShifterDescription("What image should appear on the screen saver?")] + public byte[] ScreensaverImage = null; + + [ShifterMeta("Windows")] [ShifterCategory("Titlebar")] @@ -259,8 +532,8 @@ namespace ShiftOS.Engine { [ShifterName("System Font")] [ShifterDescription("The font style used by the system.")] public Font MainFont = SysFont; - - [ShifterEnumMask(new[] { "Right", "Left"})] + + [ShifterEnumMask(new[] { "Right", "Left" })] [ShifterMeta("Windows")] [ShifterCategory("Title Buttons")] [ShifterName("Title button position")] @@ -850,12 +1123,12 @@ namespace ShiftOS.Engine { [ShifterMeta("System")] [ShifterCategory("General")] [ShifterName("Terminal text color")] - public Color TerminalForeColor = DefaultForeground; + public ConsoleColor TerminalForeColorCC = ConsoleColor.White; [ShifterMeta("System")] [ShifterCategory("General")] [ShifterName("Terminal background color")] - public Color TerminalBackColor = DesktopBG; + public ConsoleColor TerminalBackColorCC = ConsoleColor.Black; [ShifterMeta("Desktop")] [ShifterCategory("Desktop Panel")] @@ -1049,25 +1322,165 @@ namespace ShiftOS.Engine { [ShifterName("App icon from side")] [ShifterDescription("How far from the side should the icon be?")] [RequiresUpgrade("shift_titlebar;app_icons")] - public Point TitlebarIconFromSide = new Point(4,4); + public Point TitlebarIconFromSide = new Point(4, 4); + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("Status Panel Font")] + [ShifterDescription("The font used by the status panel in the Advanced App Launcher.")] + public Font ALStatusPanelFont = SysFont; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("Status Panel Text Color")] + [ShifterDescription("The text color for the AL status panel.")] + public Color ALStatusPanelTextColor = Skin.DefaultForeground; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("Status Panel Background")] + [ShifterDescription("The status panel's background color.")] + public Color ALStatusPanelBackColor = TitleBG; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("Status Panel Text Alignment")] + [ShifterDescription("What part of the panel should the status text stick to?")] + public ContentAlignment ALStatusPanelAlignment = ContentAlignment.MiddleCenter; + + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("AL System Status Height")] + [ShifterDescription("Set the height of the top system status bar in the App Launcher.")] + public int ALSystemStatusHeight = 50; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("AL Size")] + [ShifterDescription("Set the size of the App Launcher's container")] + public Size AALSize = new Size(425, 500); + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("AL Category View Width")] + [ShifterDescription("Set the width of the left Category Listing on the app launcher.")] + public int AALCategoryViewWidth = 237; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("AL Item List Width")] + [ShifterDescription("Set the width of the item list in the app launcher.")] + public int AALItemViewWidth = 237; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("AL System Actions Height")] + [ShifterDescription("Set the height of the bottom system actions bar in the App Launcher.")] + public int ALSystemActionHeight = 30; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("skinning;shift_advanced_app_launcher")] + [ShifterName("Status Panel Background Image")] + [ShifterDescription("Use an image for the App Launcher Status Panel")] + [Image("al_bg_status")] + public byte[] ALStatusPanelBG = null; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterEnumMask(new[] { "Button, bottom panel", "Button, top panel", "Category Item" })] + [ShifterName("Shutdown Button position")] + [ShifterDescription("Change the position and layout of the App Launcher Shutdown button.")] + public int ShutdownButtonStyle = 0; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("Shutdown Button from side")] + [ShifterDescription("The location relative to the side of the system panel that the shutdown button should reside from.")] + public Point ShutdownButtonFromSide = new Point(2, 2); + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("Align shutdown button to left?")] + [ShifterDescription("Should the location of the shutdown button be calculated relative to the left of the system panel?")] + public bool ShutdownOnLeft = false; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("Shutdown Button Font")] + [ShifterDescription("The font of the Shutdown Button")] + public Font ShutdownFont = SysFont; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("Shutdown Text Color")] + [ShifterDescription("The foreground color of the Shutdown button")] + public Color ShutdownForeColor = DefaultForeground; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("System Panel background color")] + [ShifterDescription("The background color of the App Launcher System Panel.")] + public Color SystemPanelBackground = TitleBG; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("skinning;shift_advanced_app_launcher")] + [ShifterName("System Panel Background Image")] + [ShifterDescription("The background image of the System Panel.")] + [Image("al_bg_system")] + public byte[] SystemPanelBG = null; + + [ShifterMeta("Desktop")] + [ShifterCategory("App Launcher")] + [RequiresUpgrade("shift_advanced_app_launcher")] + [ShifterName("App Launcher Item Font")] + [ShifterDescription("Select the font to use for the items in the App Launcher.")] + public Font AdvALItemFont = SysFont2; } - public class ShifterHiddenAttribute : Attribute { + /// <summary> + /// Marks a skin spec field as hidden from the Shifter. + /// </summary> + public class ShifterHiddenAttribute : Attribute + { } - public class ShifterFlagAttribute : Attribute { - public ShifterFlagAttribute(string flag, bool expected) { + public class ShifterFlagAttribute : Attribute + { + public ShifterFlagAttribute(string flag, bool expected) + { Expected = expected; Flag = flag; } public bool Expected { get; set; } public string Flag { get; set; } - public bool IsTrue(Skin skn) { - foreach (var f in skn.GetType().GetFields()) { - if (f.Name == Flag) { - if (f.FieldType == typeof(bool)) { + public bool IsTrue(Skin skn) + { + foreach (var f in skn.GetType().GetFields()) + { + if (f.Name == Flag) + { + if (f.FieldType == typeof(bool)) + { return (bool)f.GetValue(skn) == Expected; } } @@ -1076,12 +1489,14 @@ namespace ShiftOS.Engine { } } - public class ImageAttribute : Attribute { + public class ImageAttribute : Attribute + { /// <summary> /// Attribute a byte array within the Skin object with this attribute and the engine and Shifter will see it as an image and you'll be able to grab the image by calling SkinEngine.GetImage() passing the name you input here. /// </summary> /// <param name="name">The name you want to reference this image as in the code.</param> - public ImageAttribute(string name) { + public ImageAttribute(string name) + { Name = name; } @@ -1089,8 +1504,10 @@ namespace ShiftOS.Engine { } - public class ShifterEnumMaskAttribute : Attribute { - public ShifterEnumMaskAttribute(string[] items) { + public class ShifterEnumMaskAttribute : Attribute + { + public ShifterEnumMaskAttribute(string[] items) + { Items = items; } @@ -1099,37 +1516,55 @@ namespace ShiftOS.Engine { - public class ShifterNameAttribute : Attribute { - public ShifterNameAttribute(string name) { + public class ShifterNameAttribute : Attribute + { + public ShifterNameAttribute(string name) + { Name = name; } public string Name { get; set; } } - public class ShifterDescriptionAttribute : Attribute { - public ShifterDescriptionAttribute(string description) { + public class ShifterDescriptionAttribute : Attribute + { + public ShifterDescriptionAttribute(string description) + { Description = description; } public string Description { get; set; } } - public class ShifterCategoryAttribute : Attribute { + public class ShifterCategoryAttribute : Attribute + { - public ShifterCategoryAttribute(string category) { + public ShifterCategoryAttribute(string category) + { Category = category; } public string Category { get; set; } } - public class ShifterMetaAttribute : Attribute { + public interface ISkinPostProcessor + { + /// <summary> + /// Perform algorithmic operations at the bit level on a ShiftOS skin image. + /// </summary> + /// <param name="original">The image, as loaded by the engine, as a 32-bit ARGB byte array.</param> + /// <returns>The processed image.</returns> + byte[] ProcessImage(byte[] original); + } + + public class ShifterMetaAttribute : Attribute + { - public ShifterMetaAttribute(string meta) { + public ShifterMetaAttribute(string meta) + { Meta = meta; } public string Meta { get; set; } } -} +}
\ No newline at end of file diff --git a/ShiftOS_TheReturn/Story.cs b/ShiftOS_TheReturn/Story.cs index 9d8078e..d62dae4 100644 --- a/ShiftOS_TheReturn/Story.cs +++ b/ShiftOS_TheReturn/Story.cs @@ -35,231 +35,88 @@ using Newtonsoft.Json; namespace ShiftOS.Engine { - public class Story + /// <summary> + /// Storyline management class. + /// </summary> + public static class Story { - public static void RunFromInternalResource(string resource_id) - { - var t = typeof(Properties.Resources); - - foreach(var prop in t.GetProperties(System.Reflection.BindingFlags.NonPublic | BindingFlags.Static)) - { - if(prop.Name == resource_id) - { - if(prop.PropertyType == typeof(string)) - { - WriteStory(prop.GetValue(null) as string); - - return; - } - } - } - throw new ArgumentException("Couldn't find resource ID inside the engine: " + resource_id); - } - - - public string Character { get; set; } - public List<string> Lines { get; set; } - - public static void Start() + /// <summary> + /// Starts the storyline with the specified Storyline ID. + /// </summary> + /// <param name="stid">The storyline ID to start.</param> + public static void Start(string stid) { - Console.WriteLine(); - if(SaveSystem.CurrentSave.StoryPosition == 5) + foreach (var exec in System.IO.Directory.GetFiles(Environment.CurrentDirectory)) { - StartDevXLies(); - } - } - - public static void StartDevXLies() - { - int chatProgress = 0; - //bool LoopStuck = false; - string textToWrite = ""; - const int TYPE_SPEED_MS = 45; - bool done = false; - bool write = true; - - while (done == false) { - write = true; - switch (chatProgress) - { - case 0: - textToWrite = "User joined: @" + SaveSystem.CurrentSave.Username; - break; - case 1: - textToWrite = $"Hello, {SaveSystem.CurrentSave.Username}."; - break; - case 2: //If C:\ShiftOS doesn't exist the player won't notice this is here. - if (Directory.Exists(Paths.GetPath("classic"))) - { - textToWrite = "I see you've participated in my previous ShiftOS experiment. Welcome back, Shifter. I assume you know lots about ShiftOS, but there are some updates I have to tell you."; - } - else - { - write = false; - } - break; - case 3: //DevX hates ShiftOS-Next secretly. - if (Directory.Exists(Paths.GetPath("classic") + "-Next")) - { - textToWrite = "Hmmmm.... looking at my sentience records, I see you've participated in ShiftOS-Next. This is gonna be difficult."; - } - else - { - write = false; - } - break; - case 4: - textToWrite = "There's a lot that has changed within ShiftOS."; - break; - case 5: - textToWrite = "First off, I want to tell you a bit about myself in case you don't already know."; - break; - case 6: - textToWrite = "My name is DevX. I am the architect of ShiftOS. I have chosen you to take part in helping me out with it."; - break; - case 7: - textToWrite = "You see, in my past attempts it has all been about an evolving operating system and seeing how the users work with it..."; - break; - case 8: - textToWrite = "Almost one hundred percent of the time, people have found out it was an experiment and they could simply return to their regular system with a specific upgrade."; - break; - case 9: - textToWrite = "But now, I want to try something different - something unique."; - break; - case 10: - textToWrite = "ShiftOS is the same as it has been in my previous attempts, but now, your goal is to gain as much wealth and power as possible."; - break; - case 11: - textToWrite = "Right now you are inside my segregation LAN. Only you and me exist within this domain. You are free from other users unless I create them."; - break; - case 12: - textToWrite = "Since you have proved your sentience, I have a task for you outside the segregation LAN."; - break; - case 13: - textToWrite = "But first... you need to be taught a few things."; - break; - case 14: - textToWrite = "First off, when I bring you into my multi-user domain, you'll first want to establish as much wealth as possible."; - break; - case 15: - textToWrite = "Wealth comes in the form of Codepoints - a currency used among users of the multi-user domain."; - break; - case 16: - textToWrite = @"You can get Codepoints by doing the following: - - - Stealing them from other users - - Extracting them from inactive/unverified sentiences - - Using specific scripts/programs within ShiftOS - - Creating paid scripts/applications within ShiftOS"; - break; - case 17: - textToWrite = "You can use Codepoints to buy upgrades using the 'shiftorium.buy' command, or you can use them to pay other users, or scripts."; - break; - case 18: - textToWrite = "Within the multi-user domain you are free to do whatever you want. Larcany, theft, deceiving, lies, and distribution of malware is permitted under my watch."; - break; - case 19: - textToWrite = "Do whatever you have to to get Codepoints."; - break; - case 20: - textToWrite = "Then use them to make yourself stronger by buying upgrades at the shiftorium."; - break; - case 21: - textToWrite = "If you want to get a bit devious within the multi-user domain, look around for scripts that will expose user account information."; - break; - case 22: - textToWrite = "Or just spread a virus around the mud."; - break; - case 23: - textToWrite = "Or you can be the 'good' guy and stop these attacks and gain the trust of other users."; - break; - case 24: - textToWrite = "It's up to you. Just, don't mess with my system. You won't want me coming to you after that. I'm watching."; - break; - case 25: - textToWrite = "User left chat: @" + SaveSystem.CurrentSave.Username; - done = true; - SaveSystem.CurrentSave.StoryPosition++; - TerminalBackend.InvokeCommand("sos.save"); - break; - - } - - if (write == true) + if(exec.EndsWith(".exe") || exec.EndsWith(".dll")) { - Console.WriteLine(); - Console.Write("DevX: "); - foreach(char c in textToWrite) + try { - Console.Beep(750, TYPE_SPEED_MS); - if (c == '\n') - { - - } - else if (c == '\r') + if (SaveSystem.CurrentSave.StoriesExperienced == null) + SaveSystem.CurrentSave.StoriesExperienced = new List<string>(); + var asm = Assembly.LoadFile(exec); + foreach(var type in asm.GetTypes()) { - Console.WriteLine(); - } - else - { - Console.Write(c); + foreach(var mth in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + foreach(var attrib in mth.GetCustomAttributes(false)) + { + if(attrib is StoryAttribute) + { + var story = attrib as StoryAttribute; + if(story.StoryID == stid) + { + new Thread(() => + { + mth.Invoke(null, null); + SaveSystem.CurrentSave.StoriesExperienced.Add(stid); + }).Start(); + return; + } + } + } + } } } - Thread.Sleep(1000); + catch (Exception ex) { throw ex; } } - chatProgress += 1; } +#if DEBUG + throw new ArgumentException("Story ID not found: " + stid + " - Talk to Michael. NOW."); +#else + Debug.Print("No such story: " + stid); +#endif } - - public static void WriteStory(string json) + [Obsolete("Please use Story.Start() in tandem with [StoryAttribute].")] + public static void RunFromInternalResource(string resource_id) { - var thread = new Thread(new ThreadStart(() => - { - var story = JsonConvert.DeserializeObject<Story>(json); - - const int TYPESPEED = 45; - - foreach (var line in story.Lines) - { - var localized = Localization.Parse(line); + } - if (line.StartsWith("cmd:")) - { - string[] cmdsplit = line.Replace("cmd:", "").Split(' '); - switch (cmdsplit[0]) - { - case "givecp": - SaveSystem.TransferCodepointsFrom(story.Character, Convert.ToInt32(cmdsplit[1])); - break; - } - } - else - { - Console.Write(story.Character + ": "); + } - foreach (var c in localized) - { - Console.Beep(1024, TYPESPEED); - if (c == '\r') - { + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class StoryAttribute : Attribute + { + /// <summary> + /// Creates a new instance of the <see cref="StoryAttribute"/> attribute. + /// </summary> + /// <param name="id">The ID of this story plot.</param> + /// <remarks> + /// <para> + /// The <see cref="StoryAttribute"/> is used to turn a static, public method into a story element. Using the specified <paramref name="id"/> argument, the ShiftOS Engine can determine whether this plot has already been experienced, and using the <see cref="Shiftorium"/> classes, the ID is treated as a special Shiftorium upgrade, and you can use the <see cref="RequiresUpgradeAttribute"/> attribute as well as the various other ways of determining whether a Shiftorium upgrade is installed to determine if this plot has been experienced. + /// </para> + /// </remarks> + public StoryAttribute(string id) + { + StoryID = id; + } - } - else if (c == '\n') - Console.WriteLine(); - else - Console.Write(c); - } + /// <summary> + /// Gets the storyline ID stored in this attribute. + /// </summary> + public string StoryID { get; private set; } - Console.WriteLine(); - Thread.Sleep(1000); - } - } - Console.Write($"{SaveSystem.CurrentSave.Username}@{SaveSystem.CurrentSave.SystemName}:~$ "); - })); - thread.IsBackground = true; - thread.Start(); - } } } diff --git a/ShiftOS_TheReturn/TerminalBackend.cs b/ShiftOS_TheReturn/TerminalBackend.cs index 3c8e62a..6104927 100644 --- a/ShiftOS_TheReturn/TerminalBackend.cs +++ b/ShiftOS_TheReturn/TerminalBackend.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; @@ -34,13 +35,27 @@ using static ShiftOS.Engine.SaveSystem; namespace ShiftOS.Engine { + /// <summary> + /// Backend for the ShiftOS terminal. + /// </summary> public static class TerminalBackend { + /// <summary> + /// Occurs when a command is processed. + /// </summary> public static event Action<string, string> CommandProcessed; + /// <summary> + /// Gets or sets whether the current command is elevated. + /// </summary> public static bool Elevated { get; set; } - public static Dictionary<string, object> GetArgs(ref string text) + /// <summary> + /// Parses command-line arguments using the ShiftOS syntax and stores them in a <see cref="Dictionary{string, string}"/>, removing the parsed text from the original string. + /// </summary> + /// <param name="text">The text to parse.</param> + /// <returns><see cref="Dictionary{string, string}"/> containing the parsed arguments.</returns> + public static Dictionary<string, string> GetArgs(ref string text) { bool shouldParse = false; int argStart = 0; @@ -61,32 +76,320 @@ namespace ShiftOS.Engine string args = text.Substring(argStart, text.Length - argStart); text = text.Remove(argStart, text.Length - argStart).Replace(" ", ""); - return JsonConvert.DeserializeObject<Dictionary<string, object>>(args); + return JsonConvert.DeserializeObject<Dictionary<string, string>>(args); } + /// <summary> + /// String representing the last command entered by the user. + /// </summary> public static string LastCommand = ""; - public static void InvokeCommand(string text, bool isRemote = false) + /// <summary> + /// Gets the output of the last command. + /// </summary> + public static string LastCommandBuffer { get; private set; } + + /// <summary> + /// Invokes a ShiftOS terminal command. + /// </summary> + /// <param name="ns">The command's namespace.</param> + /// <param name="command">The command name.</param> + /// <param name="arguments">The command arguments.</param> + /// <param name="isRemote">Whether the command should be sent through Remote Terminal Session (RTS).</param> + public static void InvokeCommand(string ns, string command, Dictionary<string, string> arguments, bool isRemote = false) { try { - if (string.IsNullOrWhiteSpace(text)) + if (string.IsNullOrWhiteSpace(ns)) return; - var args = GetArgs(ref text); - bool commandWasClient = RunClient(text, args, isRemote); + bool commandWasClient = RunClient(ns, command, arguments, isRemote); - if (!commandWasClient && !string.IsNullOrWhiteSpace(text)) + if (!commandWasClient && !string.IsNullOrWhiteSpace(ns)) { PrefixEnabled = false; + ServerManager.SendMessage("script", $@"{{ - user: ""{text.Split('.')[0]}"", - script: ""{text.Split('.')[1]}"", - args: ""{JsonConvert.SerializeObject(args)}"" + user: ""{ns}"", + script: ""{command}"", + args: ""{GetSentArgs(arguments)}"" }}"); } - CommandProcessed?.Invoke(text, JsonConvert.SerializeObject(args)); + + CommandProcessed?.Invoke(ns + "." + command, JsonConvert.SerializeObject(arguments)); + } + catch (Exception ex) + { + Console.WriteLine($"Command parse error: {ex.Message}"); // This shouldn't ever be called now + PrefixEnabled = true; + + } + } + + /// <summary> + /// Transforms a <see cref="Dictionary{String, String}"/> of arguments to a <see cref="Dictionary{String, Object}"/>. + /// </summary> + /// <param name="argss">The original argument dictionary to convert.</param> + /// <returns>The converted dictionary.</returns> + public static string GetSentArgs(Dictionary<string, string> argss) + { + Dictionary<string, object> args = new Dictionary<string, object>(); + foreach (KeyValuePair<string, string> arg in argss) + { + args[arg.Key] = arg.Value; + } + return JsonConvert.SerializeObject(args); + } + + public class TerminalCommand + { + public override int GetHashCode() + { + int hash = 0; + foreach (char c in ToString()) + { + hash += (int)c; + } + return hash; + } + + public Namespace NamespaceInfo { get; set; } + public Command CommandInfo { get; set; } + + public List<string> RequiredArguments { get; set; } + public string Dependencies { get; set; } + + public MethodInfo CommandHandler; + + public Type CommandType; + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append(this.NamespaceInfo.name); + sb.Append("."); + sb.Append(this.CommandInfo.name); + if (this.RequiredArguments.Count > 0) + { + sb.Append("{"); + foreach (var arg in RequiredArguments) + { + sb.Append(arg); + sb.Append(":"); + if (RequiredArguments.IndexOf(arg) < RequiredArguments.Count - 1) + sb.Append(','); + } + sb.Append("}"); + } + sb.Append("|"); + sb.Append(CommandHandler.Name + "()"); + return sb.ToString(); + } + + public bool RequiresElevation { get; set; } + + public void Invoke(Dictionary<string, object> args) + { + List<string> errors = new List<string>(); + bool requiresAuth = false; + if (!KernelWatchdog.IsSafe(this)) + { + if (SaveSystem.CurrentUser.Permissions == Objects.UserPermissions.Admin) + requiresAuth = true; + else + errors.Add("You can't run this command - you do not have permission."); + } + if (errors.Count > 0) + { + foreach (var error in errors) + { + Console.WriteLine("Command error: " + error); + } + return; + } + if (requiresAuth) + { + Infobox.PromptText("Enter your password.", "This command requires you to have elevated permissions. Please enter your password to confirm this action.", (pass) => + { + if (pass == SaveSystem.CurrentUser.Password) + { + var uname = SaveSystem.CurrentUser.Username; + SaveSystem.CurrentUser = SaveSystem.CurrentSave.Users.FirstOrDefault(x => x.Username == "root"); + try + { + var h = CommandHandler; + h.Invoke(null, new[] { args }); + } + catch + { + var h = CommandHandler; + h.Invoke(null, null); + } + SaveSystem.CurrentUser = SaveSystem.CurrentSave.Users.FirstOrDefault(x => x.Username == uname); + } + else + { + Infobox.Show("Access denied.", "Incorrect password provided. The command will not run."); + } + }, true); + } + + try + { + CommandHandler.Invoke(null, new[] { args }); + } + catch + { + CommandHandler.Invoke(null, null); + } + } + } + + public class MemoryTextWriter : System.IO.TextWriter + { + public override Encoding Encoding + { + get + { + return Encoding.Unicode; + } + } + + private StringBuilder sb = null; + + public MemoryTextWriter() + { + sb = new StringBuilder(); + } + + public override string ToString() + { + return sb.ToString(); + } + + public override void Write(char value) + { + sb.Append(value); + } + + public override void WriteLine() + { + sb.AppendLine(); + } + + public override void Write(string value) + { + sb.Append(value); + } + + public override void Close() + { + sb.Clear(); + sb = null; + base.Close(); + } + + public override void WriteLine(string value) + { + sb.AppendLine(value); + } + } + + public static List<TerminalCommand> Commands { get; private set; } + + public static void PopulateTerminalCommands() + { + Commands = new List<TerminalCommand>(); + foreach(var exec in System.IO.Directory.GetFiles(Environment.CurrentDirectory)) + { + if(exec.ToLower().EndsWith(".exe") || exec.ToLower().EndsWith(".dll")) + { + try + { + var asm = Assembly.LoadFile(exec); + foreach(var type in asm.GetTypes()) + { + var ns = type.GetCustomAttributes(false).FirstOrDefault(x => x is Namespace) as Namespace; + if(ns != null) + { + foreach(var mth in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + var cmd = mth.GetCustomAttributes(false).FirstOrDefault(x => x is Command); + if(cmd != null) + { + var tc = new TerminalCommand(); + tc.RequiresElevation = !(type.GetCustomAttributes(false).FirstOrDefault(x => x is KernelModeAttribute) == null); + + tc.NamespaceInfo = ns; + + tc.CommandInfo = cmd as Command; + tc.RequiresElevation = tc.RequiresElevation || !(mth.GetCustomAttributes(false).FirstOrDefault(x => x is KernelModeAttribute) == null); + tc.RequiredArguments = new List<string>(); + foreach (var arg in mth.GetCustomAttributes(false).Where(x=>x is RequiresArgument)) + { + var rarg = arg as RequiresArgument; + tc.RequiredArguments.Add(rarg.argument); + } + var rupg = mth.GetCustomAttributes(false).FirstOrDefault(x => x is RequiresUpgradeAttribute) as RequiresUpgradeAttribute; + if (rupg != null) + tc.Dependencies = rupg.Upgrade; + else + tc.Dependencies = ""; + tc.CommandType = type; + tc.CommandHandler = mth; + if (!Commands.Contains(tc)) + Commands.Add(tc); + } + } + } + } + } + catch(Exception e) + { + Console.WriteLine("[termdb] Error: " + e.ToString()); + } + } + } + Console.WriteLine("[termdb] " + Commands.Count + " commands found."); + } + + /// <summary> + /// Invokes a ShiftOS terminal command. + /// </summary> + /// <param name="text">The full command text in regular ShiftOS syntax</param> + /// <param name="isRemote">Whether the command should be sent through Remote Terminal Session (RTS).</param> + public static void InvokeCommand(string text, bool isRemote = false) + { + if (string.IsNullOrWhiteSpace(text)) + return; + var tw = new MemoryTextWriter(); + Console.SetOut(tw); + try + { + var args = GetArgs(ref text); + + Stopwatch debugger = new Stopwatch(); + debugger.Start(); + bool commandWasClient = RunClient(text, args, isRemote); + + if (!commandWasClient) + { + Console.WriteLine("Command not found."); + debugger.Stop(); + return; + } + CommandProcessed?.Invoke(text, GetSentArgs(args)); + debugger.Stop(); + ConsoleEx.ForegroundColor = ConsoleColor.White; + Console.Write("<"); + ConsoleEx.Bold = true; + ConsoleEx.ForegroundColor = ConsoleColor.Blue; + Console.Write("debugger"); + ConsoleEx.ForegroundColor = ConsoleColor.White; + ConsoleEx.Bold = false; + Console.Write("> "); + Console.WriteLine("Command " + text + " took " + debugger.Elapsed.ToString() + " to execute."); } catch (Exception ex) { @@ -94,27 +397,54 @@ namespace ShiftOS.Engine PrefixEnabled = true; } + string buffer = tw.ToString(); + LastCommandBuffer = buffer; + Console.SetOut(new TerminalTextWriter()); + if(!isRemote) + Console.Write(buffer); + } + /// <summary> + /// Gets or sets whether the user prefix is printed after a command completes. + /// </summary> public static bool PrefixEnabled { get; set; } + /// <summary> + /// Gets or sets whether the user is in a story plot, and thus, the terminal input should be disabled. + /// </summary> public static bool InStory { get; set; } + /// <summary> + /// Another latest command string. + /// </summary> public static string latestCommmand = ""; + /// <summary> + /// Occurs when the engine requests a Terminal to be open. + /// </summary> public static event EmptyEventHandler TerminalRequested; + /// <summary> + /// Opens a Terminal. + /// </summary> internal static void OpenTerminal() { TerminalRequested?.Invoke(); } + /// <summary> + /// Determines if the specified command method can be ran in RTS + /// </summary> + /// <param name="mth">The method to scan</param> + /// <param name="isRemote">Is the user in an RTS session?</param> + /// <returns>Whether the command can be run.</returns> public static bool CanRunRemotely(MethodInfo mth, bool isRemote) { if (!isRemote) return true; - foreach(var attr in mth.GetCustomAttributes(false)) + foreach (var attr in mth.GetCustomAttributes(false)) { if (attr is RemoteLockAttribute) return false; @@ -123,145 +453,123 @@ namespace ShiftOS.Engine return true; } + /// <summary> + /// Runs a command on the client-side. + /// </summary> + /// <param name="ns">The command's namespace.</param> + /// <param name="cmd">The command name.</param> + /// <param name="args">The command's arguments.</param> + /// <param name="isRemote">Whether the command should be ran through RTS.</param> + /// <returns>Whether the command ran successfully.</returns> + public static bool RunClient(string ns, string cmd, Dictionary<string, string> args, bool isRemote = false) + { + return RunClient(ns + "." + cmd, args, isRemote); + } + + /// <summary> + /// Runs a command on the client. + /// </summary> + /// <param name="text">The command text.</param> + /// <param name="argss">The command arguments.</param> + /// <param name="isRemote">Whether the command should be ran through RTS.</param> + /// <returns>Whether the command ran successfully.</returns> + public static bool RunClient(string text, Dictionary<string, string> argss, bool isRemote = false) + { + Dictionary<string, object> args = new Dictionary<string, object>(); + foreach (KeyValuePair<string, string> arg in argss) + { + args[arg.Key] = arg.Value; + } + return RunClient(text, args, isRemote); + } + + /// <summary> + /// Runs a command on the client. + /// </summary> + /// <param name="text">The command text.</param> + /// <param name="args">The command arguments.</param> + /// <param name="isRemote">Whether the command should be run in RTS.</param> + /// <returns>Whether the command ran successfully.</returns> public static bool RunClient(string text, Dictionary<string, object> args, bool isRemote = false) { latestCommmand = text; - foreach (var asmExec in System.IO.Directory.GetFiles(Environment.CurrentDirectory)) + //Console.WriteLine(text + " " + "{" + string.Join(",", args.Select(kv => kv.Key + "=" + kv.Value).ToArray()) + "}" + " " + isRemote); + + + string[] split = text.Split('.'); + var cmd = Commands.FirstOrDefault(x => x.NamespaceInfo.name == split[0] && x.CommandInfo.name == split[1]); + if (cmd == null) + return false; + if (!Shiftorium.UpgradeInstalled(cmd.Dependencies)) + return false; + bool res = false; + foreach (var arg in cmd.RequiredArguments) { - try + if (!args.ContainsKey(arg)) { - var asm = Assembly.LoadFile(asmExec); + res = true; + Console.WriteLine("You are missing an argument with the key \"" + arg + "\"."); + } + } + if (res == true) + return true; + try + { + cmd.Invoke(args); + } + catch(Exception ex) + { + Console.WriteLine("Command error: " + ex.Message); + } - var types = asm.GetTypes(); + return true; + } - foreach (var type in types) - { - if (Shiftorium.UpgradeAttributesUnlocked(type)) - { - foreach (var a in type.GetCustomAttributes(false)) - { - if (a is Namespace) - { - var ns = a as Namespace; - if (text.Split('.')[0] == ns.name) - { - foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static)) - { - if (Shiftorium.UpgradeAttributesUnlocked(method)) - { - if (CanRunRemotely(method, isRemote)) - { - foreach (var ma in method.GetCustomAttributes(false)) - { - if (ma is Command) - { - var cmd = ma as Command; - if (text.Split('.')[1] == cmd.name) - { - - var attr = method.GetCustomAttribute<CommandObsolete>(); - - if (attr != null) - { - string newcommand = attr.newcommand; - if (attr.warn) - { - Console.WriteLine(Localization.Parse((newcommand == "" ? "{ERROR}" : "{WARN}") + attr.reason, new Dictionary<string, string>() { - {"%newcommand", newcommand} - })); - } - if (newcommand != "") - { - // redo the entire process running newcommand - - return RunClient(newcommand, args); - } - } - - var requiresArgs = method.GetCustomAttributes<RequiresArgument>(); - - bool error = false; - bool providedusage = false; - - foreach (RequiresArgument argument in requiresArgs) - { - if (!args.ContainsKey(argument.argument)) - { - - if (!providedusage) - { - string usageparse = "{COMMAND_" + ns.name.ToUpper() + "_" + cmd.name.ToUpper() + "_USAGE}"; - if (usageparse == Localization.Parse(usageparse)) - usageparse = ""; - else - usageparse = Shiftorium.UpgradeInstalled("help_usage") ? Localization.Parse("{ERROR}{USAGE}" + usageparse, new Dictionary<string, string>() { - {"%ns", ns.name}, - {"%cmd", cmd.name} - }) : ""; - - Console.WriteLine(usageparse); - - providedusage = true; - } - - if (Shiftorium.UpgradeInstalled("help_usage")) - { - Console.WriteLine(Localization.Parse("{ERROR_ARGUMENT_REQUIRED}", new Dictionary<string, string>() { - {"%argument", argument.argument} - })); - } - else - { - Console.WriteLine(Localization.Parse("{ERROR_ARGUMENT_REQUIRED_NO_USAGE}")); - } - - error = true; - } - } - - if (error) - { - throw new Exception("{ERROR_COMMAND_WRONG}"); - } - - try - { - return (bool)method.Invoke(null, new[] { args }); - } - catch (TargetInvocationException e) - { - Console.WriteLine(Localization.Parse("{ERROR_EXCEPTION_THROWN_IN_METHOD}")); - Console.WriteLine(e.InnerException.Message); - Console.WriteLine(e.InnerException.StackTrace); - return true; - } - catch - { - return (bool)method.Invoke(null, new object[] { }); - } - } - } - } - } - else - { - Console.WriteLine(text + " cannot be ran in a remote session"); - return true; - } - } - } - } - } - } - } - } - } - catch { } + /// <summary> + /// Prints the user prompt to the terminal. + /// </summary> + public static void PrintPrompt() + { + Console.WriteLine(); + if (SaveSystem.CurrentSave != null && CurrentUser != null) + { + ConsoleEx.BackgroundColor = SkinEngine.LoadedSkin.TerminalBackColorCC; + ConsoleEx.Italic = false; + ConsoleEx.Underline = false; + + ConsoleEx.ForegroundColor = ConsoleColor.Magenta; + ConsoleEx.Bold = true; + + Console.Write(SaveSystem.CurrentUser.Username); + ConsoleEx.Bold = false; + ConsoleEx.ForegroundColor = ConsoleColor.Gray; + Console.Write("@"); + ConsoleEx.Italic = true; + ConsoleEx.Bold = true; + ConsoleEx.ForegroundColor = ConsoleColor.Yellow; + Console.Write(SaveSystem.CurrentSave.SystemName); + ConsoleEx.Italic = false; + ConsoleEx.Bold = false; + ConsoleEx.ForegroundColor = ConsoleColor.Gray; + Console.Write(":~"); + Console.ForegroundColor = ConsoleColor.White; + ConsoleEx.Italic = true; + if (KernelWatchdog.InKernelMode == true) + Console.Write("#"); + else + Console.Write("$"); + ConsoleEx.Italic = false; + ConsoleEx.Bold = false; + ConsoleEx.ForegroundColor = SkinEngine.LoadedSkin.TerminalForeColorCC; + Console.Write(" "); + ConsoleEx.Flush(); } - return false; } - + + /// <summary> + /// Static constructor for <see cref="TerminalBackend"/>. + /// </summary> static TerminalBackend() { ServerMessageReceived onMessageReceived = (msg) => @@ -273,7 +581,7 @@ namespace ShiftOS.Engine if (TerminalBackend.PrefixEnabled) { - text3 = text4.Remove(0, $"{SaveSystem.CurrentSave.Username}@{SaveSystem.CurrentSave.SystemName}:~$ ".Length); + text3 = text4.Remove(0, $"{SaveSystem.CurrentUser.Username}@{SaveSystem.CurrentSave.SystemName}:~$ ".Length); } IsForwardingConsoleWrites = true; if (TerminalBackend.InStory == false) @@ -282,7 +590,7 @@ namespace ShiftOS.Engine } if (TerminalBackend.PrefixEnabled) { - Console.Write($"{SaveSystem.CurrentSave.Username}@{SaveSystem.CurrentSave.SystemName}:~$ "); + Console.Write($"{SaveSystem.CurrentUser.Username}@{SaveSystem.CurrentSave.SystemName}:~$ "); } IsForwardingConsoleWrites = false; } @@ -290,14 +598,14 @@ namespace ShiftOS.Engine { Console.Write(msg.Contents); } - else if(msg.Name == "handshake_from") + else if (msg.Name == "handshake_from") { var a = JsonConvert.DeserializeObject<Dictionary<string, object>>(msg.Contents); string uName = a["username"] as string; string pass = a["password"] as string; string sys = a["sysname"] as string; string guid = msg.GUID; - if(SaveSystem.CurrentSave.Username == uName && SaveSystem.CurrentSave.Password == pass && CurrentSave.SystemName == sys) + if (SaveSystem.CurrentUser.Username == uName && SaveSystem.CurrentSave.Password == pass && CurrentSave.SystemName == sys) { ForwardGUID = guid; ServerManager.SendMessage("trm_handshake_accept", $@"{{ @@ -307,7 +615,7 @@ namespace ShiftOS.Engine IsForwardingConsoleWrites = true; InvokeCommand("sos.status"); - Console.Write($"{SaveSystem.CurrentSave.Username}@{SaveSystem.CurrentSave.SystemName}:~$ "); + Console.Write($"{SaveSystem.CurrentUser.Username}@{SaveSystem.CurrentSave.SystemName}:~$ "); IsForwardingConsoleWrites = false; } } @@ -316,8 +624,29 @@ namespace ShiftOS.Engine ServerManager.MessageReceived += onMessageReceived; } + /// <summary> + /// Gets whether the terminal backend is forwarding console write requests through RTS to a remote client. + /// </summary> public static bool IsForwardingConsoleWrites { get; internal set; } + + /// <summary> + /// Gets the RTS forward GUID. + /// </summary> public static string ForwardGUID { get; internal set; } - + + /// <summary> + /// Occurs when the user inputs text in a Terminal. + /// </summary> + public static event TextSentEventHandler TextSent; + + /// <summary> + /// Fakes the user inputting text to a Terminal. + /// </summary> + /// <param name="text">The text to input.</param> + public static void SendText(string text) + { + TextSent?.Invoke(text); + } + } } diff --git a/ShiftOS_TheReturn/TerminalTextWriter.cs b/ShiftOS_TheReturn/TerminalTextWriter.cs index bc242a9..63e88eb 100644 --- a/ShiftOS_TheReturn/TerminalTextWriter.cs +++ b/ShiftOS_TheReturn/TerminalTextWriter.cs @@ -32,12 +32,28 @@ using System.Windows.Forms; namespace ShiftOS.Engine { + /// <summary> + /// Backend class for forwarding <see cref="System.Console"/> to the ShiftOS terminal. + /// </summary> public class TerminalTextWriter : TextWriter { - [System.Runtime.InteropServices.DllImport("user32.dll")] - public static extern bool LockWindowUpdate(IntPtr hWndLock); + public TerminalTextWriter() + { + ConsoleEx.OnFlush = () => + { + System.Diagnostics.Debug.WriteLine("[terminal] " + buffer); + Desktop.InvokeOnWorkerThread(() => + { + UnderlyingControl?.Write(buffer); + buffer = ""; + }); + }; + } + /// <summary> + /// Gets the encoding format for this <see cref="TextWriter"/>. God bless the Unicode Consortiem. + /// </summary> public override Encoding Encoding { get @@ -46,6 +62,9 @@ namespace ShiftOS.Engine } } + /// <summary> + /// Gets the underlying <see cref="ITerminalWidget"/> that this <see cref="TerminalTextWriter"/> is forwarding to. + /// </summary> public ITerminalWidget UnderlyingControl { get @@ -54,15 +73,22 @@ namespace ShiftOS.Engine } } + /// <summary> + /// Moves the caret to the last character in the textbox. + /// </summary> public void select() { Desktop.InvokeOnWorkerThread(new Action(() => { - UnderlyingControl.SelectBottom(); + UnderlyingControl?.SelectBottom(); })); } + /// <summary> + /// Write a character to the Terminal. + /// </summary> + /// <param name="value">The character to write.</param> public override void Write(char value) { if (TerminalBackend.IsForwardingConsoleWrites) @@ -74,14 +100,24 @@ namespace ShiftOS.Engine } else { - Desktop.InvokeOnWorkerThread(new Action(() => - { - UnderlyingControl.Write(value.ToString()); - select(); - })); + buffer += value; + } + } + + private string buffer = ""; + + public string Buffer + { + get + { + return buffer; } } + /// <summary> + /// Write text to the Terminal, followed by a newline. + /// </summary> + /// <param name="value">The text to write.</param> public override void WriteLine(string value) { if (TerminalBackend.IsForwardingConsoleWrites) @@ -94,18 +130,20 @@ namespace ShiftOS.Engine else { - Desktop.InvokeOnWorkerThread(new Action(() => - { - UnderlyingControl.WriteLine(value); - select(); - })); + buffer += value + Environment.NewLine; + ConsoleEx.Flush(); } } + [Obsolete("Stub.")] public void SetLastText() { } + /// <summary> + /// Write text to the Terminal. + /// </summary> + /// <param name="value">The text to write.</param> public override void Write(string value) { if (TerminalBackend.IsForwardingConsoleWrites) @@ -120,8 +158,7 @@ namespace ShiftOS.Engine Desktop.InvokeOnWorkerThread(new Action(() => { - UnderlyingControl.Write(value.ToString()); - select(); + buffer += value; })); } } diff --git a/ShiftOS_TheReturn/TutorialManager.cs b/ShiftOS_TheReturn/TutorialManager.cs index ea78cd8..13df153 100644 --- a/ShiftOS_TheReturn/TutorialManager.cs +++ b/ShiftOS_TheReturn/TutorialManager.cs @@ -30,10 +30,18 @@ using System.Threading.Tasks; namespace ShiftOS.Engine { + [Obsolete("This isn't used... I don't think...")] public static class TutorialManager { + /// <summary> + /// The tutorial frontend. + /// </summary> private static ITutorial _tut = null; + /// <summary> + /// Registers a tutorial frontend to the backend. + /// </summary> + /// <param name="tut"></param> public static void RegisterTutorial(ITutorial tut) { IsInTutorial = false; diff --git a/ShiftOS_TheReturn/UserManagementCommands.cs b/ShiftOS_TheReturn/UserManagementCommands.cs new file mode 100644 index 0000000..a64c99c --- /dev/null +++ b/ShiftOS_TheReturn/UserManagementCommands.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ShiftOS.Objects; + +namespace ShiftOS.Engine +{ + /// <summary> + /// Administrative user management terminal commands. + /// </summary> + [Namespace("admin")] + [KernelMode] + [RequiresUpgrade("mud_fundamentals")] + public static class AdminUserManagementCommands + { + /// <summary> + /// Add a user to the system. + /// </summary> + /// <param name="args">Command arguments.</param> + /// <returns>Command result.</returns> + [Command("add", description = "Add a user to the system.", usage ="name:")] + [RequiresArgument("name")] + public static bool AddUser(Dictionary<string, object> args) + { + string name = args["name"].ToString(); + if(SaveSystem.CurrentSave.Users.FirstOrDefault(x=>x.Username==name) != null) + { + Console.WriteLine("Error: User already exists."); + return true; + } + + var user = new ClientSave + { + Username = name, + Password = "", + Permissions = UserPermissions.User + }; + SaveSystem.CurrentSave.Users.Add(user); + Console.WriteLine($"Creating new user \"{name}\" with no password and User permissions."); + SaveSystem.SaveGame(); + return true; + } + + /// <summary> + /// Remove a user from the system. + /// </summary> + /// <param name="args">Command arguments.</param> + /// <returns>Command result.</returns> + + [Command("remove", description = "Remove a user from the system.", usage = "name:")] + [RequiresArgument("name")] + public static bool RemoveUser(Dictionary<string, object> args) + { + string name = args["name"].ToString(); + if (SaveSystem.CurrentSave.Users.FirstOrDefault(x => x.Username == name) == null) + { + Console.WriteLine("Error: User doesn't exist."); + return true; + } + + var user = SaveSystem.CurrentSave.Users.FirstOrDefault(x => x.Username == name); + if(user.Username != SaveSystem.CurrentUser.Username) + { + Console.WriteLine("Error: Cannot remove yourself."); + return true; + } + SaveSystem.CurrentSave.Users.Remove(user); + Console.WriteLine($"Removing user \"{name}\" from system..."); + SaveSystem.SaveGame(); + return true; + } + + + + /// <summary> + /// Set access control level for a user. + /// </summary> + /// <param name="args">Command arguments.</param> + /// <returns>Command result.</returns> + + [Command("set_acl")] + [RequiresArgument("user")] + [RequiresArgument("val")] + public static bool SetUserPermission(Dictionary<string, object> args) + { + int permission = 0; + string username = args["user"].ToString(); + try + { + permission = Convert.ToInt32(args["val"].ToString()); + } + catch + { + Console.WriteLine("Error: Permission value must be an integer."); + return true; + } + + if(SaveSystem.CurrentSave.Users.FirstOrDefault(x=>x.Username==username) == null) + { + Console.WriteLine("Error: User not found."); + return true; + } + + UserPermissions uperm = UserPermissions.Guest; + + switch (permission) + { + case 0: + uperm = UserPermissions.Guest; + break; + case 1: + uperm = UserPermissions.User; + break; + case 2: + uperm = UserPermissions.Admin; + break; + case 3: + uperm = UserPermissions.Root; + break; + default: + Console.WriteLine("Permission value must be between 0 and 4."); + return true; + } + + //Permissions are backwards... oops... + if(uperm < SaveSystem.CurrentUser.Permissions) + { + Console.WriteLine("Error: Cannot set user permissions to values greater than your own!"); + return true; + } + + var oldperm = SaveSystem.Users.FirstOrDefault(x => x.Username == username).Permissions; + if (SaveSystem.CurrentUser.Permissions > oldperm) + { + Console.WriteLine("Error: Can't set the permission of this user. They have more rights than you."); + return true; + } + + SaveSystem.CurrentSave.Users.FirstOrDefault(x => x.Username == username).Permissions = uperm; + Console.WriteLine("User permissions updated."); + return true; + } + + /// <summary> + /// List all users in the system. + /// </summary> + /// <param name="args">Command arguments.</param> + /// <returns>Command result.</returns> + + [Command("users", description = "Get a list of all users on the system.")] + public static bool GetUsers() + { + foreach (var u in SaveSystem.CurrentSave.Users) + { + if (u.Username == SaveSystem.CurrentUser.Username) + { + ConsoleEx.ForegroundColor = ConsoleColor.Magenta; + ConsoleEx.Bold = true; + } + else + { + ConsoleEx.ForegroundColor = ConsoleColor.Gray; + ConsoleEx.Bold = false; + } + Console.WriteLine(u.Username); + } + return true; + } + } + + /// <summary> + /// Non-administrative user management terminal commands. + /// </summary> + [Namespace("user")] + [RequiresUpgrade("mud_fundamentals")] + public static class UserManagementCommands + { + /// <summary> + /// Log in as another user. + /// </summary> + /// <param name="args">Command arguments.</param> + /// <returns>Command result.</returns> + [Command("login", description = "Log in as another user.")] + [RequiresArgument("user")] + [RequiresArgument("pass")] + public static bool Login(Dictionary<string, object> args) + { + string user = args["user"].ToString(); + string pass = args["pass"].ToString(); + + var usr = SaveSystem.CurrentSave.Users.FirstOrDefault(x => x.Username == user); + if(usr==null) + { + Console.WriteLine("Error: No such user."); + return true; + } + + if (usr.Password != pass) + { + Console.WriteLine("Access denied."); + return true; + } + + SaveSystem.CurrentUser = usr; + Console.WriteLine("Access granted."); + return true; + } + + /// <summary> + /// Set the password for the current user. + /// </summary> + /// <param name="args">Command arguments.</param> + /// <returns>Command result.</returns> + [Command("setpass", description ="Allows you to set your password to a new value.", usage ="old:,new:")] + [RequiresArgument("old")] + [RequiresArgument("new")] + public static bool SetPassword(Dictionary<string, object> args) + { + string old = args["old"].ToString(); + string newpass = args["new"].ToString(); + + if(old == SaveSystem.CurrentUser.Password) + { + SaveSystem.CurrentUser.Password = newpass; + SaveSystem.CurrentSave.Users.FirstOrDefault(x => x.Username == SaveSystem.CurrentUser.Username).Password = newpass; + Console.WriteLine("Password set successfully."); + SaveSystem.SaveGame(); + } + else + { + Console.WriteLine("Passwords do not match."); + } + return true; + } + } +} diff --git a/ShiftOS_TheReturn/VirusEngine.cs b/ShiftOS_TheReturn/VirusEngine.cs deleted file mode 100644 index 650db92..0000000 --- a/ShiftOS_TheReturn/VirusEngine.cs +++ /dev/null @@ -1,106 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 Michael VanOverbeek and ShiftOS devs - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; -using static ShiftOS.Objects.ShiftFS.Utils; - - -namespace ShiftOS.Engine -{ - public static class VirusEngine - { - public static void InfectFile(string file, string virusid) - { - var infected = new List<string>(); - var hData = GetHeaderText(file); - - if (hData == "") - { - infected.Add(virusid); - } - else - { - infected = JsonConvert.DeserializeObject<List<string>>(hData); - if (!infected.Contains(virusid)) - infected.Add(virusid); - } - - SetHeaderText(file, JsonConvert.SerializeObject(infected)); - } - - public static void DisinfectFile(string file, int threatlevel) - { - var infected = new List<string>(); - var hData = GetHeaderText(file); - - if (hData != "") - { - infected = JsonConvert.DeserializeObject<List<string>>(hData); - for (int i = 0; i < infected.Count; i++) - { - string[] splitID = infected[i].Split('.'); - int th = Convert.ToInt32(splitID[splitID.Length - 1]); - if (th <= threatlevel) - { - infected.RemoveAt(i); - } - } - } - - SetHeaderText(file, JsonConvert.SerializeObject(infected)); - } - - internal static string[] FindAllVirusesInFile(string file) - { - string hdata = GetHeaderText(file); - return (hdata != "") ? new string[0] : JsonConvert.DeserializeObject<string[]>(hdata); - } - - } - - public abstract class Virus - { - /// <summary> - /// Inject the virus into system memory by running it. - /// </summary> - public abstract void Activate(); - - /// <summary> - /// Terminate the virus. - /// </summary> - public abstract void Deactivate(); - - public abstract int ThreatLevel { get; } - - public abstract string Signature { get; } - - public abstract string Type { get; } - } -} |
