Skip to content

Commit

Permalink
Merge pull request #784 from openmod/assembly_resources_fix
Browse files Browse the repository at this point in the history
Assembly resources fix
  • Loading branch information
Trojaner committed Nov 3, 2023
2 parents 81024c3 + 8e717a5 commit 2ca1e24
Show file tree
Hide file tree
Showing 15 changed files with 133 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public class OpenModDynamicBootstrapper
{
logger.LogInformation($"Downloading {latestPackageIdentity!.Id} v{latestPackageIdentity!.Version} via NuGet");
var installResult = await packageManager.InstallAsync(latestPackageIdentity!, allowPrereleaseVersions);
if (installResult.Code == NuGetInstallCode.Success || installResult.Code == NuGetInstallCode.NoUpdatesFound)
if (installResult.Code is NuGetInstallCode.Success or NuGetInstallCode.NoUpdatesFound)
{
packageIdentity = installResult.Identity;
logger.LogInformation(installResult.Code == NuGetInstallCode.Success
Expand Down
22 changes: 5 additions & 17 deletions framework/OpenMod.Common/Hotloading/Hotloader.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using AsmResolver;
using AsmResolver.IO;
using AsmResolver.IO;
using AsmResolver.PE;
using AsmResolver.PE.DotNet;
using AsmResolver.PE.DotNet.Builder;
Expand Down Expand Up @@ -160,18 +159,14 @@ public static void Remove(Assembly assembly)
/// <returns><b>The hotloaded assembly</b> if found; otherwise, <b>null</b>.</returns>
public static Assembly? FindAssembly(AssemblyName name)
{
if (s_Assemblies.TryGetValue(name, out var assembly))
{
return assembly;
}

return null;
return s_Assemblies.TryGetValue(name, out var assembly) ? assembly : null;
}

/// <summary>
/// Gets all hotloaded assemblies.
/// </summary>
/// <returns>The hotloaded assemblies.</returns>
// ReSharper disable once UnusedMember.Global
public static IReadOnlyCollection<Assembly> GetHotloadedAssemblies()
{
return s_Assemblies.Values;
Expand All @@ -184,15 +179,8 @@ public static IReadOnlyCollection<Assembly> GetHotloadedAssemblies()
/// <returns><b>The real assembly name</b> of the hotloaded assembly. If the given assembly was not hotloaded, it will return <b>the assembly's name</b>.</returns>
public static AssemblyName GetRealAssemblyName(Assembly assembly)
{
foreach (var kv in s_Assemblies)
{
if (kv.Value == assembly)
{
return kv.Key;
}
}

return assembly.GetName();
var assemblyName = s_Assemblies.FirstOrDefault(kv => kv.Value == assembly).Key;
return assemblyName ?? assembly.GetName();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ protected override async Task OnExecuteAsync()
await PrintAsync(
$"No updates found for {result.Identity!.Id}.", Color.DarkGreen);
break;
case NuGetInstallCode.InvalidVersion:
await PrintAsync($"Failed to install \"{packageName}@{packageVersion}\": " + result.Code, Color.DarkRed);
break;
default:
await PrintAsync($"Failed to install \"{packageName}\": " + result.Code, Color.DarkRed);
break;
Expand Down
47 changes: 21 additions & 26 deletions framework/OpenMod.Core/Helpers/AssemblyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,63 +23,58 @@ public static void CopyAssemblyResources(Assembly assembly, string? baseDir, boo
baseDir ??= string.Empty;

var resourceNames = assembly.GetManifestResourceNames();

if (resourceNames.Length > 0 && !Directory.Exists(baseDir))
{
Directory.CreateDirectory(baseDir);
}

var assemblyName = Hotloader.GetRealAssemblyName(assembly);
var assemblyNameDot = $"{assemblyName.Name}.";
var assemblyNameDotRegex = new Regex(Regex.Escape(assemblyNameDot));

Log.Debug("Found {Length} resources for {AssemblyName}.", resourceNames.Length, assemblyName.Name);

foreach (var resourceName in resourceNames)
{
if (resourceName.EndsWith("..directory"))
{
continue;
}

var assemblyName = Hotloader.GetRealAssemblyName(assembly);

if (!resourceName.Contains(assemblyName.Name + "."))
if (!resourceName.Contains(assemblyNameDot))
{
Log.Warning(
"{ResourceName} does not contain assembly name in assembly: {AssemblyName}. <AssemblyName> and <RootNamespace> must be equal inside your plugins .csproj file",
resourceName, assemblyName.Name);
}

var regex = new Regex(Regex.Escape(assemblyName.Name + "."));
var fileName = regex.Replace(resourceName, string.Empty, 1);

var fileName = assemblyNameDotRegex.Replace(resourceName, string.Empty, 1);
if (s_ExcludedResources.Contains(fileName))
{
continue;
}

var parts = fileName.Split('.');
fileName = "";
Log.Debug("Working on {ResourceName} resource with name {FileName}.", resourceName, fileName);

var fileNameParts = fileName.Split('.');

var pathSb = new StringBuilder(assemblyName.Name + ".");
foreach (var part in parts)
var fileNameSb = new StringBuilder();
var pathSb = new StringBuilder(assemblyNameDot);

foreach (var part in fileNameParts)
{
pathSb.Append(part + ".");
using var tmpStream = assembly.GetManifestResourceStream(pathSb + ".directory");
var partDot = $"{part}.";
pathSb.Append(partDot);

using var tmpStream = assembly.GetManifestResourceStream(pathSb + ".directory");
var isDirectory = tmpStream != null;
if (isDirectory)
{
fileName += part + Path.DirectorySeparatorChar;
}
else
{
fileName += part + ".";
}
fileNameSb.Append(isDirectory ? $"{part}{Path.DirectorySeparatorChar}" : partDot);
}

if (fileName.EndsWith("."))
{
fileName = fileName.Substring(0, fileName.Length - 1);
}
fileName = fileNameSb[^1] == '.' ? fileNameSb.ToString(0, fileNameSb.Length - 1) : fileNameSb.ToString();

Log.Debug("Resource {ResourceName} with name {FileName} after checks.", resourceName, fileName);
var directory = Path.GetDirectoryName(fileName);

if (directory != null)
{
directory = Path.Combine(baseDir, directory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public virtual async Task<ICollection<Assembly>> LoadPluginAssembliesAsync()

public Task<NuGetInstallResult> InstallPackageAsync(string packageName, string? version = null, bool isPreRelease = false)
{
return InstallOrUpdateAsync(packageName, version, isPreRelease, false);
return InstallOrUpdateAsync(packageName, version, isPreRelease);
}

public Task<NuGetInstallResult> UpdatePackageAsync(string packageName, string? version = null, bool isPreRelease = false)
Expand Down Expand Up @@ -79,15 +79,21 @@ private async Task<NuGetInstallResult> InstallOrUpdateAsync(string packageName,
return new NuGetInstallResult(NuGetInstallCode.PackageOrVersionNotFound);
}

NuGetVersion? nuGetVersion = null;
if (version is not null && !NuGetVersion.TryParse(version, out nuGetVersion))
{
return new NuGetInstallResult(NuGetInstallCode.InvalidVersion);
}

var package = await m_NuGetPackageManager.QueryPackageExactAsync(packageName, version, isPreRelease);
if (package == null)
{
return new NuGetInstallResult(NuGetInstallCode.PackageOrVersionNotFound);
}

var packageIdentity = version is null
? package.Identity
: new PackageIdentity(package.Identity.Id, new NuGetVersion(version));
: new PackageIdentity(package.Identity.Id, nuGetVersion ?? new NuGetVersion(version));// ?? new NuGetVersion(version) -> Fallback just in case

var result = await m_NuGetPackageManager.InstallAsync(packageIdentity, isPreRelease);
if (result.Code is not NuGetInstallCode.Success and not NuGetInstallCode.NoUpdatesFound)
Expand Down
16 changes: 13 additions & 3 deletions framework/OpenMod.Core/Plugins/PluginAssemblyStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class PluginAssemblyStore : IPluginAssemblyStore, IDisposable
private readonly NuGetPackageManager m_NuGetPackageManager;

/// <summary>
/// Defines if openmod would try to install missing dependencies.
/// Defines if OpenMod would try to install missing dependencies.
/// </summary>
public static bool TryInstallMissingDependencies { get; set; }

Expand Down Expand Up @@ -63,9 +63,18 @@ public IReadOnlyCollection<Assembly> LoadedPluginAssemblies
{
var packagesResourceName = assembly.GetManifestResourceNames()
.FirstOrDefault(x => x.EndsWith("packages.yaml"));
if (packagesResourceName == null) return newPlugins;
if (packagesResourceName == null)
return newPlugins;

var packagesRosourceInfo = assembly.GetManifestResourceInfo(packagesResourceName);
m_Logger.LogDebug("Found packages.yaml with name {PackagesResourceName}.", packagesResourceName);
m_Logger.LogDebug("Package resource info {PackagesRosourceInfo}.", packagesRosourceInfo);

#if NETSTANDARD2_1_OR_GREATER
await using var stream = assembly.GetManifestResourceStream(packagesResourceName);
#else
using var stream = assembly.GetManifestResourceStream(packagesResourceName);
#endif
if (stream == null) return newPlugins;

using var reader = new StreamReader(stream);
Expand All @@ -91,6 +100,7 @@ public IReadOnlyCollection<Assembly> LoadedPluginAssemblies

var newAssemblies = m_NuGetPackageManager.GetLoadedAssemblies().Except(existingPackages);

// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var newAssembly in newAssemblies.Select(x => (Assembly)x.Assembly.Target))
{
if (newAssembly == null) continue;
Expand Down Expand Up @@ -171,7 +181,7 @@ public async Task<ICollection<Assembly>> LoadPluginAssembliesAsync(IPluginAssemb
continue;
}

if (types.Any(d => d.GetInterfaces().Any(x => x == typeof(IOpenModPlugin)) && !d.IsAbstract && d.IsClass))
if (types.Any(d => d.GetInterfaces().Any(x => x == typeof(IOpenModPlugin)) && d is { IsAbstract: false, IsClass: true }))
{
continue;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using HarmonyLib;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using OpenMod.Common.Hotloading;
using OpenMod.NuGet;
using System;
Expand Down Expand Up @@ -40,26 +39,25 @@ public sealed class PomeloMySqlConnectorResolver

logger.LogDebug("Loaded MySqlConnector v0.69 into Hotloader");

var nuGetResolverInstalled =
AccessTools.FieldRefAccess<NuGetPackageManager, bool>("m_AssemblyResolverInstalled")(
nuGetPackageManager);

if (nuGetResolverInstalled)
if (!nuGetPackageManager.AssemblyResolverInstalled)
{
var assemblyResolveMethod = typeof(NuGetPackageManager).GetMethod("OnAssemblyResolve",
BindingFlags.NonPublic | BindingFlags.Instance);
return;
}

var assemblyResolveMethod = typeof(NuGetPackageManager).GetMethod("OnAssemblyResolve",
BindingFlags.NonPublic | BindingFlags.Instance);

if (assemblyResolveMethod == null)
{
logger.LogCritical($"Couldn't find OnAssemblyResolve method for {nameof(NuGetPackageManager)}!");
}
else
{
var @delegate = (ResolveEventHandler)assemblyResolveMethod.CreateDelegate(typeof(ResolveEventHandler), nuGetPackageManager);
if (assemblyResolveMethod == null)
{
logger.LogCritical($"Couldn't find OnAssemblyResolve method for {nameof(NuGetPackageManager)}!");
}
else
{
var @delegate = (ResolveEventHandler)assemblyResolveMethod.CreateDelegate(typeof(ResolveEventHandler), nuGetPackageManager);

AppDomain.CurrentDomain.AssemblyResolve -= @delegate;
AppDomain.CurrentDomain.AssemblyResolve += @delegate;
}
AppDomain.CurrentDomain.AssemblyResolve -= @delegate;
AppDomain.CurrentDomain.AssemblyResolve += @delegate;
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion framework/OpenMod.NuGet/NuGetInstallCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public enum NuGetInstallCode
{
Success,
NoUpdatesFound,
PackageOrVersionNotFound
PackageOrVersionNotFound,
InvalidVersion
}
}
1 change: 1 addition & 0 deletions framework/OpenMod.NuGet/NuGetPackageManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class NuGetPackageManager : IDisposable
private static readonly string[] s_PublisherBlacklist = new string[] { };

private static readonly ConcurrentDictionary<string, List<Assembly>> s_LoadedPackages = new();
public bool AssemblyResolverInstalled => m_AssemblyResolverInstalled;
private bool m_AssemblyResolverInstalled;
private AssemblyLoader m_AssemblyLoader;

Expand Down

0 comments on commit 2ca1e24

Please sign in to comment.