Lots of new optimizations utilizing prepatcher
To start, this mod now requires Prepatcher and Fishery. New optimizations include: gene ticking improvements, world object improvements, hediff optimizations, world pawn gc optimizations, better mothballing, dds support, ContentFinder caching, additions and changes to reflection caching, as well as various other additions. Multiplayer support has been improved too, but should be considered highly experimental. The WorkGiver_DoBill bug is fixed in this build. Multiple patches aiming to improve RimWorld's load time have been added too.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern alias nuget;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
[PublicAPI]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.WithMembers)]
|
||||
public abstract class ClassWithFishPrepatches : SingletonFactory<ClassWithFishPrepatches>
|
||||
{
|
||||
public virtual FishPrepatchHolder Patches { get; }
|
||||
public virtual bool RequiresLoadedGameForPatching => false;
|
||||
|
||||
protected ClassWithFishPrepatches() => Patches = new(GetType());
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
public abstract class FishClassPrepatch : FishPrepatchBase
|
||||
{
|
||||
public abstract Type Type { get; }
|
||||
|
||||
public virtual void FreePatch(TypeDefinition typeDefinition)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
public abstract class FishPrepatch : FishPrepatchBase
|
||||
{
|
||||
public abstract MethodBase TargetMethodBase { get; }
|
||||
|
||||
public virtual MethodInfo? PrefixMethodInfo
|
||||
=> TryGetMethod("PREFIX", static m => m.HasAttribute<HarmonyPrefix>());
|
||||
|
||||
public virtual MethodInfo? PostfixMethodInfo
|
||||
=> TryGetMethod("POSTFIX", static m => m.HasAttribute<HarmonyPostfix>());
|
||||
|
||||
public virtual void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
{
|
||||
}
|
||||
|
||||
private MethodInfo? TryGetMethod(string nameCaseInsensitive, Predicate<MethodInfo>? predicate = null)
|
||||
=> GetType().TryGetMethod(nameCaseInsensitive, predicate);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern alias nuget;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.WithMembers)]
|
||||
public abstract class FishPrepatchBase : SingletonFactory<FishPrepatchBase>, IExposable, IHasDescription
|
||||
{
|
||||
private bool _enabled;
|
||||
|
||||
public virtual string? Description => null;
|
||||
|
||||
public virtual string? Name => null;
|
||||
|
||||
public virtual bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set => _enabled = value;
|
||||
}
|
||||
|
||||
public virtual bool DefaultState
|
||||
{
|
||||
[Pure] get => true;
|
||||
}
|
||||
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
protected FishPrepatchBase() => _enabled = DefaultState;
|
||||
|
||||
public virtual void ExposeData() => Scribe_Values.Look(ref _enabled, "enabled", DefaultState);
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern alias nuget;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Mono.Collections.Generic;
|
||||
using MonoMod.Utils;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
[PublicAPI]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
||||
public class FishPrepatchHolder : IExposable, IEnumerable<FishPrepatchBase>
|
||||
{
|
||||
public ConcurrentDictionary<Type, FishPrepatchBase> All { get; } = new();
|
||||
|
||||
public T Get<T>() where T : FishPrepatchBase => (T)All[typeof(T)];
|
||||
public void Add(FishPrepatchBase patch) => All[patch.GetType()] = patch;
|
||||
public void PatchAll(ModuleDefinition module)
|
||||
{
|
||||
var patches = All.Values;
|
||||
foreach (var patch in patches)
|
||||
{
|
||||
if (!patch.Enabled)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
switch (patch)
|
||||
{
|
||||
case FishPrepatch methodPatch:
|
||||
ApplyMethodPatch(module, methodPatch);
|
||||
break;
|
||||
case FishClassPrepatch classPatch:
|
||||
ApplyClassPatch(module, classPatch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Exception while applying prepatches for {PerformanceFishMod.NAME}:\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyMethodPatch(ModuleDefinition module, FishPrepatch patch)
|
||||
{
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
var patchMethod = patch.TargetMethodBase
|
||||
?? ThrowHelper.ThrowInvalidOperationException<MethodBase>(
|
||||
$"Patch {patch.GetType().FullName} has no target method.");
|
||||
|
||||
if (patchMethod.IsGenericMethod && patchMethod is MethodInfo info)
|
||||
patchMethod = info.GetGenericMethodDefinition();
|
||||
|
||||
var patchType = patchMethod.ReflectedType
|
||||
?? ThrowHelper.ThrowInvalidOperationException<Type>(
|
||||
$"Method {patchMethod.FullDescription()} has no declaring type.");
|
||||
|
||||
if (patchMethod.ReflectedType != patchMethod.DeclaringType)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException($"Tried to apply patch on method {
|
||||
patchMethod.Name} for type {patchMethod.ReflectedType}, but it is only declared in {
|
||||
patchMethod.DeclaringType}.");
|
||||
}
|
||||
|
||||
if (patchType.IsGenericType)
|
||||
{
|
||||
patchType = patchType.GetGenericTypeDefinition();
|
||||
patchMethod = patchMethod is MethodInfo
|
||||
? AccessTools.DeclaredMethod(patchType, patchMethod.Name, patchMethod.GetParameters().GetTypes())
|
||||
: AccessTools.Constructor(patchType, patchMethod.GetParameters().GetTypes(), patchMethod.IsStatic);
|
||||
}
|
||||
|
||||
DebugLog.Message($"Applying prepatch {patch.GetType().FullName} on method {
|
||||
patchMethod.FullDescription()}");
|
||||
|
||||
var targetType = module.GetTypeDefinition(patchType) ?? ThrowForFailureToFindType(patchType);
|
||||
var targetMethod = targetType.GetMethod(patchMethod.Name, patchMethod.GetParameters().GetTypes())
|
||||
?? ThrowForFailureToFindMethod(targetType, patchMethod);
|
||||
|
||||
// PrepatchManager.AddSecurityAttributes(targetMethod.SecurityDeclarations, module, targetMethod.FullName);
|
||||
|
||||
var ilProcessor = targetMethod.Body.GetILProcessor();
|
||||
|
||||
try
|
||||
{
|
||||
patch.Transpiler(ilProcessor, module);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Exception while applying transpiler for {patch.GetType().FullName}\n{e}\n{
|
||||
new StackTrace() /*StackTraceUtility.ExtractStackTrace()*/}");
|
||||
}
|
||||
|
||||
var patchVariables = new PatchVariables();
|
||||
|
||||
if (patch.PrefixMethodInfo is { } prefixMethod)
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplyPrefix(prefixMethod, ilProcessor, module, ref patchVariables);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Exception while applying prefix for {patch.GetType().FullName}\n{e}\n{
|
||||
new StackTrace() /*StackTraceUtility.ExtractStackTrace()*/}");
|
||||
}
|
||||
}
|
||||
|
||||
if (patch.PostfixMethodInfo is { } postfixMethod)
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplyPostfix(postfixMethod, ilProcessor, module, ref patchVariables);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Exception while applying postfix for {patch.GetType().FullName}\n{e}\n{
|
||||
new StackTrace() /*StackTraceUtility.ExtractStackTrace()*/}");
|
||||
}
|
||||
}
|
||||
|
||||
targetMethod.Body.OptimizeMacros();
|
||||
}
|
||||
|
||||
private static void ApplyPrefix(MethodInfo prefixMethodInfo, ILProcessor ilProcessor, ModuleDefinition module,
|
||||
ref PatchVariables patchVariables)
|
||||
{
|
||||
if (prefixMethodInfo.ReturnType != typeof(bool) && prefixMethodInfo.ReturnType != typeof(void))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException($"Prefix has invalid return type: {
|
||||
prefixMethodInfo.ReturnType.FullName}. Must be void or bool");
|
||||
}
|
||||
|
||||
var instructions = ilProcessor.instructions;
|
||||
var targetMethod = ilProcessor.GetMethod();
|
||||
|
||||
var genericParameters = GetGenericParameters(targetMethod);
|
||||
var prefixParameters = prefixMethodInfo.GetParameters();
|
||||
|
||||
var resultParameter = prefixParameters.TryGetNamed("__result");
|
||||
VerifyResultParameter(module, resultParameter, targetMethod, true);
|
||||
|
||||
var stateParameter = prefixParameters.TryGetNamed("__state");
|
||||
VerifyStateParameter(stateParameter);
|
||||
|
||||
// var skipOriginalFlag = ilProcessor.DeclareLocal(typeof(bool));
|
||||
var resultVariable = GetResultVariable(ilProcessor, ref patchVariables, resultParameter, targetMethod);
|
||||
var stateVariable = GetStateVariable(ilProcessor, ref patchVariables, stateParameter);
|
||||
var startOfOriginal = instructions[0];
|
||||
|
||||
var index = 0;
|
||||
foreach (var parameter in prefixParameters)
|
||||
{
|
||||
ilProcessor.InsertAt(index++, GetInstructionForParameter(module, parameter, resultParameter, resultVariable,
|
||||
stateParameter, stateVariable, targetMethod));
|
||||
}
|
||||
|
||||
if (genericParameters != null)
|
||||
{
|
||||
ilProcessor.InsertAt(index++, OpCodes.Call,
|
||||
module.ImportMethodWithGenericArguments(prefixMethodInfo, genericParameters));
|
||||
}
|
||||
else
|
||||
{
|
||||
ilProcessor.InsertAt(index++, OpCodes.Call, prefixMethodInfo);
|
||||
}
|
||||
|
||||
if (prefixMethodInfo.ReturnType == typeof(bool))
|
||||
{
|
||||
ilProcessor.InsertAt(index++, (OpCodes.Brtrue_S, startOfOriginal));
|
||||
|
||||
if (resultVariable != null)
|
||||
{
|
||||
ilProcessor.InsertAt(index++, (OpCodes.Ldloc_S, resultVariable));
|
||||
}
|
||||
else if (targetMethod.ReturnType.FullName != module.TypeSystem.Void.FullName)
|
||||
{
|
||||
Log.Error($"Prefix {prefixMethodInfo.FullDescription()
|
||||
} with return type of bool must assign to a __result parameter");
|
||||
}
|
||||
|
||||
ilProcessor.InsertAt(index, OpCodes.Ret);
|
||||
}
|
||||
}
|
||||
|
||||
private static VariableDefinition? GetStateVariable(ILProcessor ilProcessor, ref PatchVariables patchVariables,
|
||||
ParameterInfo? stateParameter)
|
||||
=> stateParameter != null
|
||||
? patchVariables.State ??= ilProcessor.DeclareLocal(GetUnderlyingType(stateParameter.ParameterType))
|
||||
: null;
|
||||
|
||||
private static VariableDefinition? GetResultVariable(ILProcessor ilProcessor, ref PatchVariables patchVariables,
|
||||
ParameterInfo? resultParameter, MethodDefinition targetMethod)
|
||||
=> resultParameter != null
|
||||
? patchVariables.Result ??= ilProcessor.DeclareLocal(targetMethod.ReturnType)
|
||||
: null;
|
||||
|
||||
private static void VerifyStateParameter(ParameterInfo? stateParameter)
|
||||
{
|
||||
if (stateParameter is { ParameterType.IsByRef: false })
|
||||
ThrowHelper.ThrowArgumentException("__state parameter must be declared with ref or out keyword.");
|
||||
}
|
||||
|
||||
private static Collection<GenericParameter>? GetGenericParameters(MethodDefinition targetMethod)
|
||||
=> targetMethod.HasGenericParameters ? targetMethod.GenericParameters
|
||||
: targetMethod.DeclaringType.HasGenericParameters ? targetMethod.DeclaringType.GenericParameters
|
||||
: null;
|
||||
|
||||
private static Type GetUnderlyingType(Type type) => type.IsByRef ? type.GetElementType()! : type;
|
||||
|
||||
private static void ApplyPostfix(MethodInfo postfixMethodInfo, ILProcessor ilProcessor, ModuleDefinition module,
|
||||
ref PatchVariables patchVariables)
|
||||
{
|
||||
var targetMethod = ilProcessor.GetMethod();
|
||||
|
||||
if (postfixMethodInfo.ReturnType != typeof(void)
|
||||
&& !postfixMethodInfo.ReturnType.Is(targetMethod.ReturnType))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException($"Postfix has invalid return type: {
|
||||
postfixMethodInfo.ReturnType.FullName}. Must be void or {targetMethod.ReturnType}");
|
||||
}
|
||||
|
||||
var instructions = ilProcessor.instructions;
|
||||
var genericParameters = GetGenericParameters(targetMethod);
|
||||
var postfixParameters = postfixMethodInfo.GetParameters();
|
||||
|
||||
var resultParameter = postfixParameters.TryGetNamed("__result");
|
||||
VerifyResultParameter(module, resultParameter, targetMethod, false);
|
||||
|
||||
var stateParameter = postfixParameters.TryGetNamed("__state");
|
||||
// Verify? It would make sense to match against prefix state
|
||||
|
||||
// var skipOriginalFlag = ilProcessor.DeclareLocal(typeof(bool));
|
||||
var resultVariable = GetResultVariable(ilProcessor, ref patchVariables, resultParameter, targetMethod);
|
||||
var stateVariable = GetStateVariable(ilProcessor, ref patchVariables, stateParameter);
|
||||
|
||||
var postfixInstructions = new Collection<object>();
|
||||
|
||||
if (resultVariable != null)
|
||||
postfixInstructions.Add((OpCodes.Stloc_S, resultVariable));
|
||||
|
||||
foreach (var parameter in postfixParameters)
|
||||
{
|
||||
postfixInstructions.Add(GetInstructionForParameter(module, parameter, resultParameter, resultVariable,
|
||||
stateParameter, stateVariable, targetMethod));
|
||||
}
|
||||
|
||||
if (genericParameters != null)
|
||||
{
|
||||
postfixInstructions.Add((OpCodes.Call,
|
||||
module.ImportMethodWithGenericArguments(postfixMethodInfo, genericParameters)));
|
||||
}
|
||||
else
|
||||
{
|
||||
postfixInstructions.Add((OpCodes.Call, postfixMethodInfo));
|
||||
}
|
||||
|
||||
if (postfixMethodInfo.ReturnType == typeof(void) && resultVariable != null)
|
||||
postfixInstructions.Add((OpCodes.Ldloc_S, resultVariable));
|
||||
|
||||
// TODO: use label instead of inserting multiple times
|
||||
for (var i = 0; i < instructions.Count; i++)
|
||||
{
|
||||
if (instructions[i].OpCode != OpCodes.Ret)
|
||||
continue;
|
||||
|
||||
ilProcessor.InsertRange(i, postfixInstructions);
|
||||
|
||||
i += postfixInstructions.Count;
|
||||
}
|
||||
}
|
||||
|
||||
private static ITuple GetInstructionForParameter(ModuleDefinition module, ParameterInfo parameter,
|
||||
ParameterInfo? resultParameter, VariableDefinition? resultVariable, ParameterInfo? stateParameter,
|
||||
VariableDefinition? stateVariable, MethodDefinition targetMethod)
|
||||
=> parameter == resultParameter
|
||||
? (parameter.ParameterType.IsByRef ? OpCodes.Ldloca_S : OpCodes.Ldloc_S, resultVariable)
|
||||
: parameter == stateParameter
|
||||
? (parameter.ParameterType.IsByRef ? OpCodes.Ldloca_S : OpCodes.Ldloc_S, stateVariable)
|
||||
: GetMatchingTargetParameter(targetMethod, parameter, module);
|
||||
|
||||
private static void VerifyResultParameter(ModuleDefinition module, ParameterInfo? resultParameter,
|
||||
MethodDefinition targetMethod, bool requiresByRef)
|
||||
{
|
||||
if (resultParameter is null)
|
||||
return;
|
||||
|
||||
if (targetMethod.ReturnType.IsGeneric() ? !GetUnderlyingType(resultParameter.ParameterType).IsGeneric()
|
||||
: !GetUnderlyingType(resultParameter.ParameterType).Is(targetMethod.ReturnType))
|
||||
{
|
||||
// var firstType = targetMethod.ReturnType;
|
||||
// var secondType = GetUnderlyingType(resultParameter.ParameterType);
|
||||
// Log.Message($"Type FullName: {firstType.FullName
|
||||
// }: ContainsGenericParameter: {firstType.ContainsGenericParameter
|
||||
// }, HasGenericParameters :{firstType.HasGenericParameters
|
||||
// }, IsGenericInstance: {firstType.IsGenericInstance
|
||||
// }, Type FullName: {secondType.FullName
|
||||
// } ContainsGenericParameters: {secondType.ContainsGenericParameters
|
||||
// }, IsGenericType {secondType.IsGenericType
|
||||
// }, IsConstructedGenericType: {secondType.IsConstructedGenericType}");
|
||||
|
||||
ThrowHelper.ThrowArgumentException($"__result parameter has invalid type: {
|
||||
resultParameter.ParameterType.FullName}. Must match the target's return type: {
|
||||
targetMethod.ReturnType.FullName}");
|
||||
}
|
||||
if (requiresByRef && !resultParameter.ParameterType.IsByRef)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException(
|
||||
"__result parameter must be declared with ref or out keyword.");
|
||||
}
|
||||
}
|
||||
|
||||
private record struct PatchVariables
|
||||
{
|
||||
public VariableDefinition? Result, State;
|
||||
}
|
||||
|
||||
private static ITuple GetMatchingTargetParameter(MethodDefinition targetMethod,
|
||||
ParameterInfo parameter, ModuleDefinition module)
|
||||
{
|
||||
var targetParameters = targetMethod.Parameters;
|
||||
var targetParameter = parameter.Name == "__instance"
|
||||
? targetMethod.Body.ThisParameter
|
||||
: targetParameters.TryGetNamed(parameter.Name);
|
||||
if (targetParameter is null)
|
||||
{
|
||||
return ThrowHelper.ThrowArgumentException<ITuple>($"No parameter named {
|
||||
parameter.Name} found in target method");
|
||||
}
|
||||
|
||||
var targetParameterType = targetParameter.ParameterType;
|
||||
var patchParameterType = parameter.ParameterType;
|
||||
|
||||
return GetUnderlyingType(targetParameter).Name != GetUnderlyingType(patchParameterType).Name
|
||||
? ThrowHelper.ThrowArgumentException<ITuple>($"Parameter has invalid type: {
|
||||
patchParameterType.FullName}. Must match the target method parameter's type: {
|
||||
targetParameterType.FullName}")
|
||||
: (patchParameterType.IsByRef && !targetParameter.ParameterType.IsByReference
|
||||
? OpCodes.Ldarga_S
|
||||
: OpCodes.Ldarg_S, targetParameter);
|
||||
}
|
||||
|
||||
private static TypeReference GetUnderlyingType(ParameterInfo prefixParameter, ModuleDefinition module)
|
||||
=> module.ImportReference(GetUnderlyingType(prefixParameter.ParameterType));
|
||||
|
||||
private static TypeReference GetUnderlyingType(ParameterDefinition prefixParameter)
|
||||
=> prefixParameter.ParameterType.IsByReference ? prefixParameter.ParameterType.GetElementType()!
|
||||
: prefixParameter.ParameterType;
|
||||
|
||||
private static TypeDefinition ThrowForFailureToFindType(Type patchType)
|
||||
=> ThrowHelper.ThrowInvalidOperationException<TypeDefinition>(
|
||||
$"Failed to find type with namespace {patchType.Namespace} and name {patchType.Name}");
|
||||
|
||||
private static MethodDefinition ThrowForFailureToFindMethod(TypeDefinition? type, MethodBase patchMethod)
|
||||
=> ThrowHelper.ThrowInvalidOperationException<MethodDefinition>(
|
||||
$"Failed to find method with type {type} and name {patchMethod.Name}");
|
||||
|
||||
private static void ApplyClassPatch(ModuleDefinition module, FishClassPrepatch patch)
|
||||
{
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
var patchType = patch.Type
|
||||
?? ThrowHelper.ThrowInvalidOperationException<Type>(
|
||||
$"Patch {patch.GetType().FullName} has no target type.");
|
||||
|
||||
if (patchType.IsGenericType)
|
||||
patchType = patchType.GetGenericTypeDefinition();
|
||||
|
||||
DebugLog.Message($"Applying prepatch {patch.GetType().FullName} on type {
|
||||
patchType.FullName}");
|
||||
|
||||
var targetType = module.GetType(patchType.Namespace, patchType.Name) ?? ThrowForFailureToFindType(patchType);
|
||||
|
||||
// PrepatchManager.AddSecurityAttributes(targetType.SecurityDeclarations, module);
|
||||
|
||||
patch.FreePatch(targetType);
|
||||
}
|
||||
|
||||
public FishPrepatchBase this[Type type] => All[type];
|
||||
|
||||
public FishPrepatchHolder(Type type)
|
||||
{
|
||||
_type = type;
|
||||
AddPatchesRecursively(type);
|
||||
}
|
||||
|
||||
public void Scribe() => ExposeData(); // Exposable.Scribe(this, _type.FullName);
|
||||
// directly calling ExposeData prevents creating nested nodes in the config file. Looks cleaner imo.
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
foreach (var (type, patch) in All)
|
||||
Exposable.Scribe(patch, type.FullName ?? type.Name);
|
||||
}
|
||||
|
||||
private void AddPatchesRecursively(Type type)
|
||||
{
|
||||
if (typeof(FishPrepatchBase).IsAssignableFrom(type) && !All.ContainsKey(type))
|
||||
{
|
||||
if (PerformanceFishMod.AllPrepatchClasses is { } allPatches)
|
||||
RemoveDupes(allPatches, type);
|
||||
|
||||
All.TryAdd(type, FishPrepatchBase.Get(type));
|
||||
}
|
||||
|
||||
foreach (var nestedType in type.GetNestedTypes(AccessTools.all))
|
||||
AddPatchesRecursively(nestedType);
|
||||
}
|
||||
|
||||
private void RemoveDupes(ClassWithFishPrepatches[] patches, Type type)
|
||||
{
|
||||
foreach (var patchClass in patches)
|
||||
{
|
||||
if (patchClass.GetType() == _type
|
||||
|| !patchClass.Patches.All.ContainsKey(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
patchClass.Patches.All.TryRemove(type, out _);
|
||||
Log.Warning($"Performance Fish removed a duplicate patch from {
|
||||
patchClass.GetType().FullName}. This is likely caused by no longer valid mod configs");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<FishPrepatchBase> GetEnumerator() => All.Values.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => All.Values.GetEnumerator();
|
||||
|
||||
private Type _type;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct FishStash
|
||||
{
|
||||
private const string ANCHOR_STRING_VALUE = "F1shSt4sh69";
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
|
||||
private string? _anchorString;
|
||||
public UnhandledMessageQueue? UnhandledMessages;
|
||||
public GCHandle MessageQueueGCHandle;
|
||||
|
||||
public static ref FishStash Get
|
||||
=> ref StaticHandle != default
|
||||
? ref FromHandle(StaticHandle)
|
||||
: ref GetFishStashFromInternalHandle();
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static ref FishStash GetFishStashFromInternalHandle()
|
||||
{
|
||||
StaticHandle = InternalFishStashHandle?.Handle
|
||||
?? ThrowHelper.ThrowInvalidOperationException<nint>("Failed to fetch internal FishStashHandle.");
|
||||
|
||||
ref var stash = ref InternalFishStashHandle.Stash;
|
||||
if (stash._anchorString != ANCHOR_STRING_VALUE)
|
||||
ThrowHelper.ThrowInvalidDataException($"FishStash started with invalid anchor: {stash._anchorString}");
|
||||
|
||||
return ref stash;
|
||||
}
|
||||
|
||||
internal static FishStashHandle? InternalFishStashHandle { get; }
|
||||
= typeof(Verse.Log).GetField(nameof(FishStashHandle), BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?.GetValue(null) as FishStashHandle;
|
||||
|
||||
internal static nint StaticHandle { get; private set; }
|
||||
|
||||
internal static unsafe ref FishStash FromHandle(IntPtr handle) => ref Unsafe.AsRef<FishStash>(handle.ToPointer());
|
||||
|
||||
private static unsafe void Create()
|
||||
{
|
||||
if (StaticHandle != default)
|
||||
ThrowHelper.ThrowInvalidOperationException("Tried creating duplicate FishStash.");
|
||||
|
||||
var handle = Marshal.AllocHGlobal(sizeof(FishStash));
|
||||
Unsafe.InitBlockUnaligned(handle.ToPointer(), 0, (uint)sizeof(FishStash));
|
||||
((FishStash*)handle)->_anchorString = ANCHOR_STRING_VALUE;
|
||||
|
||||
StaticHandle = handle;
|
||||
}
|
||||
|
||||
static FishStash()
|
||||
{
|
||||
if (!Log.Ready && InternalFishStashHandle is null)
|
||||
Create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
public class FishStashHandle : IDisposable
|
||||
{
|
||||
private static readonly List<nint> _allHandles = new();
|
||||
|
||||
public ref FishStash Stash => ref FishStash.FromHandle(Handle);
|
||||
|
||||
public nint Handle { get; }
|
||||
|
||||
public FishStashHandle(long handle)
|
||||
{
|
||||
if (_allHandles.Contains((nint)handle))
|
||||
ThrowHelper.ThrowInvalidOperationException("Tried creating duplicate FishStashHandle.");
|
||||
else
|
||||
_allHandles.Add((nint)handle);
|
||||
|
||||
Handle = (nint)handle;
|
||||
}
|
||||
|
||||
private void ReleaseUnmanagedResources()
|
||||
{
|
||||
Marshal.FreeHGlobal(Handle);
|
||||
_allHandles.Remove(Handle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~FishStashHandle() => ReleaseUnmanagedResources();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using Mono.Cecil;
|
||||
using PerformanceFish.ModCompatibility;
|
||||
using Prepatcher;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
public static class FreePatchEntry
|
||||
{
|
||||
[FreePatch]
|
||||
[UsedImplicitly]
|
||||
public static void Start(ModuleDefinition module)
|
||||
{
|
||||
ActiveMods.CheckRequirements();
|
||||
PrepatchManager.Start(module);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern alias nuget;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Security;
|
||||
using System.Threading.Tasks;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Collections.Generic;
|
||||
using nuget::JetBrains.Annotations;
|
||||
using PerformanceFish.ModCompatibility;
|
||||
using CollectionExtensions = PerformanceFish.Utility.CollectionExtensions;
|
||||
using MethodBody = Mono.Cecil.Cil.MethodBody;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
public static class PrepatchManager
|
||||
{
|
||||
public static bool PatchingFinished { get; private set; }
|
||||
|
||||
[UsedImplicitly]
|
||||
public static void Start(ModuleDefinition module)
|
||||
{
|
||||
if (PatchingFinished)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException("Tried running free patches a second time. This should "
|
||||
+ "never happen. Cancelling now to avoid further issues.");
|
||||
}
|
||||
|
||||
Log.Message(LogPatches.PERFORMANCE_FISH_WELCOME_MESSAGE);
|
||||
if (!ActiveMods.Prepatcher)
|
||||
Log.Warning("Prepatches running without prepatcher active. This should normally not happen.");
|
||||
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
// if (Type.GetType("Prepatcher.Process.FreePatcher, PrepatcherImpl") is { } freePatcherType
|
||||
// && AccessTools.Method(freePatcherType, "AddSkipVerificationAttribute") is { } addSkipVerificationMethod
|
||||
// /*Type.GetType("HugsLib.Patches.MainMenuDrawer_Quickstart_Patch, HugsLib") is { } freePatcherType
|
||||
// && AccessTools.Method(freePatcherType, "QuicktestButtonUsesQuickstarter") is { } addSkipVerificationMethod*/)
|
||||
// {
|
||||
// var rimworldAssembly = typeof(Root).Assembly;
|
||||
// var harmonyAssembly = typeof(Harmony).Assembly;
|
||||
// var prepatcherAssembly = freePatcherType.Assembly;
|
||||
// var fishAssembly = typeof(PerformanceFishMod).Assembly;
|
||||
// var monoModAssembly = typeof(MonoMod.Utils.Cil.ILGeneratorShim).Assembly;
|
||||
//
|
||||
// SetReflectionOnly(rimworldAssembly, false, out var previousRimworldValue);
|
||||
// SetReflectionOnly(harmonyAssembly, false, out var previousHarmonyValue);
|
||||
// SetReflectionOnly(prepatcherAssembly, false, out var previousPrepatcherValue);
|
||||
// SetReflectionOnly(monoModAssembly, false, out var previousMonoModValue);
|
||||
// SetReflectionOnly(fishAssembly, false, out var previousFishValue);
|
||||
//
|
||||
// PerformanceFishMod.Harmony.Patch(addSkipVerificationMethod,
|
||||
// prefix: new(methodof(FreePatcherSkipVerificationPatch)));
|
||||
//
|
||||
// SetReflectionOnly(rimworldAssembly, previousRimworldValue, out _);
|
||||
// SetReflectionOnly(harmonyAssembly, previousHarmonyValue, out _);
|
||||
// SetReflectionOnly(prepatcherAssembly, previousPrepatcherValue, out _);
|
||||
// SetReflectionOnly(fishAssembly, previousMonoModValue, out _);
|
||||
// SetReflectionOnly(fishAssembly, previousFishValue, out _);
|
||||
// }
|
||||
|
||||
AddAttributes(module);
|
||||
|
||||
ModifyAllTypes(module);
|
||||
|
||||
var allPrepatchClasses
|
||||
= PerformanceFishMod.AllPrepatchClasses
|
||||
= PerformanceFishMod.InitializeAllPatchClasses<ClassWithFishPrepatches>();
|
||||
|
||||
_ = FishSettings.Instance;
|
||||
|
||||
foreach (var prepatchClass in allPrepatchClasses)
|
||||
prepatchClass.Patches.PatchAll(module);
|
||||
|
||||
PatchingFinished = true;
|
||||
stopwatch.Stop();
|
||||
Verse.Log.Message($"Performance Fish finished applying prepatches in {
|
||||
stopwatch.ElapsedSecondsAccurate():N7} seconds");
|
||||
}
|
||||
|
||||
// private static readonly FieldInfo? _monoAssemblyField = AccessTools.Field(typeof(Assembly), "_mono_assembly");
|
||||
//
|
||||
// internal static unsafe void SetReflectionOnly(Assembly asm, bool value, out bool previousValue)
|
||||
// {
|
||||
// var refOnlyField = (int*)((IntPtr)_monoAssemblyField!.GetValue(asm) + 0x74);
|
||||
// previousValue = *refOnlyField == 1 || (*refOnlyField == 0 ? false : throw new());
|
||||
// *refOnlyField = value ? 1 : 0;
|
||||
// }
|
||||
//
|
||||
// public static bool FreePatcherSkipVerificationPatch() => false;
|
||||
|
||||
private static void ModifyAllTypes(ModuleDefinition module)
|
||||
{
|
||||
var types = module.Types;
|
||||
|
||||
Parallel.For(0, types.Count, // about 20% faster than sequential for in testing
|
||||
i => ModifyAllMethodsIn(types[i]));
|
||||
}
|
||||
|
||||
private static void ModifyAllMethodsIn(TypeDefinition type)
|
||||
{
|
||||
if (type.Methods is not { } methods)
|
||||
return;
|
||||
|
||||
for (var i = methods.Count; i-- > 0;)
|
||||
{
|
||||
if (methods[i].Body is { } body)
|
||||
ModifyMethodBody(body);
|
||||
}
|
||||
|
||||
if (type.NestedTypes is not { } nestedTypes)
|
||||
return;
|
||||
|
||||
for (var i = nestedTypes.Count; i-- > 0;)
|
||||
ModifyAllMethodsIn(nestedTypes[i]);
|
||||
}
|
||||
|
||||
private static void ModifyMethodBody(MethodBody body)
|
||||
{
|
||||
ApplySkipLocalsInit(body);
|
||||
ModifyInstructions(body.Method, body.Instructions);
|
||||
}
|
||||
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
|
||||
private enum LinqMethod
|
||||
{
|
||||
None,
|
||||
First,
|
||||
FirstOrDefault,
|
||||
Where,
|
||||
Any,
|
||||
Select,
|
||||
Count,
|
||||
Contains,
|
||||
Sum
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private static class LinqStrings
|
||||
{
|
||||
public const string
|
||||
First = "First",
|
||||
FirstOrDefault = "FirstOrDefault",
|
||||
Where = "Where",
|
||||
Any = "Any",
|
||||
Select = "Select",
|
||||
Count = "Count",
|
||||
Contains = "Contains",
|
||||
Sum = "Sum",
|
||||
DeclaringType = "System.Linq.Enumerable";
|
||||
}
|
||||
|
||||
private static void ModifyInstructions(MethodDefinition method, Collection<Instruction> instructions)
|
||||
{
|
||||
for (var i = 0; i < instructions.Count; i++)
|
||||
{
|
||||
var instruction = instructions[i];
|
||||
|
||||
if (instruction.OpCode != OpCodes.Call)
|
||||
continue;
|
||||
|
||||
if (i - 4 < 0)
|
||||
continue;
|
||||
|
||||
if (!(i + 1 < instructions.Count
|
||||
&& instruction.Operand is MethodReference
|
||||
{
|
||||
Name: LinqStrings.Where, DeclaringType.FullName: LinqStrings.DeclaringType
|
||||
}
|
||||
&& instructions[i + 1].Operand is MethodReference
|
||||
{
|
||||
Name: LinqStrings.FirstOrDefault or LinqStrings.First,
|
||||
DeclaringType.FullName: LinqStrings.DeclaringType
|
||||
})
|
||||
&& instruction.Operand is not MethodReference
|
||||
{
|
||||
Name: LinqStrings.FirstOrDefault or LinqStrings.First,
|
||||
DeclaringType.FullName: LinqStrings.DeclaringType
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var linqTarget = LinqMethod.FirstOrDefault;
|
||||
|
||||
if (((MethodReference)instruction.Operand).Name == LinqStrings.First
|
||||
|| (i + 1 < instructions.Count
|
||||
&& ((MethodReference)instruction.Operand).Name == LinqStrings.Where
|
||||
&& ((MethodReference)instructions[i + 1].Operand).Name == LinqStrings.First))
|
||||
{
|
||||
linqTarget = LinqMethod.First;
|
||||
}
|
||||
|
||||
if (!(instructions[i - 1].OpCode == OpCodes.Newobj && instructions[i - 2].OpCode == OpCodes.Ldftn)
|
||||
&& !(instructions[i - 1].OpCode == OpCodes.Stsfld && instructions[i - 2].OpCode == OpCodes.Dup
|
||||
&& instructions[i - 3].OpCode == OpCodes.Newobj && instructions[i - 4].OpCode == OpCodes.Ldftn))
|
||||
continue;
|
||||
|
||||
var inputMethodInstruction = instructions[i - 4];
|
||||
var inputMethod = inputMethodInstruction.OpCode == OpCodes.Call
|
||||
? inputMethodInstruction.Operand as MethodReference
|
||||
: null;
|
||||
|
||||
if (inputMethod is null && i - 6 < 0)
|
||||
continue;
|
||||
|
||||
if ((inputMethod ??= (inputMethodInstruction = instructions[i - 6]).Operand as MethodReference) is null)
|
||||
continue;
|
||||
|
||||
var module = method.Module;
|
||||
|
||||
if (inputMethod!.Name == "get_AllDefs")
|
||||
{
|
||||
inputMethodInstruction.Operand
|
||||
= inputMethod
|
||||
= module.ImportReference((inputMethod.DeclaringType.ContainsGenericParameter
|
||||
? typeof(DefDatabase<>).GetProperty(nameof(DefDatabase<Def>.AllDefsListForReading))
|
||||
: typeof(DefDatabase<>)
|
||||
.MakeGenericType(AccessTools.TypeByName(((GenericInstanceType)inputMethod.DeclaringType)
|
||||
.GenericArguments[0].FullName))
|
||||
.GetProperty(nameof(DefDatabase<Def>.AllDefsListForReading)))!.GetMethod, inputMethod);
|
||||
}
|
||||
|
||||
var inputReturnType = inputMethod.ReturnType;
|
||||
if (inputReturnType.Name == "List`1")
|
||||
{
|
||||
var containsGenericParameter = inputReturnType.ContainsGenericParameter
|
||||
&& (inputReturnType.TryGetGenericArguments()
|
||||
?.Any(a => a.TryGetGenericParameterType(inputMethod) is null) ?? true);
|
||||
|
||||
var listFindMethod = linqTarget == LinqMethod.FirstOrDefault
|
||||
? containsGenericParameter
|
||||
? typeof(List<>).GetMethod(nameof(List<object>.Find))
|
||||
: typeof(List<>)
|
||||
.MakeGenericType(AccessTools.TypeByName(inputReturnType
|
||||
.TryGetGenericArguments(inputMethod)![0].FullName)).GetMethod(nameof(List<object>.Find))
|
||||
: methodof((Func<List<object>, Predicate<object>, object>)CollectionExtensions.First)
|
||||
.GetGenericMethodDefinition();
|
||||
|
||||
if (linqTarget == LinqMethod.First && !containsGenericParameter)
|
||||
{
|
||||
listFindMethod = listFindMethod!.MakeGenericMethod(AccessTools.TypeByName(inputReturnType
|
||||
.TryGetGenericArguments(inputMethod)![0].FullName));
|
||||
}
|
||||
|
||||
var newOperand = containsGenericParameter
|
||||
? module.ImportReference(listFindMethod, method)
|
||||
: module.ImportReference(listFindMethod);
|
||||
|
||||
// Log.Message($"Performance Fish successfully optimized {
|
||||
// ((MethodReference)instruction.Operand).FullName} within method {
|
||||
// method.FullName} by applying new operand {newOperand.FullName}");
|
||||
|
||||
var instructionsToRemove = 0;
|
||||
if (instruction.Operand is MethodReference { Name: LinqStrings.Where })
|
||||
instructionsToRemove++;
|
||||
|
||||
instruction.Operand = newOperand;
|
||||
|
||||
while (instructionsToRemove-- > 0)
|
||||
instructions.RemoveAt(i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplySkipLocalsInit(MethodBody body) => body.InitLocals = false;
|
||||
|
||||
private static void AddAttributes(ModuleDefinition module)
|
||||
{
|
||||
AddModuleAttributes(module);
|
||||
AddAssemblyAttributes(module);
|
||||
// AddSecurityAttributes(module.Assembly.SecurityDeclarations, module);
|
||||
}
|
||||
|
||||
private static void AddAssemblyAttributes(ModuleDefinition module)
|
||||
{
|
||||
var rimworldCustomAttributes = module.Assembly.CustomAttributes;
|
||||
|
||||
if (TryAddAttribute(module, rimworldCustomAttributes, typeof(IgnoresAccessChecksToAttribute),
|
||||
typeof(string)) is { } ignoresAccessChecksToAttribute)
|
||||
{
|
||||
ignoresAccessChecksToAttribute.ConstructorArguments.Add(new(module.TypeSystem.String, "mscorlib"));
|
||||
}
|
||||
|
||||
TryAddAttribute(module, rimworldCustomAttributes, typeof(AllowPartiallyTrustedCallersAttribute));
|
||||
|
||||
TryAddAttribute(module, rimworldCustomAttributes, typeof(SecurityTransparentAttribute));
|
||||
|
||||
if (TryAddAttribute(module, rimworldCustomAttributes, typeof(SecurityRulesAttribute),
|
||||
typeof(SecurityRuleSet)) is { } securityRulesAttribute)
|
||||
{
|
||||
securityRulesAttribute.ConstructorArguments.Add(new(module.ImportReference(typeof(SecurityRuleSet)),
|
||||
SecurityRuleSet.Level2));
|
||||
|
||||
securityRulesAttribute.Properties.Add(new(nameof(SecurityRulesAttribute.SkipVerificationInFullTrust),
|
||||
new(module.TypeSystem.Boolean, true)));
|
||||
}
|
||||
|
||||
var debuggableAttribute = rimworldCustomAttributes.First(static a
|
||||
=> a.AttributeType.Name == nameof(DebuggableAttribute));
|
||||
|
||||
debuggableAttribute.Constructor
|
||||
= module.ImportReference(typeof(DebuggableAttribute)
|
||||
.GetConstructor(new[] { typeof(bool), typeof(bool) }));
|
||||
|
||||
debuggableAttribute.ConstructorArguments.Clear();
|
||||
debuggableAttribute.ConstructorArguments.Add(new(module.TypeSystem.Boolean, false));
|
||||
debuggableAttribute.ConstructorArguments.Add(new(module.TypeSystem.Boolean, false));
|
||||
}
|
||||
|
||||
private static void AddModuleAttributes(ModuleDefinition module)
|
||||
=> TryAddAttribute(module, module.CustomAttributes, typeof(UnverifiableCodeAttribute));
|
||||
|
||||
// internal static void AddSecurityAttributes(Collection<SecurityDeclaration> securityDeclarations,
|
||||
// ModuleDefinition module, [CallerArgumentExpression(nameof(securityDeclarations))] string? targetName = null)
|
||||
// {
|
||||
// if (securityDeclarations.Any(static s => s.SecurityAttributes
|
||||
// .Any(static a => a.AttributeType.Name == nameof(SecurityPermissionAttribute)
|
||||
// && a.Properties.Any(static p => p.Name == nameof(SecurityPermissionAttribute.SkipVerification)))))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (targetName != null)
|
||||
// Log.Message($"Added skipVerification attribute to {targetName}");
|
||||
//
|
||||
// securityDeclarations.Add(new(Mono.Cecil.SecurityAction.RequestMinimum)
|
||||
// {
|
||||
// SecurityAttributes =
|
||||
// {
|
||||
// new SecurityAttribute(module.ImportReference(typeof(SecurityPermissionAttribute)))
|
||||
// {
|
||||
// Properties =
|
||||
// {
|
||||
// new(nameof(SecurityPermissionAttribute.SkipVerification),
|
||||
// new(module.TypeSystem.Boolean, true))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
public static CustomAttribute? TryAddAttribute(ModuleDefinition module, IList<CustomAttribute> attributes,
|
||||
Type attributeType, params Type[] parameters)
|
||||
{
|
||||
if (ContainsAttribute(attributes, attributeType.Name))
|
||||
return null;
|
||||
|
||||
var attribute = ImportAttribute(module, attributeType, parameters);
|
||||
attributes.Add(attribute);
|
||||
return attribute;
|
||||
}
|
||||
|
||||
private static CustomAttribute ImportAttribute(ModuleDefinition module, Type attributeType,
|
||||
params Type[] parameters)
|
||||
=> new(module.ImportReference(AccessTools.Constructor(attributeType, parameters)));
|
||||
|
||||
private static bool ContainsAttribute(IList<CustomAttribute> attributes, string name)
|
||||
{
|
||||
for (var i = attributes.Count; i-- > 0;)
|
||||
{
|
||||
if (attributes[i].AttributeType.Name == name)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user