ShiftOS_TheReturn/ShiftOS_TheReturn/Scripting.cs
2017-02-18 13:25:30 -05:00

360 lines
11 KiB
C#

/*
* 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.Tasks;
using System.Reflection;
using ShiftOS.Objects.ShiftFS;
using DynamicLua;
using System.Dynamic;
using Newtonsoft.Json;
using System.Windows.Forms;
using System.Threading;
using System.Net;
namespace ShiftOS.Engine.Scripting
{
public class LuaInterpreter
{
public dynamic Lua = new DynamicLua.DynamicLua();
public bool Running = true;
public LuaInterpreter()
{
Lua(@"function totable(clrlist)
local t = {}
local it = clrlist:GetEnumerator()
while it:MoveNext() do
t[#t+1] = it.Current
end
return t
end");
SetupAPIs();
Application.ApplicationExit += (o, a) =>
{
Running = false;
};
}
public void SetupAPIs()
{
Lua.registerEvent = new Action<string, Action<object>>((eventName, callback) =>
{
LuaEvent += (e, s) =>
{
if(e == eventName)
callback?.Invoke(s);
};
});
//This temporary proxy() method will be used by the API prober.
Lua.proxy = new Func<string, dynamic>((objName) =>
{
foreach (var f in System.IO.Directory.GetFiles(Environment.CurrentDirectory))
{
if (f.EndsWith(".exe") || f.EndsWith(".dll"))
{
try
{
var asm = Assembly.LoadFile(f);
foreach (var type in asm.GetTypes())
{
if (type.Name == objName)
{
dynamic dynObj = Activator.CreateInstance(type);
return dynObj;
}
}
}
catch { }
}
}
throw new Exception("{CLASS_NOT_FOUND}");
});
foreach (var f in System.IO.Directory.GetFiles(Environment.CurrentDirectory))
{
if (f.EndsWith(".exe") || f.EndsWith(".dll"))
{
try
{
var thisasm = Assembly.LoadFile(f);
foreach (var type in thisasm.GetTypes())
{
foreach (var attr in type.GetCustomAttributes(false))
{
if (attr is ExposedAttribute)
{
var eattr = attr as ExposedAttribute;
Lua($"{eattr.Name} = proxy(\"{type.Name}\")");
}
}
}
}
catch
{
}
}
}
//Now we can null out the proxy() method as it can cause security risks.
Lua.isRunning = new Func<bool>(() => { return this.Running; });
Lua.proxy = null;
Lua.invokeOnWorkerThread = new Action<string>((func) =>
{
Desktop.InvokeOnWorkerThread(new Action(() =>
{
Lua(func + "()");
}));
});
Lua.invokeOnNewThread = new Action<string>((func) =>
{
var t = new Thread(new ThreadStart(() =>
{
Lua(func + "()");
}));
t.IsBackground = true;
t.Start();
});
Lua.includeScript = new Action<string>((file) =>
{
if (!Utils.FileExists(file))
throw new ArgumentException("File does not exist.");
if (!file.EndsWith(".lua"))
throw new ArgumentException("File is not a lua file.");
Lua(Utils.ReadAllText(file));
});
}
public void ExecuteFile(string file)
{
if (Utils.FileExists(file))
{
Execute(Utils.ReadAllText(file));
}
else
{
throw new Exception("The file \"" + file + "\" was not found on the system.");
}
}
public void Execute(string lua)
{
try
{
Console.WriteLine("");
Lua(lua);
Console.WriteLine($"{SaveSystem.CurrentSave.Username}@{SaveSystem.CurrentSave.SystemName}:~$ ");
}
catch (Exception e)
{
Infobox.Show("Script error", $@"Script threw {e.GetType().Name}:
{e.Message}");
}
}
/// <summary>
/// Occurs when a Lua event is fired by C#.
/// </summary>
private static event Action<string, object> LuaEvent;
/// <summary>
/// Raises a Lua event with the specified name and caller object.
/// </summary>
/// <param name="eventName">The name of the event. Scripts use this to check what type of event occurred.</param>
/// <param name="caller">The caller of the event. Scripts can use this to check if they should handle this event.</param>
public static void RaiseEvent(string eventName, object caller)
{
LuaEvent?.Invoke(eventName, caller);
}
}
[Exposed("net")]
public class NetFunctions
{
public string get(string url)
{
return new WebClient().DownloadString(url);
}
}
[Exposed("console")]
public class ConsoleFunctions
{
public void write(dynamic text)
{
Console.Write(text.ToString());
}
public void writeLine(dynamic text)
{
Console.WriteLine(text.ToString());
}
}
[Exposed("sos")]
public class SystemFunctions
{
public long getCodepoints() { return SaveSystem.CurrentSave.Codepoints; }
public bool runCommand(string cmd)
{
var args = TerminalBackend.GetArgs(ref cmd);
return TerminalBackend.RunClient(cmd, args);
}
public void addCodepoints(int cp)
{
if (cp > 100 || cp <= 0)
{
throw new Exception("Value 'cp' must be at or below 100, and above 0.");
}
else
{
SaveSystem.CurrentSave.Codepoints += cp;
SaveSystem.SaveGame();
}
}
}
[Exposed("infobox")]
public class InfoboxFunctions
{
public void show(string title, string message)
{
Infobox.Show(title, message);
}
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)
{
Infobox.PromptText(title, message, callback);
}
}
[Exposed("fileskimmer")]
public class FileSkimmerFunctions
{
public void openFile(string extensions, Action<string> callback)
{
FileSkimmerBackend.GetFile(extensions.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries), FileOpenerStyle.Open, callback);
}
public void saveFile(string extensions, Action<string> callback)
{
FileSkimmerBackend.GetFile(extensions.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries), FileOpenerStyle.Save, callback);
}
}
[Exposed("fs")]
public class ShiftFSFunctions
{
public string readAllText(string path)
{
return Utils.ReadAllText(path);
}
public void copy(string i, string o)
{
Utils.WriteAllBytes(o, Utils.ReadAllBytes(i));
}
public string[] getFiles(string dir)
{
return Utils.GetFiles(dir);
}
public string[] getDirectories(string dir)
{
return Utils.GetDirectories(dir);
}
public byte[] readAllBytes(string path)
{
return Utils.ReadAllBytes(path);
}
public void writeAllText(string path, string contents)
{
Utils.WriteAllText(path, contents);
}
public void writeAllBytes(string path, byte[] contents)
{
Utils.WriteAllBytes(path, contents);
}
public bool fileExists(string path)
{
return Utils.FileExists(path);
}
public bool directoryExists(string path)
{
return Utils.DirectoryExists(path);
}
public void delete(string path)
{
Utils.Delete(path);
}
public void createDirectory(string path)
{
Utils.CreateDirectory(path);
}
}
public class ExposedAttribute : Attribute
{
/// <summary>
/// If applied to a non-static class, the ShiftOS engine will see this class as a Lua object of the specified name.
/// </summary>
/// <param name="name">The name of the Lua object</param>
public ExposedAttribute(string name)
{
Name = name;
}
public string Name { get; private set; }
}
}