From fe96f71e6a3df75b3c8dcd13685907b44257ba4e Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Sun, 25 Apr 2021 12:24:30 -0400 Subject: [PATCH] Add filmscript auto-installer --- NScript/Filmscript/FilmscriptInstaller.cs | 33 ++++++++++ NScript/Filmscript/GeneralBindings.cs | 6 +- NScript/Gitea/Asset.cs | 17 +++++ NScript/Gitea/Author.cs | 19 ++++++ NScript/Gitea/Release.cs | 22 +++++++ NScript/Gitea/ReleaseUtility.cs | 65 +++++++++++++++++++ NScript/Install/IInstallable.cs | 11 ++++ NScript/Install/InstallTools.cs | 26 ++++++++ NScript/Install/Installer.cs | 76 +++++++++++++++++++++++ NScript/NScript.csproj | 9 +++ NScript/NScriptPlugin.cs | 23 +++++-- 11 files changed, 301 insertions(+), 6 deletions(-) create mode 100644 NScript/Filmscript/FilmscriptInstaller.cs create mode 100644 NScript/Gitea/Asset.cs create mode 100644 NScript/Gitea/Author.cs create mode 100644 NScript/Gitea/Release.cs create mode 100644 NScript/Gitea/ReleaseUtility.cs create mode 100644 NScript/Install/IInstallable.cs create mode 100644 NScript/Install/InstallTools.cs create mode 100644 NScript/Install/Installer.cs diff --git a/NScript/Filmscript/FilmscriptInstaller.cs b/NScript/Filmscript/FilmscriptInstaller.cs new file mode 100644 index 0000000..42cc245 --- /dev/null +++ b/NScript/Filmscript/FilmscriptInstaller.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace NScript.Filmscript +{ + public class FilmscriptInstaller : Install.IInstallable + { + public string Name { get; } = "filmscript.dll"; + + public void Install() + { + List releases = Gitea.ReleaseUtility.GetReleases("NGnius", "filmscript-rs"); + + GamecraftModdingAPI.Utility.Logging.Log($"Downloading {Name}"); +#if DEBUG + var r = Gitea.ReleaseUtility.FindRelease(releases, prerelease: true)?? throw new Exception("Failed to find valid release candidate"); +#else + var r = Gitea.ReleaseUtility.FindRelease(releases)?? throw new Exception("Failed to find valid release candidate"); +#endif + var a = Gitea.ReleaseUtility.FindAsset(r, Name)?? throw new Exception($"Failed to find valid asset candidate for {Name}"); + var download = Gitea.ReleaseUtility.DownloadAsset(a); + File.WriteAllBytes(NScript.Install.InstallTools.NativeDllFilepath(Name), download); + } + + public bool IsInstalled() + { + // NOTE: Mono will only try to load a native DLL once, so trying to call a non-existent DLL + // will prevent it from being loaded later, even if it's downloaded between now and then. + return NScript.Install.InstallTools.NativeDllExists(Name); + } + } +} \ No newline at end of file diff --git a/NScript/Filmscript/GeneralBindings.cs b/NScript/Filmscript/GeneralBindings.cs index 3724e6b..4daee34 100644 --- a/NScript/Filmscript/GeneralBindings.cs +++ b/NScript/Filmscript/GeneralBindings.cs @@ -5,16 +5,20 @@ namespace NScript.Filmscript { public static class GeneralBindings { + // raw bindings [DllImport("filmscript.dll")] public static extern string filmscript_version(); + private static bool? dllExists = null; + public static string Version() { return filmscript_version(); } - public static bool Exists() + public static bool DllExists() { + if (dllExists != null) return dllExists.Value; try { filmscript_version(); diff --git a/NScript/Gitea/Asset.cs b/NScript/Gitea/Asset.cs new file mode 100644 index 0000000..0f956f0 --- /dev/null +++ b/NScript/Gitea/Asset.cs @@ -0,0 +1,17 @@ +using System; +using Newtonsoft.Json; + +namespace NScript.Gitea +{ + [JsonObject] + public struct Asset + { + [JsonProperty("id")] public int Id; + [JsonProperty("name")] public string Name; + [JsonProperty("size")] public ulong Size; + [JsonProperty("download_count")] public int DownloadCount; + [JsonProperty("created_at")] public DateTime CreatedAt; + [JsonProperty("uuid")] public string Uuid; + [JsonProperty("browser_download_url")] public string BrowserDownloadUrl; + } +} \ No newline at end of file diff --git a/NScript/Gitea/Author.cs b/NScript/Gitea/Author.cs new file mode 100644 index 0000000..3bcb16e --- /dev/null +++ b/NScript/Gitea/Author.cs @@ -0,0 +1,19 @@ +using System; +using Newtonsoft.Json; + +namespace NScript.Gitea +{ + [JsonObject] + public struct Author + { + [JsonProperty("id")] public int Id; + [JsonProperty("login")] public string Login; + [JsonProperty("full_name")] public string FullName; + [JsonProperty("email")] public string Email; + [JsonProperty("avatar_url")] public string AvatarUrl; + [JsonProperty("language")] public string Language; + [JsonProperty("last_login")] public DateTime LastLogin; + [JsonProperty("created")] public DateTime CreatedAt; + [JsonProperty("username")] public string Username; + } +} \ No newline at end of file diff --git a/NScript/Gitea/Release.cs b/NScript/Gitea/Release.cs new file mode 100644 index 0000000..e742467 --- /dev/null +++ b/NScript/Gitea/Release.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace NScript.Gitea +{ + [JsonObject] + public struct Release + { + [JsonProperty("id")] public int Id; + [JsonProperty("tag_name")] public string TagName; + [JsonProperty("name")] public string Name; + [JsonProperty("body")] public string Body; + [JsonProperty("url")] public string Url; + [JsonProperty("draft")] public bool Draft; + [JsonProperty("prerelease")] public bool PreRelease; + [JsonProperty("created_at")] public DateTime CreatedAt; + [JsonProperty("published_at")] public DateTime PublishedAt; + [JsonProperty("author")] public Author Author; + [JsonProperty("assets")] public List Assets; + } +} \ No newline at end of file diff --git a/NScript/Gitea/ReleaseUtility.cs b/NScript/Gitea/ReleaseUtility.cs new file mode 100644 index 0000000..812053c --- /dev/null +++ b/NScript/Gitea/ReleaseUtility.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using Newtonsoft.Json; + +namespace NScript.Gitea +{ + public class ReleaseUtility + { + public static List GetReleases(string owner, string repo, + string apiUrl = "https://git.exmods.org/api/v1") + { + string fullUrl = $"{apiUrl}/repos/{owner}/{repo}/releases"; + WebRequest req = WebRequest.Create(fullUrl); + req.Method = "GET"; + HttpWebResponse resp = (HttpWebResponse) req.GetResponse(); + StreamReader body = new StreamReader(resp.GetResponseStream()); + string respBody = body.ReadToEnd(); + resp.Close(); + return JsonConvert.DeserializeObject>(respBody); + } + + public static byte[] DownloadAsset(Asset asset) + { + return DownloadAsset(asset.BrowserDownloadUrl); + } + + public static byte[] DownloadAsset(string url) + { + WebRequest req = WebRequest.Create(url); + req.Method = "GET"; + HttpWebResponse resp = (HttpWebResponse) req.GetResponse(); + BinaryReader body = new BinaryReader(resp.GetResponseStream()); + byte[] respBody = body.ReadBytes(int.MaxValue); + resp.Close(); + return respBody; + } + + public static Release? FindRelease(List releases, bool prerelease = false, bool draft = false) + { + foreach (Release release in releases) + { + if (release.PreRelease == prerelease && release.Draft == draft) return release; + } + + return null; + } + + public static Asset? FindAsset(Release release, string name) + { + return FindAsset(release.Assets, name); + } + + public static Asset? FindAsset(List assets, string name) + { + foreach (var asset in assets) + { + if (string.Equals(asset.Name, name, StringComparison.InvariantCulture)) return asset; + } + + return null; + } + } +} \ No newline at end of file diff --git a/NScript/Install/IInstallable.cs b/NScript/Install/IInstallable.cs new file mode 100644 index 0000000..6606824 --- /dev/null +++ b/NScript/Install/IInstallable.cs @@ -0,0 +1,11 @@ +namespace NScript.Install +{ + public interface IInstallable + { + string Name { get; } + + void Install(); + + bool IsInstalled(); + } +} \ No newline at end of file diff --git a/NScript/Install/InstallTools.cs b/NScript/Install/InstallTools.cs new file mode 100644 index 0000000..0db380a --- /dev/null +++ b/NScript/Install/InstallTools.cs @@ -0,0 +1,26 @@ +using System.IO; +using NScript.Filmscript; + +namespace NScript.Install +{ + public static class InstallTools + { + public static bool NativeDllExists(string name) + { + return File.Exists(NativeDllFilepath(name)); + } + + public static string NativeDllFilepath(string name = "") + { + return "TechbloxPreview_Data/Plugins/x86_64/" + name; + } + + public static Installer BuildDefaultInstaller() + { + return new Installer(new IInstallable[] + { + new FilmscriptInstaller(), + }); + } + } +} \ No newline at end of file diff --git a/NScript/Install/Installer.cs b/NScript/Install/Installer.cs new file mode 100644 index 0000000..edde2f4 --- /dev/null +++ b/NScript/Install/Installer.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace NScript.Install +{ + public class Installer + { + private List _toInstall; + + private bool _isRunning = false; + + private bool _isCompleted = false; + + public bool Completed => _isCompleted; + + private string _currentItemName = null; + + public string CurrentlyInstalling => _currentItemName; + + private Thread _worker = null; + + public Installer() + { + _toInstall = new List(); + } + + public Installer(IEnumerable toInstall) + { + _toInstall = new List(toInstall); + } + + public void AddInstallable(IInstallable i) + { + if (_isRunning) throw new Exception("Cannot add IInstallable when already installing"); + _toInstall.Add(i); + } + + public void InstallEverything() + { + if (_worker != null) _worker.Join(); + _worker = new Thread(InstallWorker); + _worker.Start(); + } + + public void WaitForCompletion() + { + if (_worker != null) _worker.Join(); + _worker = null; + } + + private void InstallWorker() + { + _isRunning = true; + foreach (var i in _toInstall) + { + _currentItemName = i.Name; + if (!i.IsInstalled()) + { + try + { + i.Install(); + } + catch (Exception e) + { + GamecraftModdingAPI.Utility.Logging.LogWarning( + $"Failed to install {_currentItemName}: {e.Message}"); + } + } + } + _isRunning = false; + _isCompleted = true; + _currentItemName = null; + } + } +} \ No newline at end of file diff --git a/NScript/NScript.csproj b/NScript/NScript.csproj index 1b417eb..116eac9 100644 --- a/NScript/NScript.csproj +++ b/NScript/NScript.csproj @@ -1083,4 +1083,13 @@ + + diff --git a/NScript/NScriptPlugin.cs b/NScript/NScriptPlugin.cs index b941338..74c1bf1 100644 --- a/NScript/NScriptPlugin.cs +++ b/NScript/NScriptPlugin.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Reflection; using IllusionPlugin; @@ -16,6 +18,8 @@ namespace NScript private HarmonyLib.Harmony instance; + private Install.Installer _installer = null; + // called when Gamecraft shuts down public override void OnApplicationQuit() { @@ -37,14 +41,18 @@ namespace NScript instance.PatchAll(Assembly.GetExecutingAssembly()); // load external libraries - try + _installer = Install.InstallTools.BuildDefaultInstaller(); + _installer.InstallEverything(); + // TODO wait for this later, since these resources are usually only used when in a game + // (i.e. there's no need to prevent the main menu from loading just to download this stuff) + _installer.WaitForCompletion(); + if (Filmscript.GeneralBindings.DllExists()) { - string filmscriptVersion = Filmscript.GeneralBindings.Version(); - GamecraftModdingAPI.Utility.Logging.MetaLog($"filmscript.dll {filmscriptVersion}"); + GamecraftModdingAPI.Utility.Logging.MetaLog($"filmscript.dll {Filmscript.GeneralBindings.Version()}"); } - catch (DllNotFoundException e) + else { - GamecraftModdingAPI.Utility.Logging.MetaLog($"Failed to find filmscript DLL: {e.Message} ({e.TypeName} | {e.Source})"); + GamecraftModdingAPI.Utility.Logging.MetaLog($"Failed to find filmscript DLL"); } // Initialize this mod @@ -71,6 +79,11 @@ namespace NScript CameraPatch.AllowDefaultBehaviour = !CameraPatch.AllowDefaultBehaviour; CameraEngine.useDefaultBehaviour = !CameraEngine.useDefaultBehaviour; } + + if (_installer != null && _installer.CurrentlyInstalling != null) + { + UnityEngine.GUILayout.Box($"Downloading {_installer.CurrentlyInstalling}..."); + } } } }