Skip to content

Commit

Permalink
Backport MEC~419
Browse files Browse the repository at this point in the history
  • Loading branch information
Equinox- committed Mar 2, 2024
1 parent c90c372 commit 46300e2
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 12 deletions.
3 changes: 3 additions & 0 deletions Meds.Shared/WrapperConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ public class AdjustmentsConfig : MemberwiseEquatable<AdjustmentsConfig>

[XmlElement]
public bool? ReplaceLogger;

[XmlElement("SuppressPatch")]
public List<string> SuppressPatch = new List<string>();
}

public class MetricConfig : MemberwiseEquatable<MetricConfig>
Expand Down
2 changes: 1 addition & 1 deletion Meds.Wrapper/Entrypoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static void Main(string[] args)
throw new Exception("Wrapper should not be invoked manually. [installConfig] [runtimeConfig]");
var cfg = new Configuration(args[0], args[1]);

PatchHelper.PatchStartup(cfg.Install.Adjustments.ReplaceLogger ?? false);
PatchHelper.PatchStartup(cfg.Install);

using var instance = new HostBuilder()
.ConfigureServices(services =>
Expand Down
113 changes: 112 additions & 1 deletion Meds.Wrapper/Shim/MecPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -560,16 +560,127 @@ public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructio
{
foreach (var i in instructions)
{
if (i.opcode == OpCodes.Call && i.operand is MethodBase method && method.DeclaringType == typeof(Enumerable) && method.Name == nameof(Enumerable.First))
if (i.opcode == OpCodes.Call && i.operand is MethodBase method && method.DeclaringType == typeof(Enumerable) &&
method.Name == nameof(Enumerable.First))
{
// Change .First() call to .FirstOrDefault() to push crashes elsewhere.
// This doesn't come close to actually fixing everything, but it's better than nothing.
i.operand = AccessTools.GetDeclaredMethods(typeof(Enumerable))
.First(x => x.Name == nameof(Enumerable.FirstOrDefault) && x.GetParameters().Length == 1)
.MakeGenericMethod(method.GetGenericArguments());
}

yield return i;
}
}
}

// https://communityedition.medievalengineers.com/mantis/view.php?id=419
[HarmonyPatch(typeof(WorkerManager), "NotifyWorkStart")]
[AlwaysPatch(VersionRange = "[,0.7.4)")]
public static class Mec419RareDeadlock_NotifyWorkStart
{
private static ILogger Log => Entrypoint.LoggerFor(typeof(Mec419RareDeadlock_NotifyWorkStart));
private static readonly Type TrackerData = AccessTools.TypeByName("VRage.ParallelWorkers.WorkerManager+TrackerData, VRage.Library");
private static readonly FieldInfo ExecutionReferenceCount = AccessTools.Field(TrackerData, "ExecutionReferenceCount");

public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> stream, ILGenerator ilg)
{
var instructions = stream.ToList();
var hash = instructions
.Aggregate(19L, (a, b) => a * 31 + b.opcode.Value.GetHashCode());
if (hash != 4003687741625888343 || ExecutionReferenceCount == null)
{
Log.ZLogInformation("Not patching NotifyWorkStart since the hash doesn't match ({0})", hash);
return instructions;
}

// arg1 is "workId"
// arg2 is "dequeue"
// local2 is "tracker data"
// local3 is "should run"

var labelReturnShouldRun = ilg.DefineLabel();
var labelSkipUpdate = ilg.DefineLabel();
var labelMaybeUpdate = ilg.DefineLabel();

instructions.AddRange(new[]
{
new CodeInstruction(OpCodes.Ldloc_3).WithLabels(labelReturnShouldRun),
new CodeInstruction(OpCodes.Ret)
});

// 80-85 (ldarg.0, ldfld, ldarg.1, ldloc.2, callvirt, leave) -- original "m_trackedWork[workId] = track;"
instructions[80].WithLabels(labelMaybeUpdate);
instructions.InsertRange(85, new[]
{
new CodeInstruction(OpCodes.Leave, labelReturnShouldRun),
// pop the ldarg.0 or dummy value
new CodeInstruction(OpCodes.Pop).WithLabels(labelSkipUpdate),
new CodeInstruction(OpCodes.Leave, labelReturnShouldRun),
});
instructions.InsertRange(81, new[]
{
// if (!(shouldRun || dequeue)) { goto skipUpdate; }
new CodeInstruction(OpCodes.Ldloc_3),
new CodeInstruction(OpCodes.Ldarg_2),
new CodeInstruction(OpCodes.Or),
new CodeInstruction(OpCodes.Brfalse, labelSkipUpdate),
});

// 77-79 (ldc.i4.0, stloc.3, leave.s) -- original "return false"
instructions.InsertRange(79, ReturnFalseReplacement());

// 65-67 (ldc.i4.0, stloc.3, leave.s) -- original "return false"
instructions.InsertRange(67, ReturnFalseReplacement());

// Adding: var shouldRun = true;
// 33... -- original "track.Queue != WorkerGroupId.Null"
instructions.InsertRange(34, new[]
{
new CodeInstruction(OpCodes.Ldc_I4_1),
new CodeInstruction(OpCodes.Stloc_3)
});

return instructions;

IEnumerable<CodeInstruction> ReturnFalseReplacement() => new[]
{
// track.ExecutionReferenceCount--;
new CodeInstruction(OpCodes.Ldloca_S, 2),
new CodeInstruction(OpCodes.Ldflda, ExecutionReferenceCount),
new CodeInstruction(OpCodes.Dup),
new CodeInstruction(OpCodes.Ldind_I4),
new CodeInstruction(OpCodes.Ldc_I4_1),
new CodeInstruction(OpCodes.Sub),
new CodeInstruction(OpCodes.Stind_I4),
// goto maybe update
new CodeInstruction(OpCodes.Br, labelMaybeUpdate),
};
}
}

// https://communityedition.medievalengineers.com/mantis/view.php?id=419a
[HarmonyPatch(typeof(WorkerManager), "VRage.ParallelWorkers.IWorkerManager.ExecutePendingWork")]
[AlwaysPatch(VersionRange = "[,0.7.4)")]
public static class Mec419RareDeadlock_ExecutePendingWork
{
private static ILogger Log => Entrypoint.LoggerFor(typeof(Mec419RareDeadlock_ExecutePendingWork));

public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> stream)
{
var instructions = stream.ToList();
var hash = instructions
.Aggregate(19L, (a, b) => a * 31 + b.opcode.Value.GetHashCode());
if (hash != -4260687172009927681L)
{
Log.ZLogInformation("Not patching ExecutePendingWork since the hash doesn't match ({0})", hash);
return instructions;
}

// Replace "track.Queue != WorkerGroupId.Null" with "track.Queue == WorkerGroupId.Null"
instructions[42].opcode = OpCodes.Brtrue;
return instructions;
}
}
}
36 changes: 26 additions & 10 deletions Meds.Wrapper/Shim/PatchHelper.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.ExceptionServices;
using HarmonyLib;
using Medieval.ObjectBuilders;
using MedievalEngineersDedicated;
using Meds.Metrics;
using Meds.Shared;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Sandbox;
Expand All @@ -30,6 +29,8 @@ public static class PatchHelper
{
private static readonly Harmony _harmony = new Harmony("meds.wrapper.core");
private static bool ReplaceLogger;
private static readonly HashSet<string> SuppressedPatches = new HashSet<string>();
private static ILogger Log => Entrypoint.LoggerFor(typeof(PatchHelper));

static PatchHelper()
{
Expand All @@ -54,13 +55,18 @@ public static void PatchAlways(bool late)
}
}

public static void PatchStartup(bool replaceLogger)
public static void PatchStartup(RenderedInstallConfig cfg)
{
ReplaceLogger = cfg.Adjustments.ReplaceLogger ?? false;
SuppressedPatches.Clear();
if (cfg.Adjustments.SuppressPatch != null)
foreach (var patch in cfg.Adjustments.SuppressPatch)
SuppressedPatches.Add(patch);

Patch(typeof(PatchWaitForKey));
Patch(typeof(PatchConfigSetup));
Patch(typeof(PatchMinidump));
ReplaceLogger = replaceLogger;
if (replaceLogger)
if (ReplaceLogger)
Patch(typeof(LoggerPatches.PatchReplaceLogger));
}

Expand All @@ -80,6 +86,13 @@ public static void Transpile(MethodBase target, MethodInfo transpiler)

public static void Prefix(MethodBase target, MethodInfo prefix)
{
var declaringType = prefix.DeclaringType;
if (declaringType != null && (SuppressedPatches.Contains(declaringType.Name) || SuppressedPatches.Contains(declaringType.FullName)))
{
Log?.ZLogInformation("Suppressing patch {0}", declaringType.FullName);
return;
}

try
{
var processor = _harmony.CreateProcessor(target);
Expand All @@ -94,13 +107,15 @@ public static void Prefix(MethodBase target, MethodInfo prefix)

public static void Patch(Type type)
{
var results = _harmony.CreateClassProcessor(type).Patch().Select(x => x?.Name).Where(x => x != null)
.ToList();
if (results.Count > 0)
if (SuppressedPatches.Contains(type.Name) || SuppressedPatches.Contains(type.FullName))
{
Entrypoint.LoggerFor(typeof(PatchHelper))?
.ZLogInformationWithPayload(results, "Applied patch {0} ", type.FullName);
Log?.ZLogInformation("Suppressing patch {0}", type.FullName);
return;
}

var results = _harmony.CreateClassProcessor(type).Patch().Select(x => x?.Name).Where(x => x != null).ToList();
if (results.Count > 0)
Log?.ZLogInformationWithPayload(results, "Applied patch {0} ", type.FullName);
}

public static IEnumerable<(MyModContext mod, Type type)> ModTypes(string typeName)
Expand All @@ -112,6 +127,7 @@ public static void Patch(Type type)
.ZLogWarning("Tried to fetch mod types {0} but mod manager isn't initialized", typeName);
yield break;
}

foreach (var kv in mods.Assemblies)
{
var type = kv.Value.GetType(typeName);
Expand Down

0 comments on commit 46300e2

Please sign in to comment.