mirror of
https://github.com/muskit/H3VR-TNH-Quality-of-Life-Improvements.git
synced 2026-06-02 20:24:26 -07:00
update MeatKit (9a1a68ab68cd0650227af944ffa30d1166b9e056)
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BepInEx;
|
||||
using HarmonyLib;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using MethodBody = Mono.Cecil.Cil.MethodBody;
|
||||
|
||||
namespace MeatKit
|
||||
{
|
||||
public static partial class MeatKit
|
||||
{
|
||||
private const string EditorAssemblyPath = "Library/ScriptAssemblies/";
|
||||
|
||||
private static void ExportEditorAssembly(string folder, string tempFile = null,
|
||||
Dictionary<string, List<string>> requiredScripts = null)
|
||||
{
|
||||
// Make a copy of the file if we aren't already given one
|
||||
string editorAssembly = EditorAssemblyPath + AssemblyName + ".dll";
|
||||
if (!File.Exists(editorAssembly) && !File.Exists(tempFile))
|
||||
{
|
||||
throw new MeatKitBuildException("Editor assembly missing! Can't export scripts.");
|
||||
}
|
||||
if (string.IsNullOrEmpty(tempFile))
|
||||
{
|
||||
tempFile = Path.GetTempFileName();
|
||||
File.Copy(editorAssembly, tempFile, true);
|
||||
}
|
||||
|
||||
// Delete the old file
|
||||
var settings = BuildWindow.SelectedProfile;
|
||||
var exportPath = folder + settings.PackageName + ".dll";
|
||||
if (File.Exists(exportPath)) File.Delete(exportPath);
|
||||
|
||||
var rParams = new ReaderParameters
|
||||
{
|
||||
AssemblyResolver =
|
||||
new RedirectedAssemblyResolver(Path.GetDirectoryName(typeof(UnityEngine.Object).Assembly.Location), ManagedDirectory)
|
||||
};
|
||||
|
||||
// Get the MeatKitPlugin class and rename it
|
||||
using (var asm = AssemblyDefinition.ReadAssembly(tempFile, rParams))
|
||||
{
|
||||
// Locate the plugin class for this profile and set it's name and namespace
|
||||
string mainNamespace = BuildWindow.SelectedProfile.MainNamespace;
|
||||
var plugin = FindPluginClass(asm.MainModule, mainNamespace);
|
||||
BuildLog.WriteLine("Using plugin class " + plugin.FullName);
|
||||
plugin.Namespace = mainNamespace;
|
||||
plugin.Name = settings.PackageName + "Plugin";
|
||||
|
||||
// Watermark the plugin just in case it's useful to someone
|
||||
BuildLog.WriteLine("Watermarking plugin class");
|
||||
var str = asm.MainModule.TypeSystem.String;
|
||||
var descriptionAttributeConstructor = typeof(DescriptionAttribute).GetConstructor(new[] {typeof(string)});
|
||||
var descriptionAttributeRef = asm.MainModule.ImportReference(descriptionAttributeConstructor);
|
||||
var descriptionAttribute = new CustomAttribute(descriptionAttributeRef);
|
||||
descriptionAttribute.ConstructorArguments.Add(new CustomAttributeArgument(str, "Built with MeatKit"));
|
||||
plugin.CustomAttributes.Add(descriptionAttribute);
|
||||
|
||||
// This is some quantum bullshit.
|
||||
// If you don't enumerate the constructor arguments for attributes their values aren't updated correctly.
|
||||
BuildLog.WriteLine("Performing quantum bullshit");
|
||||
foreach (var x in GetAllCustomAttributes(asm).SelectMany(a => a.ConstructorArguments))
|
||||
{
|
||||
}
|
||||
|
||||
// Get the BepInPlugin attribute and replace the values in it with our own
|
||||
BuildLog.WriteLine("Applying BepInPlugin attribute params");
|
||||
var guid = settings.Author + "." + settings.PackageName;
|
||||
var pluginAttribute = plugin.CustomAttributes.First(a => a.AttributeType.Name == "BepInPlugin");
|
||||
pluginAttribute.ConstructorArguments[0] = new CustomAttributeArgument(str, guid);
|
||||
pluginAttribute.ConstructorArguments[1] = new CustomAttributeArgument(str, settings.PackageName);
|
||||
pluginAttribute.ConstructorArguments[2] = new CustomAttributeArgument(str, settings.Version);
|
||||
|
||||
// Get the LoadAssets method and make a new body for it
|
||||
BuildLog.WriteLine("Generating LoadAssets()");
|
||||
var loadAssetsMethod = plugin.Methods.First(m => m.Name == "LoadAssets");
|
||||
loadAssetsMethod.Body = new MethodBody(loadAssetsMethod);
|
||||
var il = loadAssetsMethod.Body.GetILProcessor();
|
||||
|
||||
// If we're automatically applying Harmony patches, do that now
|
||||
// This IL translates to Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "PluginGuid");
|
||||
if (settings.ApplyHarmonyPatches)
|
||||
{
|
||||
BuildLog.WriteLine(" Code to apply harmony patches");
|
||||
var assemblyGetExecutingAssembly = typeof(Assembly).GetMethod("GetExecutingAssembly");
|
||||
var harmonyCreateAndPatchALl = typeof(Harmony).GetMethod("CreateAndPatchAll", new[] {typeof(Assembly), typeof(string)});
|
||||
il.Emit(OpCodes.Call, plugin.Module.ImportReference(assemblyGetExecutingAssembly));
|
||||
il.Emit(OpCodes.Ldstr, guid);
|
||||
il.Emit(OpCodes.Call, plugin.Module.ImportReference(harmonyCreateAndPatchALl));
|
||||
il.Emit(OpCodes.Pop);
|
||||
}
|
||||
|
||||
// Let any build items insert their own code in here
|
||||
foreach (var item in settings.BuildItems)
|
||||
{
|
||||
BuildLog.WriteLine(" " + item);
|
||||
item.GenerateLoadAssets(plugin, il);
|
||||
}
|
||||
|
||||
// Insert a ret at the end so it's valid
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
// Module name needs to be changed away from Assembly-CSharp.dll because it is a reserved name.
|
||||
string newAssemblyName = settings.PackageName + ".dll";
|
||||
BuildLog.WriteLine("Renaming assembly (" + newAssemblyName + ")");
|
||||
asm.Name = new AssemblyNameDefinition(settings.PackageName, asm.Name.Version);
|
||||
asm.MainModule.Name = newAssemblyName;
|
||||
|
||||
// References to renamed unity code must be swapped out.
|
||||
BuildLog.WriteLine("Renaming assembly references");
|
||||
foreach (var ii in asm.MainModule.AssemblyReferences)
|
||||
{
|
||||
// Rename any references to the game's code
|
||||
if (ii.Name.Contains("H3VRCode-CSharp"))
|
||||
{
|
||||
var newReference = ii.Name.Replace("H3VRCode-CSharp", "Assembly-CSharp");
|
||||
BuildLog.WriteLine(" " + ii.Name + " -> " + newReference);
|
||||
ii.Name = newReference;
|
||||
}
|
||||
|
||||
// And also if we're referencing a MonoMod DLL, we need to fix reference too
|
||||
if (ii.Name.EndsWith(".mm"))
|
||||
{
|
||||
// What the name currently is:
|
||||
// Assembly-CSharp.PatchName.mm
|
||||
// What we want:
|
||||
// Assembly-CSharp
|
||||
// So just lop off anything past the second to last dot
|
||||
int idx = ii.Name.LastIndexOf('.', ii.Name.Length - 4);
|
||||
var newReference = ii.Name.Substring(0, idx);
|
||||
BuildLog.WriteLine(" " + ii.Name + " -> " + newReference);
|
||||
ii.Name = newReference;
|
||||
}
|
||||
}
|
||||
|
||||
if (BuildWindow.SelectedProfile.StripNamespaces)
|
||||
{
|
||||
// Remove types not in an allowed namespace or the global namespace
|
||||
BuildLog.WriteLine("Stripping namespaces");
|
||||
string[] allowedNamespaces = BuildWindow.SelectedProfile.GetAllAllowedNamespaces();
|
||||
List<TypeDefinition> typesToRemove = new List<TypeDefinition>();
|
||||
foreach (var type in asm.MainModule.Types)
|
||||
{
|
||||
if (type.Namespace == "" || allowedNamespaces.Any(x => type.Namespace.Contains(x)))
|
||||
continue;
|
||||
BuildLog.WriteLine(" " + type.FullName);
|
||||
typesToRemove.Add(type);
|
||||
}
|
||||
|
||||
foreach (var type in typesToRemove) asm.MainModule.Types.Remove(type);
|
||||
}
|
||||
|
||||
// Remove the same types we didn't want to import. This cannot be skipped.
|
||||
foreach (var type in StripAssemblyTypes
|
||||
.Select(x => asm.MainModule.GetType(x))
|
||||
.Where(x => x != null)) asm.MainModule.Types.Remove(type);
|
||||
|
||||
// Check if we're now missing any scripts from the export
|
||||
BuildLog.WriteLine("Checking for missing types");
|
||||
List<string> missing = new List<string>();
|
||||
string originalAssemblyName = AssemblyName + ".dll";
|
||||
if (requiredScripts != null && requiredScripts.ContainsKey(originalAssemblyName))
|
||||
{
|
||||
missing.AddRange(requiredScripts[originalAssemblyName]
|
||||
.Where(typeName => !StripAssemblyTypes.Contains(typeName) && asm.MainModule.GetType(typeName) == null));
|
||||
}
|
||||
|
||||
// If we're missing anything, fail the build.
|
||||
if (missing.Count > 0)
|
||||
{
|
||||
string missingTypes = string.Join("\n", missing.ToArray());
|
||||
throw new MeatKitBuildException(
|
||||
"Exported objects reference scripts which do not exist in the exported assembly... Did you forget to allow a namespace?\n\nMissing types:\n" +
|
||||
missingTypes, null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Save it
|
||||
asm.Write(exportPath);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
throw new MeatKitBuildException(
|
||||
"Unable to write exported scripts file. This is likely due to namespace stripping being enabled and a required namespace is not whitelisted.",
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete temp file now that we're done.
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
|
||||
private static TypeDefinition FindPluginClass(ModuleDefinition module, string mainNamespace)
|
||||
{
|
||||
// Get the default MeatKitPlugin class from the module
|
||||
var pluginClass = module.GetType("MeatKitPlugin");
|
||||
|
||||
// Try and locate any alternative plugin classes
|
||||
foreach (var type in module.Types)
|
||||
{
|
||||
// We're looking for types that extend the BaseUnityPlugin class and is in the main namespace of our mod
|
||||
if (type.IsSubtypeOf(typeof(BaseUnityPlugin)) && type.Namespace == mainNamespace)
|
||||
{
|
||||
pluginClass = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pluginClass;
|
||||
}
|
||||
|
||||
private static IEnumerable<CustomAttribute> GetAllCustomAttributes(AssemblyDefinition asm)
|
||||
{
|
||||
foreach (var type in asm.MainModule.Types)
|
||||
{
|
||||
foreach (var attrib in type.CustomAttributes) yield return attrib;
|
||||
foreach (CustomAttribute attrib in type.Methods.SelectMany(method => method.CustomAttributes))
|
||||
yield return attrib;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c6f57f9c2dd40b8a7eabb4f85e2e283
|
||||
timeCreated: 1617344435
|
||||
@@ -0,0 +1,292 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Mono.Cecil;
|
||||
using NStrip;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MeatKit
|
||||
{
|
||||
/// <summary>
|
||||
/// Assembly importer class to get the managed assemblies from the game into the Unity editor without
|
||||
/// the editor wanting to crash itself. Original implementation by Nolenz.
|
||||
/// https://github.com/WurstModders/WurstMod-Reloaded/blob/2e33e83284b3a9f39c8df210ad907925d1d7d9d8/WMRWorkbench/Assets/Editor/Manglers/AssemblyMangler.cs
|
||||
/// </summary>
|
||||
public static partial class MeatKit
|
||||
{
|
||||
public const string AssemblyName = "Assembly-CSharp";
|
||||
public const string AssemblyRename = "H3VRCode-CSharp";
|
||||
public const string AssemblyFirstpassName = "Assembly-CSharp-firstpass";
|
||||
public const string AssemblyFirstpassRename = "H3VRCode-CSharp-firstpass";
|
||||
|
||||
// Types we want to strip from the main Unity assembly
|
||||
public static readonly string[] StripAssemblyTypes =
|
||||
{
|
||||
// Alloy classes
|
||||
"MaterialMapChannelPackerDefinition",
|
||||
"Alloy.PackedMapDefinition",
|
||||
"Alloy.BaseTextureChannelMapping",
|
||||
"Alloy.MapChannel",
|
||||
"Alloy.TextureValueChannelMode",
|
||||
"Alloy.NormalMapChannelTextureChannelMapping",
|
||||
"Alloy.TextureImportConfig",
|
||||
"Alloy.MapTextureChannelMapping",
|
||||
"AlloyUtils",
|
||||
"Alloy.EnumExtension",
|
||||
"MinValueAttribute",
|
||||
"MaxValueAttribute",
|
||||
"AlloyEffectsManager",
|
||||
"Alloy.EnumFlagsAttribute",
|
||||
|
||||
// Bakery MonoBehaviours
|
||||
"BakeryAlwaysRender",
|
||||
"BakeryDirectLight",
|
||||
"BakeryLightmapGroup",
|
||||
"BakeryLightmapGroupSelector",
|
||||
"BakeryLightmappedPrefab",
|
||||
"BakeryLightMesh",
|
||||
"BakeryPointLight",
|
||||
"BakerySkyLight",
|
||||
"BakeryVolume",
|
||||
"BakeryVolumeReceiver",
|
||||
"BakeryVolumeTrigger",
|
||||
"BakeryProjectSettings",
|
||||
"VolumeTestScene2",
|
||||
"BakeryPackAsSingleSquare",
|
||||
"BakerySector",
|
||||
"BakerySectorCapture",
|
||||
"ftGlobalStorage",
|
||||
"ftLightmaps",
|
||||
"ftLightmapsStorage",
|
||||
"ftLocalStorage",
|
||||
|
||||
// Bakery supporting types
|
||||
"ftUniqueIDRegistry",
|
||||
"BakeryLightmapGroupPlain",
|
||||
|
||||
//Editor Tool Scripts
|
||||
"IconCamera"
|
||||
};
|
||||
|
||||
// Array of the extra assemblies that need to come with the main Unity assemblies
|
||||
private static readonly string[] ExtraAssemblies =
|
||||
{
|
||||
"DinoFracture.dll",
|
||||
"ES2.dll"
|
||||
};
|
||||
|
||||
|
||||
private static void ImportAssemblies(string assembliesDirectory, string destinationDirectory)
|
||||
{
|
||||
// Remove whatever was there before and make the folder again
|
||||
if (!Directory.Exists(destinationDirectory)) Directory.CreateDirectory(destinationDirectory);
|
||||
|
||||
// Load all of our modifiers
|
||||
var editors = Extensions.GetAllInstances<AssemblyModifier>();
|
||||
foreach (var editor in editors) editor.Applied = false;
|
||||
|
||||
// We need a custom assembly resolver that sometimes points to different directories.
|
||||
var rParams = new ReaderParameters
|
||||
{
|
||||
AssemblyResolver = new RedirectedAssemblyResolver(assembliesDirectory, destinationDirectory)
|
||||
};
|
||||
|
||||
// Rename the game's firstpass assembly
|
||||
{
|
||||
var firstpassAssembly =
|
||||
AssemblyDefinition.ReadAssembly(Path.Combine(assembliesDirectory, AssemblyFirstpassName + ".dll"));
|
||||
firstpassAssembly.Name =
|
||||
new AssemblyNameDefinition(AssemblyFirstpassRename, firstpassAssembly.Name.Version);
|
||||
firstpassAssembly.MainModule.Name = AssemblyFirstpassRename + ".dll";
|
||||
|
||||
// Apply modifications
|
||||
foreach (var editor in editors) editor.ApplyModification(firstpassAssembly);
|
||||
|
||||
// Publicize Assembly
|
||||
AssemblyStripper.MakePublic(firstpassAssembly, new string[0], false, false);
|
||||
|
||||
firstpassAssembly.Write(Path.Combine(destinationDirectory, AssemblyFirstpassRename + ".dll"));
|
||||
firstpassAssembly.Dispose();
|
||||
}
|
||||
|
||||
// Main assembly
|
||||
{
|
||||
// Rename the main assembly
|
||||
var mainAssembly =
|
||||
AssemblyDefinition.ReadAssembly(Path.Combine(assembliesDirectory, AssemblyName + ".dll"), rParams);
|
||||
mainAssembly.Name = new AssemblyNameDefinition(AssemblyRename, mainAssembly.Name.Version);
|
||||
mainAssembly.MainModule.Name = AssemblyRename + ".dll";
|
||||
|
||||
// Change the firstpass reference in this assembly
|
||||
mainAssembly.MainModule.AssemblyReferences
|
||||
.First(x => x.Name == AssemblyFirstpassName)
|
||||
.Name = AssemblyFirstpassRename;
|
||||
|
||||
// Strip some types from the assembly to prevent doubles in the editor
|
||||
foreach (var typename in StripAssemblyTypes)
|
||||
{
|
||||
var type = mainAssembly.MainModule.GetType(typename);
|
||||
if (type != null) mainAssembly.MainModule.Types.Remove(type);
|
||||
else Debug.LogWarning("Type " + typename + " was not found in assembly.");
|
||||
}
|
||||
|
||||
// Apply modifications
|
||||
foreach (var editor in editors) editor.ApplyModification(mainAssembly);
|
||||
|
||||
// Publicize assembly
|
||||
AssemblyStripper.MakePublic(mainAssembly, new string[0], false, false);
|
||||
|
||||
// Apply help URLs
|
||||
ApplyWikiHelpAttribute(mainAssembly);
|
||||
|
||||
// Write the main assembly out into the destination folder and dispose it
|
||||
mainAssembly.Write(Path.Combine(destinationDirectory, AssemblyRename + ".dll"));
|
||||
}
|
||||
|
||||
// Then lastly copy the other assemblies to the destination folder
|
||||
foreach (var file in ExtraAssemblies)
|
||||
{
|
||||
var path = Path.Combine(assembliesDirectory, file);
|
||||
if (File.Exists(path))
|
||||
ImportSingleAssembly(path, destinationDirectory);
|
||||
}
|
||||
|
||||
// Check if anything didn't apply
|
||||
foreach (var editor in editors)
|
||||
if (!editor.Applied)
|
||||
Debug.LogWarning(editor.name + " was not applied while importing.", editor);
|
||||
|
||||
// When we're done importing assemblies, let Unity refresh the asset database
|
||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "H3VR_IMPORTED");
|
||||
NormalizeMetaFileGUIDs();
|
||||
}
|
||||
|
||||
private static void ImportSingleAssembly(string assemblyPath, string destinationDirectory)
|
||||
{
|
||||
var rParams = new ReaderParameters
|
||||
{
|
||||
AssemblyResolver =
|
||||
new RedirectedAssemblyResolver(Path.GetDirectoryName(assemblyPath), destinationDirectory)
|
||||
};
|
||||
|
||||
// If this assembly uses the Assembly-CSharp name at all for any reason, replace it with H3VRCode-CSharp
|
||||
// This would probably only be done on MonoMod patches but is required to make Unity shut up
|
||||
var asm = AssemblyDefinition.ReadAssembly(assemblyPath, rParams);
|
||||
string name = asm.Name.Name;
|
||||
if (name.Contains("Assembly-CSharp"))
|
||||
{
|
||||
name = name.Replace("Assembly-CSharp", "H3VRCode-CSharp");
|
||||
asm.Name = new AssemblyNameDefinition(name, asm.Name.Version);
|
||||
asm.MainModule.Name = name + ".dll";
|
||||
}
|
||||
|
||||
// Replace all occurrences to references of Assembly-CSharp with H3VRCode-CSharp
|
||||
foreach (var reference in asm.MainModule.AssemblyReferences)
|
||||
{
|
||||
if (reference.Name.Contains("Assembly-CSharp"))
|
||||
{
|
||||
reference.Name = reference.Name.Replace("Assembly-CSharp", "H3VRCode-CSharp");
|
||||
}
|
||||
}
|
||||
|
||||
asm.Write(Path.Combine(destinationDirectory, asm.MainModule.Name));
|
||||
NormalizeMetaFileGUIDs();
|
||||
}
|
||||
|
||||
private static void ApplyWikiHelpAttribute(AssemblyDefinition asm)
|
||||
{
|
||||
// For convenience, we can add the Unity HelpURL attribute to the components from the game assembly.
|
||||
// We'll point the url at the wiki and just append the full type name at the end
|
||||
|
||||
// Iterate over every type in the assembly and just stick the attribute on it
|
||||
// Probably doesn't matter if types that don't need it have it.
|
||||
foreach (var type in asm.MainModule.Types)
|
||||
{
|
||||
// If the type doesn't already have this attribute, add it.
|
||||
if (type.CustomAttributes.Any(a => a.AttributeType.Name == "HelpURLAttribute")) continue;
|
||||
|
||||
string helpUrl = "https://h3vr-modding.github.io/wiki/docs/h3vr/" + type.FullName + ".html";
|
||||
|
||||
var str = asm.MainModule.TypeSystem.String;
|
||||
var attributeConstructor = typeof(HelpURLAttribute).GetConstructor(new[] {typeof(string)});
|
||||
var attributeRef = asm.MainModule.ImportReference(attributeConstructor);
|
||||
var attribute = new CustomAttribute(attributeRef);
|
||||
attribute.ConstructorArguments.Add(new CustomAttributeArgument(str, helpUrl));
|
||||
type.CustomAttributes.Add(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
private static void NormalizeMetaFileGUIDs()
|
||||
{
|
||||
// This is a really important step. We need to make sure that the meta files for the assemblies are generated
|
||||
// WITH THE SAME GUIDs each time. Otherwise, if you lose one and didn't have a backup, all your scripts will be missing
|
||||
// and that is of course no bueno. Unity expects 32 hexadecimal digits for the guid so we'll use md5.
|
||||
|
||||
// We need every meta file to exist already.
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
var hashFunction = MD5.Create();
|
||||
var replaceWith = new Regex(@"^guid: [0-9a-f]{32}$", RegexOptions.Multiline);
|
||||
|
||||
foreach (var metaFile in Directory.GetFiles(ManagedDirectory, "*.meta"))
|
||||
{
|
||||
// First we get the hash
|
||||
var assemblyName = Path.GetFileName(metaFile.Substring(0, metaFile.Length - 5));
|
||||
var hash = hashFunction.ComputeHash(Encoding.UTF8.GetBytes(assemblyName));
|
||||
var hexHash = Extensions.ByteArrayToString(hash).ToLower();
|
||||
|
||||
// Then we need to replace the hash in the meta file with it.
|
||||
var metaText = File.ReadAllText(metaFile);
|
||||
metaText = replaceWith.Replace(metaText, "guid: " + hexHash);
|
||||
File.WriteAllText(metaFile, metaText);
|
||||
}
|
||||
|
||||
// If anything was changed we need Unity to apply it immediately.
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assembly resolver that redirects references to another path if not found.
|
||||
/// </summary>
|
||||
private class RedirectedAssemblyResolver : BaseAssemblyResolver
|
||||
{
|
||||
private readonly DefaultAssemblyResolver _defaultResolver = new DefaultAssemblyResolver();
|
||||
private readonly string[] _redirectPaths;
|
||||
|
||||
public RedirectedAssemblyResolver(params string[] redirectPath)
|
||||
{
|
||||
_redirectPaths = redirectPath;
|
||||
}
|
||||
|
||||
public override AssemblyDefinition Resolve(AssemblyNameReference name)
|
||||
{
|
||||
AssemblyDefinition asm = null;
|
||||
try
|
||||
{
|
||||
asm = _defaultResolver.Resolve(name);
|
||||
}
|
||||
catch (AssemblyResolutionException)
|
||||
{
|
||||
foreach (var path in _redirectPaths)
|
||||
try
|
||||
{
|
||||
var asmPath = Path.Combine(path, name.Name + ".dll");
|
||||
if (File.Exists(asmPath))
|
||||
asm = AssemblyDefinition.ReadAssembly(asmPath,
|
||||
new ReaderParameters {AssemblyResolver = this});
|
||||
}
|
||||
catch (AssemblyResolutionException)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
if (asm != null) return asm;
|
||||
throw new AssemblyResolutionException(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d2f661647864f0689e2c38c7c687c99
|
||||
timeCreated: 1617325184
|
||||
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MeatKit
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public static class AssetBundleIO
|
||||
{
|
||||
// Toggles for keeping track if we're processing reads and/or writes
|
||||
public static bool ProcessingEnabledRead { get; private set; }
|
||||
public static bool ProcessingEnabledWrite { get; private set; }
|
||||
|
||||
// Output dictionaries for remembering what scripts got modified.
|
||||
public static Dictionary<string, List<string>> SerializedScriptNames { get; private set; }
|
||||
public static Dictionary<string, List<string>> DeserializedScriptNames { get; private set; }
|
||||
|
||||
private static Dictionary<string, string> _replaceMap;
|
||||
|
||||
static AssetBundleIO()
|
||||
{
|
||||
if (!EditorVersion.IsSupportedVersion) return;
|
||||
|
||||
// Apply the one hook we need here
|
||||
OrigMonoScriptTransferWrite = NativeHookManager.ApplyEditorDetour<MonoScriptTransferWrite>(EditorVersion.Current.FunctionOffsets.MonoScriptTransferWrite, new MonoScriptTransferWrite(OnMonoScriptTransferWrite));
|
||||
OrigMonoScriptTransferRead = NativeHookManager.ApplyEditorDetour<MonoScriptTransferRead>(EditorVersion.Current.FunctionOffsets.MonoScriptTransferRead, new MonoScriptTransferRead(OnMonoScriptTransferRead));
|
||||
}
|
||||
|
||||
public static void EnableProcessing(Dictionary<string, string> replaceMap, bool read, bool write)
|
||||
{
|
||||
_replaceMap = replaceMap;
|
||||
SerializedScriptNames = new Dictionary<string, List<string>>();
|
||||
DeserializedScriptNames = new Dictionary<string, List<string>>();
|
||||
ProcessingEnabledRead = read;
|
||||
ProcessingEnabledWrite = write;
|
||||
}
|
||||
|
||||
public static void DisableProcessing()
|
||||
{
|
||||
ProcessingEnabledRead = false;
|
||||
ProcessingEnabledWrite = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a detour on some of the native Unity editor code which is part of writing data to asset bundles.
|
||||
/// When Unity goes to serialize a MonoScript struct, we want to pre-process it a bit before it actually
|
||||
/// writes the data to the bundle.
|
||||
///
|
||||
/// Our processing includes building a list of used scripts so we can verify the user has their exports setup
|
||||
/// properly, as well as remapping the assembly names for some scripts so that references are maintained
|
||||
/// correctly when loaded in the game.
|
||||
/// </summary>
|
||||
private static void OnMonoScriptTransferWrite(IntPtr monoScript, IntPtr streamedBinaryWrite)
|
||||
{
|
||||
// If processing is disabled just run the original and skip.
|
||||
if (!ProcessingEnabledWrite)
|
||||
{
|
||||
OrigMonoScriptTransferWrite(monoScript, streamedBinaryWrite);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a couple variables for later
|
||||
var applied = false;
|
||||
|
||||
// Read the assembly name and class name from memory
|
||||
var className = UnityNativeHelper.ReadNativeString(monoScript, MonoScriptClassName);
|
||||
var assemblyName = UnityNativeHelper.ReadNativeString(monoScript, MonoScriptAssemblyName);
|
||||
var namespaceName = UnityNativeHelper.ReadNativeString(monoScript, MonoScriptNamespace);
|
||||
var fullName = string.IsNullOrEmpty(namespaceName) ? className : (namespaceName + "." + className);
|
||||
|
||||
// Add it to the scripts usage dictionary
|
||||
if (!SerializedScriptNames.ContainsKey(assemblyName)) SerializedScriptNames[assemblyName] = new List<string>();
|
||||
SerializedScriptNames[assemblyName].Add(fullName);
|
||||
|
||||
// Prepare some debugging string
|
||||
string debug = " " + assemblyName + " " + fullName + ": ";
|
||||
|
||||
// Check if we want to remap this assembly name
|
||||
string newAssemblyName;
|
||||
if (_replaceMap.TryGetValue(assemblyName, out newAssemblyName))
|
||||
{
|
||||
// If we're processing a type that should exist in the main game assembly, skip translation
|
||||
if (assemblyName != MeatKit.AssemblyName + ".dll" || !MeatKit.StripAssemblyTypes.Contains(fullName))
|
||||
{
|
||||
// Write the new assembly name into memory
|
||||
UnityNativeHelper.WriteNativeString(monoScript, MonoScriptAssemblyName, newAssemblyName);
|
||||
applied = true;
|
||||
debug += "ReplaceMap";
|
||||
}
|
||||
else
|
||||
{
|
||||
debug += "Ignored";
|
||||
}
|
||||
}
|
||||
|
||||
// If it didn't exist in the replace map, check if it contains H3VRCode-CSharp. This is for MonoMod assemblies.
|
||||
else if (assemblyName.Contains(MeatKit.AssemblyRename))
|
||||
{
|
||||
// Write the new assembly name into memory
|
||||
UnityNativeHelper.WriteNativeString(monoScript, MonoScriptAssemblyName, assemblyName.Replace(MeatKit.AssemblyRename, MeatKit.AssemblyName));
|
||||
applied = true;
|
||||
debug += "MonoMod";
|
||||
}
|
||||
else
|
||||
{
|
||||
debug += "Unchanged";
|
||||
}
|
||||
|
||||
BuildLog.WriteLine(debug);
|
||||
|
||||
// Let the original method run
|
||||
OrigMonoScriptTransferWrite(monoScript, streamedBinaryWrite);
|
||||
|
||||
// If we didn't apply any remapping, skip this last part.
|
||||
if (!applied) return;
|
||||
|
||||
// Cleanup by writing the original value back to memory and freeing the memory allocated for the new name.
|
||||
UnityNativeHelper.WriteNativeString(monoScript, MonoScriptAssemblyName, assemblyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Any time the editor reads from an asset bundle, we want to apply our remapping so that references from
|
||||
/// the game can be properly deserialized.
|
||||
/// </summary>
|
||||
private static long OnMonoScriptTransferRead(IntPtr monoScript, IntPtr streamedBinaryRead)
|
||||
{
|
||||
// Run the original method and return the result if processing is disabled.
|
||||
long result = OrigMonoScriptTransferRead(monoScript, streamedBinaryRead);
|
||||
if (!ProcessingEnabledRead) return result;
|
||||
|
||||
// Read the assembly name and class name from memory
|
||||
var className = UnityNativeHelper.ReadNativeString(monoScript, MonoScriptClassName);
|
||||
var assemblyName = UnityNativeHelper.ReadNativeString(monoScript, MonoScriptAssemblyName);
|
||||
var namespaceName = UnityNativeHelper.ReadNativeString(monoScript, MonoScriptNamespace);
|
||||
var fullName = string.IsNullOrEmpty(namespaceName) ? className : (namespaceName + "." + className);
|
||||
|
||||
// Add it to the scripts usage dictionary
|
||||
if (!DeserializedScriptNames.ContainsKey(assemblyName)) DeserializedScriptNames[assemblyName] = new List<string>();
|
||||
DeserializedScriptNames[assemblyName].Add(fullName);
|
||||
|
||||
// Check if we want to remap this assembly name
|
||||
string newAssemblyName;
|
||||
if (_replaceMap.TryGetValue(assemblyName, out newAssemblyName))
|
||||
{
|
||||
// If we're processing a type that should exist in the main game assembly, skip translation
|
||||
if (assemblyName != MeatKit.AssemblyName || !MeatKit.StripAssemblyTypes.Contains(fullName))
|
||||
// Write the new assembly name into memory
|
||||
UnityNativeHelper.WriteNativeString(monoScript, MonoScriptAssemblyName, newAssemblyName);
|
||||
}
|
||||
|
||||
// If it didn't exist in the replace map, check if it contains H3VRCode-CSharp. This is for MonoMod assemblies.
|
||||
else if (assemblyName.Contains(MeatKit.AssemblyName))
|
||||
{
|
||||
// Write the new assembly name into memory
|
||||
UnityNativeHelper.WriteNativeString(monoScript, MonoScriptAssemblyName, assemblyName.Replace(MeatKit.AssemblyName, MeatKit.AssemblyRename));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Actual name: MonoScript::Transfer<StreamedBinaryWrite<0>>(StreamedBinaryWrite<0> &)
|
||||
private delegate void MonoScriptTransferWrite(IntPtr monoScript, IntPtr streamedBinaryWrite);
|
||||
|
||||
private static readonly MonoScriptTransferWrite OrigMonoScriptTransferWrite;
|
||||
|
||||
// Actual name: MonoScript::Transfer<StreamedBinaryRead<1>>(StreamedBinaryRead<1> &)
|
||||
private delegate long MonoScriptTransferRead(IntPtr monoScript, IntPtr streamedBinaryRead);
|
||||
|
||||
private static readonly MonoScriptTransferRead OrigMonoScriptTransferRead;
|
||||
|
||||
private const int MonoScriptClassName = 224;
|
||||
|
||||
private const int MonoScriptNamespace = 272;
|
||||
|
||||
private const int MonoScriptAssemblyName = 320;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3994182a62444e7e97029b92adc131f5
|
||||
timeCreated: 1655415474
|
||||
Reference in New Issue
Block a user