Skip to content

Instantly share code, notes, and snippets.

@pardeike
Last active April 17, 2019 16:23
Show Gist options
  • Save pardeike/db627ffbb3c004a7ae01850dbc7517cd to your computer and use it in GitHub Desktop.
Save pardeike/db627ffbb3c004a7ae01850dbc7517cd to your computer and use it in GitHub Desktop.
Explaining how Harmony could implement a new type of patch to handle finally blocks
using System;
using System.Reflection;
public static class Program
{
public static void Main(string[] args)
{
try
{
// run original
RunTest(false);
}
catch (AIException ai)
{
Console.WriteLine("Original ==> " + ai);
}
Console.WriteLine("");
try
{
// run patched
RunTest(true);
}
catch (AIException ai)
{
Console.WriteLine("Patched ==> " + ai);
}
Console.ReadKey();
}
public static void RunTest(bool simulatePatching)
{
if (simulatePatching)
PATCHED();
else
ORIGINAL();
}
// ==== original method with some rethrowing logic =================================
public static void ORIGINAL()
{
try
{
A();
}
catch (MovementException mex)
{
throw new AIException("Could not move to target", mex);
}
}
public static void A() { B(); }
public static void B() { C(); }
public static void C() { throw new MovementException("first"); }
// ==== simplified patched method logic =================================
public static void PATCHED()
{
Exception __ex = null;
try
{
// call all prefixes logic here
ORIGINAL();
// call all postfixes logic here
}
catch (Exception ex)
{
__ex = ex;
}
finally
{
var stacktrace = GetStackTrace(__ex, false);
__ex = Finalizer1(__ex);
Finalizer2();
__ex = Finalizer3(__ex);
__ex = Finalizer4(__ex);
if (__ex != null)
throw ApplyStackTrace(__ex, stacktrace);
}
}
// ==== new FINALIZER patch methods =================================
public static Exception Finalizer1(Exception ex)
{
if (ex == null) return null; // always check for null!
// replace old exception with a new exception
var mex = new MovementException("second");
// fake inner exceptions origin to match original
ApplyStackTrace(mex, GetStackTrace(ex.InnerException));
return new AIException("Could not move to target", mex);
}
public static void Finalizer2()
{
// void Finalizer methods can be used to execute code regardless
// of what happens in the original or any patch method
//
// a void return value will not alter the exception
}
public static Exception Finalizer3(Exception ex)
{
// to suppress exceptions, return null
if (ex is ArgumentNullException)
return null;
return ex;
}
public static Exception Finalizer4(Exception ex)
{
// Finalizer handlers must expect null exception input
// since any of the previous Finalizer handlers might
// want to suppress the original exception
if (ex != null)
{
// do something with ex here
}
return ex;
}
// ==== Harmony internal methods =================================
private static string GetStackTraceString(this Exception exception)
{
var f_remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
return f_remoteStackTraceString?.GetValue(exception) as string;
}
private static void SetStackTraceString(this Exception exception, string trace)
{
var f_remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic);
f_remoteStackTraceString?.SetValue(exception, trace);
}
private static void PreserveStackTrace(Exception exception)
{
var m_InternalPreserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic);
m_InternalPreserveStackTrace?.Invoke(exception, null);
}
private static string GetStackTrace(Exception exception, bool trimEnd = true)
{
PreserveStackTrace(exception);
var trace = exception.GetStackTraceString() ?? "";
return trimEnd ? trace.TrimEnd() : trace;
}
private static Exception ApplyStackTrace(Exception exception, string stacktrace)
{
exception.SetStackTraceString(stacktrace);
return exception;
}
}
[Serializable()]
public class MovementException : System.Exception
{
public MovementException(string name) : base(name) { }
}
[Serializable()]
public class AIException : System.Exception
{
public AIException(string name, Exception ex) : base(name, ex) { }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment