Added gas grid optimization, various despawn optimizations, new hauling optimizations utilizing events and regions, bugfixes for old patches and for vanilla functions too, as well as smaller additions. 1.3 files removed.
This commit is contained in:
@@ -13,7 +13,22 @@ namespace PerformanceFish.Prepatching;
|
||||
public abstract class ClassWithFishPrepatches : SingletonFactory<ClassWithFishPrepatches>
|
||||
{
|
||||
public virtual FishPrepatchHolder Patches { get; }
|
||||
public virtual bool RequiresLoadedGameForPatching => false;
|
||||
|
||||
public virtual bool ShowSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var patch in Patches)
|
||||
{
|
||||
if (patch.ShowSettings)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Enabled => true;
|
||||
|
||||
protected ClassWithFishPrepatches() => Patches = new(GetType());
|
||||
}
|
||||
@@ -11,7 +11,25 @@ public abstract class FishClassPrepatch : FishPrepatchBase
|
||||
{
|
||||
public abstract Type Type { get; }
|
||||
|
||||
public virtual void FreePatch(TypeDefinition typeDefinition)
|
||||
public abstract void FreePatch(TypeDefinition typeDefinition);
|
||||
|
||||
protected internal override void ApplyPatch(ModuleDefinition module)
|
||||
{
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
var patchType = Type
|
||||
?? ThrowHelper.ThrowInvalidOperationException<Type>(
|
||||
$"Patch {GetType().FullName} has no target type.");
|
||||
|
||||
if (patchType.IsGenericType)
|
||||
patchType = patchType.GetGenericTypeDefinition();
|
||||
|
||||
DebugLog.Message($"Applying prepatch {GetType().FullName} on type {
|
||||
patchType.FullName}");
|
||||
|
||||
var targetType = module.GetType(patchType.Namespace, patchType.Name) ?? ThrowForFailureToFindType(patchType);
|
||||
|
||||
// PrepatchManager.AddSecurityAttributes(targetType.SecurityDeclarations, module);
|
||||
|
||||
FreePatch(targetType);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Mono.Collections.Generic;
|
||||
using MonoMod.Utils;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
@@ -24,4 +27,331 @@ public abstract class FishPrepatch : FishPrepatchBase
|
||||
|
||||
private MethodInfo? TryGetMethod(string nameCaseInsensitive, Predicate<MethodInfo>? predicate = null)
|
||||
=> GetType().TryGetMethod(nameCaseInsensitive, predicate);
|
||||
|
||||
protected internal override void ApplyPatch(ModuleDefinition module)
|
||||
{
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
var patchMethod = TargetMethodBase
|
||||
?? ThrowHelper.ThrowInvalidOperationException<MethodBase>(
|
||||
$"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 {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 targetMethodBody = targetMethod.Body;
|
||||
var ilProcessor = targetMethodBody.GetILProcessor();
|
||||
|
||||
try
|
||||
{
|
||||
Transpiler(ilProcessor, module);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Exception while applying transpiler for {GetType().FullName}\n{e}\n{
|
||||
Environment.StackTrace /*StackTraceUtility.ExtractStackTrace()*/}");
|
||||
}
|
||||
|
||||
var patchVariables = new PatchVariables();
|
||||
|
||||
if (PrefixMethodInfo is { } prefixMethod)
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplyPrefix(prefixMethod, ilProcessor, module, ref patchVariables);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Exception while applying prefix for {GetType().FullName}\n{e}\n{
|
||||
Environment.StackTrace /*StackTraceUtility.ExtractStackTrace()*/}");
|
||||
}
|
||||
}
|
||||
|
||||
if (PostfixMethodInfo is { } postfixMethod)
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplyPostfix(postfixMethod, ilProcessor, module, ref patchVariables);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Exception while applying postfix for {GetType().FullName}\n{e}\n{
|
||||
Environment.StackTrace /*StackTraceUtility.ExtractStackTrace()*/}");
|
||||
}
|
||||
}
|
||||
|
||||
targetMethodBody.SimplifyMacros();
|
||||
targetMethodBody.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, startOfOriginal));
|
||||
|
||||
if (resultVariable != null)
|
||||
{
|
||||
ilProcessor.InsertAt(index++, (OpCodes.Ldloc, 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 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, 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, resultVariable));
|
||||
|
||||
Span<int> retInstructions = stackalloc int[instructions.Count];
|
||||
var retInstructionCount = 0;
|
||||
|
||||
for (var i = 0; i < instructions.Count; i++)
|
||||
{
|
||||
if (instructions[i].OpCode == OpCodes.Ret)
|
||||
retInstructions[retInstructionCount++] = i;
|
||||
}
|
||||
|
||||
var lastRetInstructionIndex = retInstructions[retInstructionCount - 1];
|
||||
ilProcessor.InsertRange(lastRetInstructionIndex, postfixInstructions);
|
||||
|
||||
for (var i = 0; i < instructions.Count; i++)
|
||||
{
|
||||
var instruction = instructions[i];
|
||||
if (instruction.Operand is Instruction branchTarget && branchTarget.OpCode == OpCodes.Ret)
|
||||
instruction.Operand = instructions[lastRetInstructionIndex];
|
||||
}
|
||||
|
||||
for (var i = 0; i < retInstructionCount - 1; i++)
|
||||
{
|
||||
var instruction = instructions[retInstructions[i]];
|
||||
instruction.OpCode = OpCodes.Br;
|
||||
instruction.Operand = instructions[lastRetInstructionIndex];
|
||||
}
|
||||
}
|
||||
|
||||
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 ITuple GetInstructionForParameter(ModuleDefinition module, ParameterInfo parameter,
|
||||
ParameterInfo? resultParameter, VariableDefinition? resultVariable, ParameterInfo? stateParameter,
|
||||
VariableDefinition? stateVariable, MethodDefinition targetMethod)
|
||||
=> parameter == resultParameter
|
||||
? (parameter.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc, resultVariable)
|
||||
: parameter == stateParameter
|
||||
? (parameter.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc, 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 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 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
|
||||
: OpCodes.Ldarg, 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 MethodDefinition ThrowForFailureToFindMethod(TypeDefinition? type, MethodBase patchMethod)
|
||||
=> ThrowHelper.ThrowInvalidOperationException<MethodDefinition>(
|
||||
$"Failed to find method with type {type} and name {patchMethod.Name}");
|
||||
|
||||
private record struct PatchVariables
|
||||
{
|
||||
public VariableDefinition? Result, State;
|
||||
}
|
||||
}
|
||||
@@ -4,23 +4,73 @@
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern alias nuget;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using nuget::JetBrains.Annotations;
|
||||
using PerformanceFish.ModCompatibility;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.WithMembers)]
|
||||
public abstract class FishPrepatchBase : SingletonFactory<FishPrepatchBase>, IExposable, IHasDescription
|
||||
{
|
||||
private bool _enabled;
|
||||
private bool
|
||||
_enabled,
|
||||
_isActive;
|
||||
|
||||
private string? _descriptionWithNotes;
|
||||
|
||||
private FishPrepatchBase[]? _resolvedLinkedPrepatches;
|
||||
private FishPatch[]? _resolvedLinkedHarmonyPatches;
|
||||
|
||||
internal int IDNumber { get; }
|
||||
|
||||
public virtual int Priority => HarmonyLib.Priority.Normal;
|
||||
|
||||
public virtual List<string> IncompatibleModIDs { get; } = [];
|
||||
|
||||
public virtual List<Type> LinkedPatches { get; } = [];
|
||||
|
||||
private FishPrepatchBase[] ResolvedLinkedPrepatches
|
||||
=> _resolvedLinkedPrepatches ??= LinkedPatches
|
||||
.Where(static type => type.IsAssignableTo(typeof(FishPrepatchBase))).Select(static type => Get(type))
|
||||
.ToArray();
|
||||
|
||||
private FishPatch[] ResolvedLinkedHarmonyPatches
|
||||
=> _resolvedLinkedHarmonyPatches ??= LinkedPatches
|
||||
.Where(static type => type.IsAssignableTo(typeof(FishPatch))).Select(static type => FishPatch.Get(type))
|
||||
.ToArray();
|
||||
|
||||
public bool DisabledForModCompatibility => ActiveMods.ContainsAnyOf(IncompatibleModIDs);
|
||||
|
||||
public virtual string? Description => null;
|
||||
|
||||
public string? DescriptionWithNotes
|
||||
=> _descriptionWithNotes ??= !DisabledForModCompatibility
|
||||
? Description
|
||||
: Description
|
||||
+ $"{(Description?.EndsWith(".") ?? true ? "" : ".")} Disabled for compatibility with {
|
||||
IncompatibleModIDs.Select(static id => ActiveMods.TryGetModMetaData(id)?.Name)
|
||||
.Where(static id => id != null).ToCommaList(true)}";
|
||||
|
||||
public virtual string? Name => null;
|
||||
|
||||
public virtual bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set => _enabled = value;
|
||||
get => _enabled && !DisabledForModCompatibility;
|
||||
set
|
||||
{
|
||||
if (_enabled == value)
|
||||
return;
|
||||
|
||||
_enabled = value;
|
||||
|
||||
for (var i = 0; i < ResolvedLinkedPrepatches.Length; i++)
|
||||
ResolvedLinkedPrepatches[i].Enabled = value;
|
||||
|
||||
for (var i = 0; i < ResolvedLinkedHarmonyPatches.Length; i++)
|
||||
ResolvedLinkedHarmonyPatches[i].Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool DefaultState
|
||||
@@ -28,8 +78,38 @@ public abstract class FishPrepatchBase : SingletonFactory<FishPrepatchBase>, IEx
|
||||
[Pure] get => true;
|
||||
}
|
||||
|
||||
public virtual bool ShowSettings => true;
|
||||
|
||||
public bool IsActive => _isActive;
|
||||
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
protected FishPrepatchBase() => _enabled = DefaultState;
|
||||
protected FishPrepatchBase()
|
||||
{
|
||||
_enabled = DefaultState;
|
||||
IDNumber = Interlocked.Increment(ref _idCounter);
|
||||
|
||||
if (OnAssembliesLoaded.Loaded)
|
||||
_isActive = FishStash.Get.IsPatchActive(this);
|
||||
}
|
||||
|
||||
protected internal abstract void ApplyPatch(ModuleDefinition module);
|
||||
|
||||
protected internal virtual void OnPatchingCompleted()
|
||||
{
|
||||
}
|
||||
|
||||
protected internal virtual void BeforeHarmonyPatching()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void ExposeData() => Scribe_Values.Look(ref _enabled, "enabled", DefaultState);
|
||||
|
||||
internal static Comparer<FishPrepatchBase> PriorityComparer
|
||||
= Comparer<FishPrepatchBase>.Create(static (x, y) => y.Priority.CompareTo(x.Priority));
|
||||
|
||||
protected static TypeDefinition ThrowForFailureToFindType(Type patchType)
|
||||
=> ThrowHelper.ThrowInvalidOperationException<TypeDefinition>(
|
||||
$"Failed to find type with namespace {patchType.Namespace} and name {patchType.Name}");
|
||||
|
||||
private static volatile int _idCounter;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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 static class FishPrepatchExtensions
|
||||
{
|
||||
internal static void ApplyPatches(this ClassWithFishPrepatches[] allPrepatchClasses, ModuleDefinition module)
|
||||
{
|
||||
var prepatches = ToSortedList(allPrepatchClasses, true);
|
||||
|
||||
foreach (var prepatch in prepatches)
|
||||
{
|
||||
if (!prepatch.Enabled)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
prepatch.ApplyPatch(module);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Exception while applying prepatch '{prepatch}' for '{PerformanceFishMod.NAME}':\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void BeforeHarmonyPatching(this ClassWithFishPrepatches[] allPrepatchClasses)
|
||||
=> allPrepatchClasses.FinalizePrepatchAction(static patch => patch.BeforeHarmonyPatching());
|
||||
|
||||
internal static void OnPatchingCompleted(this ClassWithFishPrepatches[] allPrepatchClasses)
|
||||
=> allPrepatchClasses.FinalizePrepatchAction(static patch => patch.OnPatchingCompleted());
|
||||
|
||||
private static void FinalizePrepatchAction(this ClassWithFishPrepatches[] allPrepatchClasses,
|
||||
Action<FishPrepatchBase> action)
|
||||
{
|
||||
var prepatches = ToSortedList(allPrepatchClasses, false);
|
||||
|
||||
foreach (var prepatch in prepatches)
|
||||
{
|
||||
if (!prepatch.IsActive)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
action(prepatch);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Exception while finalizing prepatch '{prepatch}' for '{PerformanceFishMod.NAME}':\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<FishPrepatchBase> ToSortedList(this ClassWithFishPrepatches[] allPrepatchClasses,
|
||||
bool checkEnabledState)
|
||||
{
|
||||
var prepatches = new List<FishPrepatchBase>();
|
||||
|
||||
foreach (var prepatchClass in allPrepatchClasses)
|
||||
{
|
||||
if (!checkEnabledState || prepatchClass.Enabled)
|
||||
prepatches.AddRange(prepatchClass.Patches.All.Values);
|
||||
}
|
||||
|
||||
prepatches.Sort(FishPrepatchBase.PriorityComparer);
|
||||
return prepatches;
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,6 @@
|
||||
|
||||
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;
|
||||
@@ -23,366 +17,6 @@ public class FishPrepatchHolder : IExposable, IEnumerable<FishPrepatchBase>
|
||||
|
||||
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];
|
||||
|
||||
|
||||
@@ -3,19 +3,28 @@
|
||||
// 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.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
public struct FishStash
|
||||
public unsafe struct FishStash : IEquatable<FishStash>
|
||||
{
|
||||
private const string ANCHOR_STRING_VALUE = "F1shSt4sh69";
|
||||
private const string ANCHOR_STRING_VALUE = "F1shSt4sh6912345";
|
||||
private const int ANCHOR_STRING_LENGTH = 16;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
|
||||
private string? _anchorString;
|
||||
public UnhandledMessageQueue? UnhandledMessages;
|
||||
public GCHandle MessageQueueGCHandle;
|
||||
private fixed char _anchorString[ANCHOR_STRING_LENGTH];
|
||||
|
||||
internal GCHandle MessageQueueGCHandle;
|
||||
|
||||
private int _activePrepatchCount;
|
||||
private IntPtr _activePrepatches;
|
||||
|
||||
public UnhandledMessageQueue? UnhandledMessages
|
||||
=> MessageQueueGCHandle != default && MessageQueueGCHandle.IsAllocated
|
||||
? Unsafe.As<UnhandledMessageQueue>(MessageQueueGCHandle.Target)
|
||||
: null;
|
||||
|
||||
public static ref FishStash Get
|
||||
=> ref StaticHandle != default
|
||||
@@ -29,8 +38,12 @@ public struct FishStash
|
||||
?? 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}");
|
||||
fixed (char* anchor = stash._anchorString)
|
||||
{
|
||||
var anchorString = new string(anchor, 0, ANCHOR_STRING_LENGTH);
|
||||
if (anchorString != ANCHOR_STRING_VALUE)
|
||||
ThrowHelper.ThrowInvalidDataException($"FishStash started with invalid anchor: {anchorString}");
|
||||
}
|
||||
|
||||
return ref stash;
|
||||
}
|
||||
@@ -41,18 +54,53 @@ public struct FishStash
|
||||
|
||||
internal static nint StaticHandle { get; private set; }
|
||||
|
||||
internal static unsafe ref FishStash FromHandle(IntPtr handle) => ref Unsafe.AsRef<FishStash>(handle.ToPointer());
|
||||
internal static ref FishStash FromHandle(IntPtr handle) => ref Unsafe.AsRef<FishStash>(handle.ToPointer());
|
||||
|
||||
private static unsafe void Create()
|
||||
private static void Create()
|
||||
{
|
||||
Guard.IsEqualTo(ANCHOR_STRING_VALUE.Length, ANCHOR_STRING_LENGTH);
|
||||
|
||||
if (StaticHandle != default)
|
||||
ThrowHelper.ThrowInvalidOperationException("Tried creating duplicate FishStash.");
|
||||
|
||||
var handle = (FishStash*)Marshal.AllocHGlobal(sizeof(FishStash));
|
||||
Unsafe.InitBlockUnaligned(handle, 0, (uint)sizeof(FishStash));
|
||||
|
||||
var handle = Marshal.AllocHGlobal(sizeof(FishStash));
|
||||
Unsafe.InitBlockUnaligned(handle.ToPointer(), 0, (uint)sizeof(FishStash));
|
||||
((FishStash*)handle)->_anchorString = ANCHOR_STRING_VALUE;
|
||||
|
||||
StaticHandle = handle;
|
||||
for (var i = 0; i < ANCHOR_STRING_VALUE.Length; i++)
|
||||
handle->_anchorString[i] = ANCHOR_STRING_VALUE[i];
|
||||
|
||||
StaticHandle = (nint)handle;
|
||||
}
|
||||
|
||||
internal void InitializeActivePrepatchIDs()
|
||||
{
|
||||
var activePrepatches = PerformanceFishMod.AllPrepatchClasses!.SelectMany(static patchClass
|
||||
=> patchClass.Patches.Where(static patch
|
||||
=> patch.Enabled).Select(static patch => patch.IDNumber)).ToArray();
|
||||
|
||||
var activePrepatchCount = activePrepatches.Length;
|
||||
_activePrepatchCount = activePrepatchCount;
|
||||
_activePrepatches = Marshal.AllocHGlobal(sizeof(int) * activePrepatchCount);
|
||||
Marshal.Copy(activePrepatches, 0, _activePrepatches, activePrepatchCount);
|
||||
}
|
||||
|
||||
internal void Release() => Marshal.FreeHGlobal(_activePrepatches);
|
||||
|
||||
internal bool IsPatchActive(FishPrepatchBase patch)
|
||||
{
|
||||
if (_activePrepatchCount <= 0)
|
||||
return false;
|
||||
|
||||
var patchID = patch.IDNumber;
|
||||
|
||||
var activePrepatches = (int*)_activePrepatches;
|
||||
for (var i = _activePrepatchCount; i-- > 0;)
|
||||
{
|
||||
if (activePrepatches[i] == patchID)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static FishStash()
|
||||
@@ -60,4 +108,26 @@ public struct FishStash
|
||||
if (!Log.Ready && InternalFishStashHandle is null)
|
||||
Create();
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => obj is FishStash stash && Equals(stash);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(MessageQueueGCHandle.GetHashCode(), _activePrepatchCount, _activePrepatches.GetHashCode());
|
||||
|
||||
public static bool operator ==(FishStash left, FishStash right) => left.Equals(right);
|
||||
|
||||
public static bool operator !=(FishStash left, FishStash right) => !(left == right);
|
||||
|
||||
public bool Equals(FishStash other)
|
||||
{
|
||||
fixed (char* anchor = _anchorString)
|
||||
{
|
||||
if (anchor != other._anchorString)
|
||||
return false;
|
||||
}
|
||||
|
||||
return MessageQueueGCHandle == other.MessageQueueGCHandle
|
||||
&& _activePrepatchCount == other._activePrepatchCount
|
||||
&& _activePrepatches == other._activePrepatches;
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,9 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
public class FishStashHandle : IDisposable
|
||||
public sealed class FishStashHandle : IDisposable
|
||||
{
|
||||
private static readonly List<nint> _allHandles = new();
|
||||
private static readonly List<nint> _allHandles = [];
|
||||
|
||||
public ref FishStash Stash => ref FishStash.FromHandle(Handle);
|
||||
|
||||
@@ -25,17 +25,18 @@ public class FishStashHandle : IDisposable
|
||||
Handle = (nint)handle;
|
||||
}
|
||||
|
||||
private void ReleaseUnmanagedResources()
|
||||
private void Dispose(bool _)
|
||||
{
|
||||
Stash.Release();
|
||||
Marshal.FreeHGlobal(Handle);
|
||||
_allHandles.Remove(Handle);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~FishStashHandle() => ReleaseUnmanagedResources();
|
||||
~FishStashHandle() => Dispose(false);
|
||||
}
|
||||
@@ -4,11 +4,9 @@
|
||||
// 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;
|
||||
@@ -66,6 +64,8 @@ public static class PrepatchManager
|
||||
// SetReflectionOnly(fishAssembly, previousFishValue, out _);
|
||||
// }
|
||||
|
||||
Application.logMessageReceivedThreaded -= Verse.Log.Notify_MessageReceivedThreadedInternal;
|
||||
|
||||
AddAttributes(module);
|
||||
|
||||
ModifyAllTypes(module);
|
||||
@@ -76,9 +76,9 @@ public static class PrepatchManager
|
||||
|
||||
_ = FishSettings.Instance;
|
||||
|
||||
foreach (var prepatchClass in allPrepatchClasses)
|
||||
prepatchClass.Patches.PatchAll(module);
|
||||
allPrepatchClasses.ApplyPatches(module);
|
||||
|
||||
FishStash.Get.InitializeActivePrepatchIDs();
|
||||
PatchingFinished = true;
|
||||
stopwatch.Stop();
|
||||
Verse.Log.Message($"Performance Fish finished applying prepatches in {
|
||||
@@ -312,7 +312,7 @@ public static class PrepatchManager
|
||||
|
||||
debuggableAttribute.Constructor
|
||||
= module.ImportReference(typeof(DebuggableAttribute)
|
||||
.GetConstructor(new[] { typeof(bool), typeof(bool) }));
|
||||
.GetConstructor([typeof(bool), typeof(bool)]));
|
||||
|
||||
debuggableAttribute.ConstructorArguments.Clear();
|
||||
debuggableAttribute.ConstructorArguments.Add(new(module.TypeSystem.Boolean, false));
|
||||
|
||||
Reference in New Issue
Block a user