diff --git a/src/Extensions.cs b/src/Extensions.cs index 9b08cbf..ea746dd 100644 --- a/src/Extensions.cs +++ b/src/Extensions.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using OpenMod.Installer.RocketMod.Jobs; namespace OpenMod.Installer.RocketMod @@ -19,5 +20,14 @@ public static string[] GetPreventionCommands(this Type type) var attributes = type.GetCustomAttributes(); return attributes.Select(x => x.CommandToPrevent).ToArray(); } + + private static readonly Regex VersionRegex = new Regex("Version=(?.+?), ", RegexOptions.Compiled); + + public static string GetVersionIndependentName(string fullAssemblyName, out string extractedVersion) + { + var match = VersionRegex.Match(fullAssemblyName); + extractedVersion = match.Groups[1].Value; + return VersionRegex.Replace(fullAssemblyName, string.Empty); + } } } \ No newline at end of file diff --git a/src/Helpers/AsyncHelper.cs b/src/Helpers/AsyncHelper.cs new file mode 100644 index 0000000..38f4233 --- /dev/null +++ b/src/Helpers/AsyncHelper.cs @@ -0,0 +1,14 @@ +using Nito.AsyncEx; +using System; +using System.Threading.Tasks; + +namespace OpenMod.Installer.RocketMod.Helpers +{ + public static class AsyncHelper + { + public static void RunSync(Func func) + { + AsyncContext.Run(func); + } + } +} diff --git a/src/Helpers/NuGetHelper.cs b/src/Helpers/NuGetHelper.cs new file mode 100644 index 0000000..834351a --- /dev/null +++ b/src/Helpers/NuGetHelper.cs @@ -0,0 +1,36 @@ +using OpenMod.NuGet; +using System; + +namespace OpenMod.Installer.RocketMod.Helpers +{ + public static class NuGetHelper + { + private static NuGetPackageManager NuGetPackageManager { get; set; } + public static NuGetPackageManager GetNuGetPackageManager() + { + if (NuGetPackageManager != null) + { + return NuGetPackageManager; + } + + var path = OpenModInstallerPlugin.Instance.OpenModManager.PackagesDirectory; + Environment.SetEnvironmentVariable("NUGET_COMMON_APPLICATION_DATA", path); + + // NuGetPackageManager should auto create directory + NuGetPackageManager = new NuGetPackageManager(path) + { + Logger = new NuGetConsoleLogger() + }; + + // these dependencies do not exist on NuGet and create warnings + // they are not required + NuGetPackageManager.IgnoreDependencies( + "Microsoft.NETCore.Platforms", + "Microsoft.Packaging.Tools", + "NETStandard.Library", + "System.IO.FileSystem.Watcher"); + + return NuGetPackageManager; + } + } +} diff --git a/src/Jobs/AssemblyLoadJob.cs b/src/Jobs/AssemblyLoadJob.cs new file mode 100644 index 0000000..de05ec2 --- /dev/null +++ b/src/Jobs/AssemblyLoadJob.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; + +namespace OpenMod.Installer.RocketMod.Jobs +{ + public class AssemblyLoadJob : IJob, IRevertable + { + private static bool _assemblyResolveInstalled; + private static readonly Dictionary _loadedAssembles = new Dictionary(); + + public void ExecuteMigration() + { + if (!_assemblyResolveInstalled) + { + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + _assemblyResolveInstalled = true; + } + + foreach (var file in Directory.GetFiles(OpenModInstallerPlugin.Instance.OpenModManager.WorkingDirectory)) + { + if (file.EndsWith(".dll")) + { + var dllPath = Path.Combine(OpenModInstallerPlugin.Instance.OpenModManager.WorkingDirectory, file); + var asm = Assembly.Load(File.ReadAllBytes(dllPath)); + + var name = Extensions.GetVersionIndependentName(asm.FullName, out _); + if (_loadedAssembles.ContainsKey(name)) + { + continue; + } + + _loadedAssembles.Add(name, asm); + } + } + } + + private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + var name = Extensions.GetVersionIndependentName(args.Name, out _); + if (_loadedAssembles.ContainsKey(name)) + { + return _loadedAssembles[name]; + } + return null; + } + + public void Revert() + { + if (_assemblyResolveInstalled) + { + _loadedAssembles.Clear(); + AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve; + _assemblyResolveInstalled = false; + } + } + } +} diff --git a/src/Jobs/NuGetInstallJob.cs b/src/Jobs/NuGetInstallJob.cs index b7b0856..e6a8bae 100644 --- a/src/Jobs/NuGetInstallJob.cs +++ b/src/Jobs/NuGetInstallJob.cs @@ -1,8 +1,16 @@ -namespace OpenMod.Installer.RocketMod.Jobs +using OpenMod.Installer.RocketMod.Helpers; +using OpenMod.NuGet; +using Rocket.Core.Logging; +using System; +using System.IO; +using System.Threading.Tasks; + +namespace OpenMod.Installer.RocketMod.Jobs { - public abstract class NuGetInstallJob : IJob + public abstract class NuGetInstallJob : IJob, IRevertable { private readonly string _packageId; + private string _packageDirectory; protected NuGetInstallJob(string packageId) { @@ -11,7 +19,41 @@ protected NuGetInstallJob(string packageId) public void ExecuteMigration() { - throw new System.NotImplementedException(); + AsyncHelper.RunSync(DownloadPackage); + } + + private async Task DownloadPackage() + { + var nuGetPackageManager = NuGetHelper.GetNuGetPackageManager(); + + const bool allowPrereleaseVersions = false; + + var pluginPackage = await nuGetPackageManager.QueryPackageExactAsync(_packageId, includePreRelease: allowPrereleaseVersions); + if (pluginPackage == null) + { + Logger.Log($"Downloading has failed for {_packageId}: {NuGetInstallCode.PackageOrVersionNotFound}"); + return; + } + var installResult = await nuGetPackageManager.InstallAsync(pluginPackage.Identity, allowPrereleaseVersions); + if (installResult.Code == NuGetInstallCode.Success) + { + _packageDirectory = Path.Combine(OpenModInstallerPlugin.Instance.OpenModManager.PackagesDirectory, + installResult.Identity.ToString()); + Logger.Log($"Finished downloading \"{_packageId}\"."); + } + else + { + Logger.Log($"Downloading has failed for {pluginPackage.Identity.Id} v{pluginPackage.Identity.Version.OriginalVersion}: {installResult.Code}"); + } + } + + public void Revert() + { + if (string.IsNullOrEmpty(_packageDirectory) || !Directory.Exists(_packageDirectory)) + { + return; + } + Directory.Delete(_packageDirectory, true); } } } \ No newline at end of file diff --git a/src/Jobs/OpenModModuleInstallJob.cs b/src/Jobs/OpenModModuleInstallJob.cs index 2849e72..dcead2e 100644 --- a/src/Jobs/OpenModModuleInstallJob.cs +++ b/src/Jobs/OpenModModuleInstallJob.cs @@ -1,7 +1,6 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using OpenMod.Installer.RocketMod.Models; using Rocket.Core.Logging; -using SDG.Unturned; using System; using System.IO; using System.IO.Compression; @@ -13,46 +12,46 @@ public class OpenModModuleInstallJob : IJob, IRevertable { public void ExecuteMigration() { - var webClient = new WebClient(); + using var webClient = new WebClient(); webClient.Headers.Add("User-Agent", "request"); - var releaseData = webClient.DownloadString("https://api.github.com/repos/openmod/openmod/releases/latest"); - if (string.IsNullOrEmpty(releaseData)) - { - throw new NullReferenceException("GitHub API returns empty request"); - } - var release = JsonConvert.DeserializeObject(releaseData); - var downloadLink = release.Assets.Find(x => x.BrowserDownloadUrl.Contains("OpenMod.Unturned.Module"))?.BrowserDownloadUrl; - if (downloadLink == null) + + //this can never be empty UNLESS troja posts a release without Module + var moduleAsset = release.Assets.Find(x => x.BrowserDownloadUrl.Contains("OpenMod.Unturned.Module")); + Logger.Log($"Downloading {moduleAsset.AssetName}"); + var dataZip = webClient.DownloadData(moduleAsset.BrowserDownloadUrl); + Logger.Log("Extracting.."); + var modulesDirectory = OpenModInstallerPlugin.Instance.OpenModManager.WorkingDirectory; + ExtractArchive(dataZip, modulesDirectory); + Logger.Log("Successfully installed OpenMod module."); + } + + //There can be a long talk about this made to be universal while actually not being universal. + public void ExtractArchive(byte[] archive, string directory) + { + if (!Directory.Exists(directory)) { - throw new NullReferenceException(nameof(downloadLink)); + Directory.CreateDirectory(directory); } - Logger.Log($"Downloading OpenMod.Unturned.Module.."); - var dataZip = webClient.DownloadData(downloadLink); - - Logger.Log("Extracting.."); - using MemoryStream stream = new MemoryStream(dataZip); + // ZipStorer dispose is broken, so using instead memoryStream dispose + using var stream = new MemoryStream(archive); var zip = ZipStorer.Create(stream); foreach (var file in zip.ReadCentralDir()) { - var path = Path.Combine(OpenModInstallerPlugin.Instance.OpenModManager.WorkingDirectory, - Path.GetFileName(file.FilenameInZip)); + //We dont want to leave the readme in the Modules folder do we? + if (file.FilenameInZip == "Readme.txt") + continue; + var path = Path.Combine(directory, Path.GetFileName(file.FilenameInZip)); zip.ExtractFile(file, path); } - - var rocketModulePath = Path.Combine(ReadWrite.PATH, "Modules", "Rocket.Unturned", "Rocket.Unturned.module"); - var renamedRocketModulePath = Path.Combine(ReadWrite.PATH, "Modules", "Rocket.Unturned", "Rocket.Unturned.module.bak"); - if(File.Exists(rocketModulePath)) - { - File.Move(rocketModulePath, renamedRocketModulePath); - } - Logger.Log("Successfully installed OpenMod module."); } public void Revert() { + if (Directory.Exists(OpenModInstallerPlugin.Instance.OpenModManager.WorkingDirectory)) + Directory.Delete(OpenModInstallerPlugin.Instance.OpenModManager.WorkingDirectory, true); } } } \ No newline at end of file diff --git a/src/Jobs/OpenModPackagesInstallJobs/OpenModUnityEngineInstallJob.cs b/src/Jobs/OpenModPackagesInstallJobs/OpenModUnityEngineInstallJob.cs new file mode 100644 index 0000000..f65fb2b --- /dev/null +++ b/src/Jobs/OpenModPackagesInstallJobs/OpenModUnityEngineInstallJob.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenMod.Installer.RocketMod.Jobs.OpenModPackagesInstallJobs +{ + public class OpenModUnityEngineInstallJob : NuGetInstallJob + { + public OpenModUnityEngineInstallJob() : base("OpenMod.UnityEngine") + { + } + } +} diff --git a/src/Jobs/OpenModPackagesInstallJobs/OpenModUnturnedInstallJob.cs b/src/Jobs/OpenModPackagesInstallJobs/OpenModUnturnedInstallJob.cs new file mode 100644 index 0000000..85d3e86 --- /dev/null +++ b/src/Jobs/OpenModPackagesInstallJobs/OpenModUnturnedInstallJob.cs @@ -0,0 +1,9 @@ +namespace OpenMod.Installer.RocketMod.Jobs.OpenModPackagesInstallJobs +{ + public class OpenModUnturnedInstallJob : NuGetInstallJob + { + public OpenModUnturnedInstallJob() : base("OpenMod.Unturned") + { + } + } +} diff --git a/src/Jobs/OpenModUconomyToOpenModInstallJob.cs b/src/Jobs/OpenModUconomyToOpenModInstallJob.cs index 55ed68e..f4fdb75 100644 --- a/src/Jobs/OpenModUconomyToOpenModInstallJob.cs +++ b/src/Jobs/OpenModUconomyToOpenModInstallJob.cs @@ -3,7 +3,7 @@ [Prevent("--no-uconomy-link")] public class OpenModUconomyToOpenModInstallJob : NuGetInstallJob { - public OpenModUconomyToOpenModInstallJob() : base("OpenMod.Unturned.UconomyToOpenMod") + public OpenModUconomyToOpenModInstallJob() : base("OpenMod.UconomyToOpenMod") { } } diff --git a/src/Jobs/RocketModUninstallJob.cs b/src/Jobs/RocketModUninstallJob.cs index 9492a43..6e7ad01 100644 --- a/src/Jobs/RocketModUninstallJob.cs +++ b/src/Jobs/RocketModUninstallJob.cs @@ -1,10 +1,5 @@ using SDG.Unturned; -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OpenMod.Installer.RocketMod.Jobs { diff --git a/src/OpenMod.Installer.RocketMod.csproj b/src/OpenMod.Installer.RocketMod.csproj index 402315c..2110e04 100644 --- a/src/OpenMod.Installer.RocketMod.csproj +++ b/src/OpenMod.Installer.RocketMod.csproj @@ -7,6 +7,8 @@ + + diff --git a/src/OpenModInstallerPlugin.cs b/src/OpenModInstallerPlugin.cs index c941342..35f93a0 100644 --- a/src/OpenModInstallerPlugin.cs +++ b/src/OpenModInstallerPlugin.cs @@ -1,13 +1,12 @@ -using System; -using System.IO; -using System.Linq; -using OpenMod.Installer.RocketMod.Jobs; +using OpenMod.Installer.RocketMod.Jobs; +using OpenMod.Installer.RocketMod.Jobs.OpenModPackagesInstallJobs; using Rocket.Core.Plugins; using SDG.Unturned; -using UnityEngine; +using System; +using System.IO; +using System.Linq; // todo: main command logs -// NuGetInstallJob implementation by using OpenMod.NuGet // OpenModRocketModUninstallJob (rename RocketMod module to module.bak and rename OpenMod to .bak) // OpenModPermissionsMigrationJob (migrate permissions, only if PermissionLink will get installed) // OpenModUconomyMigrationJob (execute /migrate of OpenMod.Economy after next restart) @@ -28,11 +27,15 @@ protected override void Load() Instance = this; var openmodPath = Path.Combine(ReadWrite.PATH, "Modules", "OpenMod.Unturned"); - OpenModManager = new OpenModManager(openmodPath); + var packagesPath = Path.Combine(ReadWrite.PATH, "Servers", Provider.serverID, "OpenMod", "packages"); + OpenModManager = new OpenModManager(openmodPath, packagesPath); JobsManager = new JobsManager(); JobsManager.RegisterJob(new OpenModModuleInstallJob()); - JobsManager.RegisterJob(new OpenModRocketModUninstallJob()); + JobsManager.RegisterJob(new RocketModUninstallJob()); + JobsManager.RegisterJob(new AssemblyLoadJob()); + JobsManager.RegisterJob(new OpenModUnturnedInstallJob()); + JobsManager.RegisterJob(new OpenModUnityEngineInstallJob()); JobsManager.RegisterJob(new OpenModCooldownsInstallJob()); JobsManager.RegisterJob(new OpenModEconomyInstallJob()); JobsManager.RegisterJob(new OpenModPermissionLinkInstallJob()); diff --git a/src/OpenModManager.cs b/src/OpenModManager.cs index 3013c0b..6a9a1e3 100644 --- a/src/OpenModManager.cs +++ b/src/OpenModManager.cs @@ -2,11 +2,13 @@ { public class OpenModManager { - public OpenModManager(string workingDirectory) + public OpenModManager(string workingDirectory, string packagesDirectory) { WorkingDirectory = workingDirectory; + PackagesDirectory = packagesDirectory; } public string WorkingDirectory { get; } // gets the openmod directory + public string PackagesDirectory { get; } } } \ No newline at end of file