@@ -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<Gitea.Release> 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); | |||
} | |||
} | |||
} |
@@ -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(); | |||
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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<Asset> Assets; | |||
} | |||
} |
@@ -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<Release> 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<List<Release>>(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<Release> 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<Asset> assets, string name) | |||
{ | |||
foreach (var asset in assets) | |||
{ | |||
if (string.Equals(asset.Name, name, StringComparison.InvariantCulture)) return asset; | |||
} | |||
return null; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
namespace NScript.Install | |||
{ | |||
public interface IInstallable | |||
{ | |||
string Name { get; } | |||
void Install(); | |||
bool IsInstalled(); | |||
} | |||
} |
@@ -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(), | |||
}); | |||
} | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Threading; | |||
namespace NScript.Install | |||
{ | |||
public class Installer | |||
{ | |||
private List<IInstallable> _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<IInstallable>(); | |||
} | |||
public Installer(IEnumerable<IInstallable> toInstall) | |||
{ | |||
_toInstall = new List<IInstallable>(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; | |||
} | |||
} | |||
} |
@@ -1083,4 +1083,13 @@ | |||
</ItemGroup> | |||
<!--End Dependencies--> | |||
<!--<ItemGroup> | |||
<PackageReference Include="ILRepack" Version="2.0.18" /> | |||
<PackageReference Include="Gitea.API" Version="1.0.20" /> | |||
</ItemGroup> | |||
<Target Name="StaticLinkMergeAfterBuild" AfterTargets="Build"> | |||
<Exec Command="$(PkgILRepack)\tools\ILRepack.exe /ndebug /parallel /lib:..\ref\TechbloxPreview_Data\Managed\ /lib:..\ref\Plugins\ /lib:..\..\ref\TechbloxPreview_Data\Managed\ /lib:..\..\ref\Plugins\ /lib:bin\$(Configuration)\net472\ /out:bin\$(Configuration)\net472\FilmScript.dll bin\$(Configuration)\net472\NScript.dll bin\$(Configuration)\net472\Gitea.API.dll" /> | |||
</Target>--> | |||
</Project> |
@@ -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}..."); | |||
} | |||
} | |||
} | |||
} |