2019-12-25 15:32:57 -05:00
|
|
|
|
using Jint;
|
2020-05-11 17:10:43 -04:00
|
|
|
|
using Jint.Native;
|
|
|
|
|
using Jint.Runtime;
|
2020-02-12 14:13:59 -05:00
|
|
|
|
using Microsoft.CSharp.RuntimeBinder;
|
2020-02-11 17:44:06 -05:00
|
|
|
|
using SharedLibraryCore;
|
2019-12-25 15:32:57 -05:00
|
|
|
|
using SharedLibraryCore.Database.Models;
|
2020-05-11 17:10:43 -04:00
|
|
|
|
using SharedLibraryCore.Exceptions;
|
2018-11-05 22:01:29 -05:00
|
|
|
|
using SharedLibraryCore.Interfaces;
|
2020-05-11 17:10:43 -04:00
|
|
|
|
using System.Collections.Generic;
|
2018-08-23 17:16:30 -04:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
2019-12-25 15:32:57 -05:00
|
|
|
|
using System.Threading;
|
2018-08-23 17:16:30 -04:00
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
2020-02-11 17:44:06 -05:00
|
|
|
|
namespace IW4MAdmin.Application.Misc
|
2018-08-23 17:16:30 -04:00
|
|
|
|
{
|
2020-02-11 17:44:06 -05:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// implementation of IPlugin
|
|
|
|
|
/// used to proxy script plugin requests
|
|
|
|
|
/// </summary>
|
2020-01-31 21:15:07 -05:00
|
|
|
|
public class ScriptPlugin : IPlugin
|
2018-08-23 17:16:30 -04:00
|
|
|
|
{
|
|
|
|
|
public string Name { get; set; }
|
|
|
|
|
|
|
|
|
|
public float Version { get; set; }
|
|
|
|
|
|
2018-08-26 20:20:47 -04:00
|
|
|
|
public string Author { get; set; }
|
2018-08-23 17:16:30 -04:00
|
|
|
|
|
2020-02-17 11:05:31 -05:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// indicates if the plugin is a parser
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IsParser { get; private set; }
|
|
|
|
|
|
2020-02-01 13:27:14 -05:00
|
|
|
|
public FileSystemWatcher Watcher { get; private set; }
|
|
|
|
|
|
2020-02-02 17:21:34 -05:00
|
|
|
|
private Engine _scriptEngine;
|
2020-02-01 13:27:14 -05:00
|
|
|
|
private readonly string _fileName;
|
2020-02-02 17:21:34 -05:00
|
|
|
|
private readonly SemaphoreSlim _onProcessing;
|
2019-12-25 15:32:57 -05:00
|
|
|
|
private bool successfullyLoaded;
|
2020-05-11 17:10:43 -04:00
|
|
|
|
private readonly List<string> _registeredCommandNames;
|
2018-08-23 17:16:30 -04:00
|
|
|
|
|
2020-04-22 19:46:41 -04:00
|
|
|
|
public ScriptPlugin(string filename, string workingDirectory = null)
|
2018-08-23 17:16:30 -04:00
|
|
|
|
{
|
2020-02-01 13:27:14 -05:00
|
|
|
|
_fileName = filename;
|
|
|
|
|
Watcher = new FileSystemWatcher()
|
2018-08-23 17:16:30 -04:00
|
|
|
|
{
|
2020-04-22 19:46:41 -04:00
|
|
|
|
Path = workingDirectory == null ? $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}" : workingDirectory,
|
2018-08-23 17:16:30 -04:00
|
|
|
|
NotifyFilter = NotifyFilters.Size,
|
2020-02-01 13:27:14 -05:00
|
|
|
|
Filter = _fileName.Split(Path.DirectorySeparatorChar).Last()
|
2018-08-23 17:16:30 -04:00
|
|
|
|
};
|
|
|
|
|
|
2020-02-01 13:27:14 -05:00
|
|
|
|
Watcher.EnableRaisingEvents = true;
|
2020-02-02 17:21:34 -05:00
|
|
|
|
_onProcessing = new SemaphoreSlim(1, 1);
|
2020-05-11 17:10:43 -04:00
|
|
|
|
_registeredCommandNames = new List<string>();
|
2019-11-15 15:50:20 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~ScriptPlugin()
|
|
|
|
|
{
|
2020-02-01 13:27:14 -05:00
|
|
|
|
Watcher.Dispose();
|
2020-02-02 17:21:34 -05:00
|
|
|
|
_onProcessing.Dispose();
|
2018-08-23 17:16:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-05-11 17:10:43 -04:00
|
|
|
|
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory)
|
2020-02-01 13:27:14 -05:00
|
|
|
|
{
|
2020-02-02 17:21:34 -05:00
|
|
|
|
await _onProcessing.WaitAsync();
|
2019-12-25 15:32:57 -05:00
|
|
|
|
|
2020-02-01 13:27:14 -05:00
|
|
|
|
try
|
2019-12-25 15:32:57 -05:00
|
|
|
|
{
|
2020-02-01 13:27:14 -05:00
|
|
|
|
// for some reason we get an event trigger when the file is not finished being modified.
|
|
|
|
|
// this must have been a change in .NET CORE 3.x
|
|
|
|
|
// so if the new file is empty we can't process it yet
|
|
|
|
|
if (new FileInfo(_fileName).Length == 0L)
|
2019-12-25 15:32:57 -05:00
|
|
|
|
{
|
2020-02-01 13:27:14 -05:00
|
|
|
|
return;
|
2019-12-25 15:32:57 -05:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-02 17:21:34 -05:00
|
|
|
|
bool firstRun = _scriptEngine == null;
|
2019-12-25 15:32:57 -05:00
|
|
|
|
|
2020-02-01 13:27:14 -05:00
|
|
|
|
// it's been loaded before so we need to call the unload event
|
|
|
|
|
if (!firstRun)
|
|
|
|
|
{
|
|
|
|
|
await OnUnloadAsync();
|
2020-05-11 17:10:43 -04:00
|
|
|
|
|
|
|
|
|
foreach (string commandName in _registeredCommandNames)
|
|
|
|
|
{
|
|
|
|
|
manager.GetLogger(0).WriteDebug($"Removing plugin registered command \"{commandName}\"");
|
|
|
|
|
manager.RemoveCommandByName(commandName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_registeredCommandNames.Clear();
|
2020-02-01 13:27:14 -05:00
|
|
|
|
}
|
2018-08-23 17:16:30 -04:00
|
|
|
|
|
2020-02-01 13:27:14 -05:00
|
|
|
|
successfullyLoaded = false;
|
|
|
|
|
string script;
|
2018-10-09 21:19:06 -04:00
|
|
|
|
|
2020-02-01 13:27:14 -05:00
|
|
|
|
using (var stream = new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
2018-10-09 21:19:06 -04:00
|
|
|
|
{
|
2020-02-01 13:27:14 -05:00
|
|
|
|
using (var reader = new StreamReader(stream, Encoding.Default))
|
|
|
|
|
{
|
|
|
|
|
script = await reader.ReadToEndAsync();
|
|
|
|
|
}
|
2018-10-09 21:19:06 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-02 17:21:34 -05:00
|
|
|
|
_scriptEngine = new Engine(cfg =>
|
2020-02-01 13:27:14 -05:00
|
|
|
|
cfg.AllowClr(new[]
|
|
|
|
|
{
|
2018-09-02 17:59:27 -04:00
|
|
|
|
typeof(System.Net.Http.HttpClient).Assembly,
|
2018-11-05 22:01:29 -05:00
|
|
|
|
typeof(EFClient).Assembly,
|
2020-01-17 18:31:53 -05:00
|
|
|
|
typeof(Utilities).Assembly,
|
|
|
|
|
typeof(Encoding).Assembly
|
2020-02-01 13:27:14 -05:00
|
|
|
|
})
|
|
|
|
|
.CatchClrExceptions());
|
2018-08-23 17:16:30 -04:00
|
|
|
|
|
2020-02-02 17:21:34 -05:00
|
|
|
|
_scriptEngine.Execute(script);
|
|
|
|
|
_scriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
|
|
|
|
|
dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject();
|
2018-08-23 17:16:30 -04:00
|
|
|
|
|
2020-02-01 13:27:14 -05:00
|
|
|
|
Author = pluginObject.author;
|
|
|
|
|
Name = pluginObject.name;
|
|
|
|
|
Version = (float)pluginObject.version;
|
2019-01-26 21:33:37 -05:00
|
|
|
|
|
2020-05-11 17:10:43 -04:00
|
|
|
|
var commands = _scriptEngine.GetValue("commands");
|
|
|
|
|
|
|
|
|
|
if (commands != JsValue.Undefined)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
foreach (var command in GenerateScriptCommands(commands, scriptCommandFactory))
|
|
|
|
|
{
|
|
|
|
|
manager.GetLogger(0).WriteDebug($"Adding plugin registered command \"{command.Name}\"");
|
|
|
|
|
manager.AddAdditionalCommand(command);
|
|
|
|
|
_registeredCommandNames.Add(command.Name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
catch (RuntimeBinderException e)
|
|
|
|
|
{
|
|
|
|
|
throw new PluginException($"Not all required fields were found: {e.Message}") { PluginFile = _fileName };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-12 14:13:59 -05:00
|
|
|
|
await OnLoadAsync(manager);
|
|
|
|
|
|
2020-02-01 13:27:14 -05:00
|
|
|
|
try
|
2019-01-27 19:54:18 -05:00
|
|
|
|
{
|
2020-02-01 13:27:14 -05:00
|
|
|
|
if (pluginObject.isParser)
|
|
|
|
|
{
|
2020-02-17 11:05:31 -05:00
|
|
|
|
IsParser = true;
|
2020-02-02 17:21:34 -05:00
|
|
|
|
IEventParser eventParser = (IEventParser)_scriptEngine.GetValue("eventParser").ToObject();
|
|
|
|
|
IRConParser rconParser = (IRConParser)_scriptEngine.GetValue("rconParser").ToObject();
|
2020-02-01 13:27:14 -05:00
|
|
|
|
manager.AdditionalEventParsers.Add(eventParser);
|
|
|
|
|
manager.AdditionalRConParsers.Add(rconParser);
|
|
|
|
|
}
|
2019-01-27 19:54:18 -05:00
|
|
|
|
}
|
|
|
|
|
|
2020-02-12 14:13:59 -05:00
|
|
|
|
catch (RuntimeBinderException) { }
|
2019-01-27 19:54:18 -05:00
|
|
|
|
|
2020-02-01 13:27:14 -05:00
|
|
|
|
if (!firstRun)
|
|
|
|
|
{
|
|
|
|
|
await OnLoadAsync(manager);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
successfullyLoaded = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
catch
|
2018-08-23 17:16:30 -04:00
|
|
|
|
{
|
2020-02-01 13:27:14 -05:00
|
|
|
|
throw;
|
2018-08-23 17:16:30 -04:00
|
|
|
|
}
|
2019-12-25 15:32:57 -05:00
|
|
|
|
|
2020-02-01 13:27:14 -05:00
|
|
|
|
finally
|
|
|
|
|
{
|
2020-02-02 17:21:34 -05:00
|
|
|
|
if (_onProcessing.CurrentCount == 0)
|
2020-02-01 13:27:14 -05:00
|
|
|
|
{
|
2020-02-02 17:21:34 -05:00
|
|
|
|
_onProcessing.Release(1);
|
2020-02-01 13:27:14 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-23 17:16:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-25 15:32:57 -05:00
|
|
|
|
public async Task OnEventAsync(GameEvent E, Server S)
|
2018-08-23 17:16:30 -04:00
|
|
|
|
{
|
2019-12-25 15:32:57 -05:00
|
|
|
|
if (successfullyLoaded)
|
2018-09-04 13:40:29 -04:00
|
|
|
|
{
|
2020-02-02 17:21:34 -05:00
|
|
|
|
await _onProcessing.WaitAsync();
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_scriptEngine.SetValue("_gameEvent", E);
|
|
|
|
|
_scriptEngine.SetValue("_server", S);
|
|
|
|
|
_scriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(S));
|
2020-02-17 11:05:31 -05:00
|
|
|
|
_scriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue();
|
2020-02-02 17:21:34 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
if (_onProcessing.CurrentCount == 0)
|
|
|
|
|
{
|
|
|
|
|
_onProcessing.Release(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-04 13:40:29 -04:00
|
|
|
|
}
|
2018-08-23 17:16:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task OnLoadAsync(IManager manager)
|
|
|
|
|
{
|
2020-02-01 13:27:14 -05:00
|
|
|
|
manager.GetLogger(0).WriteDebug($"OnLoad executing for {Name}");
|
2020-02-02 17:21:34 -05:00
|
|
|
|
_scriptEngine.SetValue("_manager", manager);
|
|
|
|
|
return Task.FromResult(_scriptEngine.Execute("plugin.onLoadAsync(_manager)").GetCompletionValue());
|
2018-08-23 17:16:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task OnTickAsync(Server S)
|
|
|
|
|
{
|
2020-02-02 17:21:34 -05:00
|
|
|
|
_scriptEngine.SetValue("_server", S);
|
|
|
|
|
return Task.FromResult(_scriptEngine.Execute("plugin.onTickAsync(_server)").GetCompletionValue());
|
2018-08-23 17:16:30 -04:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-25 15:32:57 -05:00
|
|
|
|
public async Task OnUnloadAsync()
|
2019-01-27 19:54:18 -05:00
|
|
|
|
{
|
2019-12-25 15:32:57 -05:00
|
|
|
|
if (successfullyLoaded)
|
|
|
|
|
{
|
2020-02-02 17:21:34 -05:00
|
|
|
|
await Task.FromResult(_scriptEngine.Execute("plugin.onUnloadAsync()").GetCompletionValue());
|
2019-12-25 15:32:57 -05:00
|
|
|
|
}
|
2019-01-27 19:54:18 -05:00
|
|
|
|
}
|
2020-05-11 17:10:43 -04:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// finds declared script commands in the script plugin
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="commands">commands value from jint parser</param>
|
|
|
|
|
/// <param name="scriptCommandFactory">factory to create the command from</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands, IScriptCommandFactory scriptCommandFactory)
|
|
|
|
|
{
|
|
|
|
|
List<IManagerCommand> commandList = new List<IManagerCommand>();
|
|
|
|
|
|
|
|
|
|
// go through each defined command
|
|
|
|
|
foreach (var command in commands.AsArray())
|
|
|
|
|
{
|
|
|
|
|
dynamic dynamicCommand = command.ToObject();
|
|
|
|
|
string name = dynamicCommand.name;
|
|
|
|
|
string alias = dynamicCommand.alias;
|
|
|
|
|
string description = dynamicCommand.description;
|
|
|
|
|
string permission = dynamicCommand.permission;
|
|
|
|
|
|
|
|
|
|
List<(string, bool)> args = new List<(string, bool)>();
|
|
|
|
|
dynamic arguments = null;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
arguments = dynamicCommand.arguments;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
catch (RuntimeBinderException)
|
|
|
|
|
{
|
|
|
|
|
// arguments are optional
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (arguments != null)
|
|
|
|
|
{
|
|
|
|
|
foreach (var arg in dynamicCommand.arguments)
|
|
|
|
|
{
|
|
|
|
|
args.Add((arg.name, (bool)arg.required));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void execute(GameEvent e)
|
|
|
|
|
{
|
|
|
|
|
_scriptEngine.SetValue("_event", e);
|
|
|
|
|
var jsEventObject = _scriptEngine.GetValue("_event");
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
dynamicCommand.execute.Target.Invoke(jsEventObject);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
catch (JavaScriptException ex)
|
|
|
|
|
{
|
|
|
|
|
throw new PluginException($"An error occured while executing action for script plugin: {ex.Error} (Line: {ex.Location.Start.Line}, Character: {ex.Location.Start.Column})") { PluginFile = _fileName };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, args, execute));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return commandList;
|
|
|
|
|
}
|
2018-08-23 17:16:30 -04:00
|
|
|
|
}
|
|
|
|
|
}
|