ghorsington 3 years ago
commit
0d8e886625
33 changed files with 3891 additions and 0 deletions
  1. 433 0
      .gitignore
  2. 5 0
      .idea/.idea.Harmony12Interop/.idea/codeStyles/codeStyleConfig.xml
  3. 6 0
      .idea/.idea.Harmony12Interop/.idea/discord.xml
  4. 4 0
      .idea/.idea.Harmony12Interop/.idea/encodings.xml
  5. 8 0
      .idea/.idea.Harmony12Interop/.idea/indexLayout.xml
  6. 8 0
      .idea/.idea.Harmony12Interop/.idea/modules.xml
  7. 6 0
      .idea/.idea.Harmony12Interop/.idea/projectSettingsUpdater.xml
  8. 7 0
      .idea/.idea.Harmony12Interop/riderModule.iml
  9. 16 0
      Harmony12Interop.sln
  10. 312 0
      Harmony12Interop/Attributes.cs
  11. 62 0
      Harmony12Interop/CodeInstruction.cs
  12. 43 0
      Harmony12Interop/Extras/DelegateTypeFactory.cs
  13. 123 0
      Harmony12Interop/Extras/FastAccess.cs
  14. 179 0
      Harmony12Interop/Extras/MethodInvoker.cs
  15. 79 0
      Harmony12Interop/Harmony12Interop.csproj
  16. 188 0
      Harmony12Interop/HarmonyInstance.cs
  17. 141 0
      Harmony12Interop/HarmonyMethod.cs
  18. 97 0
      Harmony12Interop/HarmonySharedState.cs
  19. 315 0
      Harmony12Interop/ILCopying/Emitter.cs
  20. 148 0
      Harmony12Interop/ILCopying/ILInstruction.cs
  21. 194 0
      Harmony12Interop/Patch.cs
  22. 74 0
      Harmony12Interop/PatchFunctions.cs
  23. 301 0
      Harmony12Interop/PatchProcessor.cs
  24. 15 0
      Harmony12Interop/Priority.cs
  25. 10 0
      Harmony12Interop/Properties/AssemblyInfo.cs
  26. 95 0
      Harmony12Interop/Tools/AccessCache.cs
  27. 398 0
      Harmony12Interop/Tools/AccessTools.cs
  28. 82 0
      Harmony12Interop/Tools/Extensions.cs
  29. 119 0
      Harmony12Interop/Tools/FileLog.cs
  30. 26 0
      Harmony12Interop/Tools/PatchTools.cs
  31. 61 0
      Harmony12Interop/Tools/SymbolExtensions.cs
  32. 298 0
      Harmony12Interop/Tools/Traverse.cs
  33. 38 0
      Harmony12Interop/Transpilers.cs

+ 433 - 0
.gitignore

@@ -0,0 +1,433 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/csharp,rider
+# Edit at https://www.toptal.com/developers/gitignore?templates=csharp,rider
+
+### Csharp ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*[.json, .xml, .info]
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+### Rider ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+# End of https://www.toptal.com/developers/gitignore/api/csharp,rider

+ 5 - 0
.idea/.idea.Harmony12Interop/.idea/codeStyles/codeStyleConfig.xml

@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
+  </state>
+</component>

+ 6 - 0
.idea/.idea.Harmony12Interop/.idea/discord.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DiscordProjectSettings">
+    <option name="show" value="PROJECT" />
+  </component>
+</project>

+ 4 - 0
.idea/.idea.Harmony12Interop/.idea/encodings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
+</project>

+ 8 - 0
.idea/.idea.Harmony12Interop/.idea/indexLayout.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ContentModelUserStore">
+    <attachedFolders />
+    <explicitIncludes />
+    <explicitExcludes />
+  </component>
+</project>

+ 8 - 0
.idea/.idea.Harmony12Interop/.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/.idea.Harmony12Interop/riderModule.iml" filepath="$PROJECT_DIR$/.idea/.idea.Harmony12Interop/riderModule.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/.idea.Harmony12Interop/.idea/projectSettingsUpdater.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RiderProjectSettingsUpdater">
+    <option name="vcsConfiguration" value="2" />
+  </component>
+</project>

+ 7 - 0
.idea/.idea.Harmony12Interop/riderModule.iml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="RIDER_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$/../.." />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 16 - 0
Harmony12Interop.sln

@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Harmony12Interop", "Harmony12Interop\Harmony12Interop.csproj", "{92D7778F-4295-483C-AAEA-5D3F9264E917}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{92D7778F-4295-483C-AAEA-5D3F9264E917}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{92D7778F-4295-483C-AAEA-5D3F9264E917}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{92D7778F-4295-483C-AAEA-5D3F9264E917}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{92D7778F-4295-483C-AAEA-5D3F9264E917}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+EndGlobal

+ 312 - 0
Harmony12Interop/Attributes.cs

@@ -0,0 +1,312 @@
+using System;
+using System.Collections.Generic;
+
+namespace Harmony
+{
+	public enum MethodType
+	{
+		Normal,
+		Getter,
+		Setter,
+		Constructor,
+		StaticConstructor
+	}
+
+	[Obsolete("This enum will be removed in the next major version. To define special methods, use MethodType")]
+	public enum PropertyMethod
+	{
+		Getter,
+		Setter
+	}
+
+	public enum ArgumentType
+	{
+		Normal,
+		Ref,
+		Out,
+		Pointer
+	}
+
+	public enum HarmonyPatchType
+	{
+		All,
+		Prefix,
+		Postfix,
+		Transpiler
+	}
+
+	public class HarmonyAttribute : Attribute
+	{
+		public HarmonyMethod info = new HarmonyMethod();
+	}
+
+	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
+	public class HarmonyPatch : HarmonyAttribute
+	{
+		// no argument (for use with TargetMethod)
+
+		public HarmonyPatch()
+		{
+		}
+
+		// starting with 'Type'
+
+		public HarmonyPatch(Type declaringType)
+		{
+			info.declaringType = declaringType;
+		}
+
+		public HarmonyPatch(Type declaringType, Type[] argumentTypes)
+		{
+			info.declaringType = declaringType;
+			info.argumentTypes = argumentTypes;
+		}
+
+		public HarmonyPatch(Type declaringType, string methodName)
+		{
+			info.declaringType = declaringType;
+			info.methodName = methodName;
+		}
+
+		public HarmonyPatch(Type declaringType, string methodName, params Type[] argumentTypes)
+		{
+			info.declaringType = declaringType;
+			info.methodName = methodName;
+			info.argumentTypes = argumentTypes;
+		}
+
+		public HarmonyPatch(Type declaringType, string methodName, Type[] argumentTypes, ArgumentType[] argumentVariations)
+		{
+			info.declaringType = declaringType;
+			info.methodName = methodName;
+			ParseSpecialArguments(argumentTypes, argumentVariations);
+		}
+
+		public HarmonyPatch(Type declaringType, MethodType methodType)
+		{
+			info.declaringType = declaringType;
+			info.methodType = methodType;
+		}
+
+		public HarmonyPatch(Type declaringType, MethodType methodType, params Type[] argumentTypes)
+		{
+			info.declaringType = declaringType;
+			info.methodType = methodType;
+			info.argumentTypes = argumentTypes;
+		}
+
+		public HarmonyPatch(Type declaringType, MethodType methodType, Type[] argumentTypes, ArgumentType[] argumentVariations)
+		{
+			info.declaringType = declaringType;
+			info.methodType = methodType;
+			ParseSpecialArguments(argumentTypes, argumentVariations);
+		}
+
+		public HarmonyPatch(Type declaringType, string propertyName, MethodType methodType)
+		{
+			info.declaringType = declaringType;
+			info.methodName = propertyName;
+			info.methodType = methodType;
+		}
+
+		// starting with 'string'
+
+		public HarmonyPatch(string methodName)
+		{
+			info.methodName = methodName;
+		}
+
+		public HarmonyPatch(string methodName, params Type[] argumentTypes)
+		{
+			info.methodName = methodName;
+			info.argumentTypes = argumentTypes;
+		}
+
+		public HarmonyPatch(string methodName, Type[] argumentTypes, ArgumentType[] argumentVariations)
+		{
+			info.methodName = methodName;
+			ParseSpecialArguments(argumentTypes, argumentVariations);
+		}
+
+		public HarmonyPatch(string propertyName, MethodType methodType)
+		{
+			info.methodName = propertyName;
+			info.methodType = methodType;
+		}
+
+		// starting with 'MethodType'
+
+		public HarmonyPatch(MethodType methodType)
+		{
+			info.methodType = methodType;
+		}
+
+		public HarmonyPatch(MethodType methodType, params Type[] argumentTypes)
+		{
+			info.methodType = methodType;
+			info.argumentTypes = argumentTypes;
+		}
+
+		public HarmonyPatch(MethodType methodType, Type[] argumentTypes, ArgumentType[] argumentVariations)
+		{
+			info.methodType = methodType;
+			ParseSpecialArguments(argumentTypes, argumentVariations);
+		}
+
+		// starting with 'Type[]'
+
+		public HarmonyPatch(Type[] argumentTypes)
+		{
+			info.argumentTypes = argumentTypes;
+		}
+
+		public HarmonyPatch(Type[] argumentTypes, ArgumentType[] argumentVariations)
+		{
+			ParseSpecialArguments(argumentTypes, argumentVariations);
+		}
+
+		// Obsolete attributes
+
+		[Obsolete("This attribute will be removed in the next major version. Use HarmonyPatch together with MethodType.Getter or MethodType.Setter instead")]
+		public HarmonyPatch(string propertyName, PropertyMethod type)
+		{
+			info.methodName = propertyName;
+			info.methodType = type == PropertyMethod.Getter ? MethodType.Getter : MethodType.Setter;
+		}
+
+		//
+
+		private void ParseSpecialArguments(Type[] argumentTypes, ArgumentType[] argumentVariations)
+		{
+			if (argumentVariations == null || argumentVariations.Length == 0)
+			{
+				info.argumentTypes = argumentTypes;
+				return;
+			}
+
+			if (argumentTypes.Length < argumentVariations.Length)
+				throw new ArgumentException("argumentVariations contains more elements than argumentTypes", nameof(argumentVariations));
+
+			var types = new List<Type>();
+			for (var i = 0; i < argumentTypes.Length; i++)
+			{
+				var type = argumentTypes[i];
+				switch (argumentVariations[i])
+				{
+					case ArgumentType.Ref:
+					case ArgumentType.Out:
+						type = type.MakeByRefType();
+						break;
+					case ArgumentType.Pointer:
+						type = type.MakePointerType();
+						break;
+				}
+				types.Add(type);
+			}
+			info.argumentTypes = types.ToArray();
+		}
+	}
+
+	[AttributeUsage(AttributeTargets.Class)]
+	public class HarmonyPatchAll : HarmonyAttribute
+	{
+		public HarmonyPatchAll()
+		{
+		}
+	}
+
+	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+	public class HarmonyPriority : HarmonyAttribute
+	{
+		public HarmonyPriority(int prioritiy)
+		{
+			info.prioritiy = prioritiy;
+		}
+	}
+
+	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+	public class HarmonyBefore : HarmonyAttribute
+	{
+		public HarmonyBefore(params string[] before)
+		{
+			info.before = before;
+		}
+	}
+
+	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+	public class HarmonyAfter : HarmonyAttribute
+	{
+		public HarmonyAfter(params string[] after)
+		{
+			info.after = after;
+		}
+	}
+
+	// If you don't want to use the special method names you can annotate
+	// using the following attributes:
+
+	[AttributeUsage(AttributeTargets.Method)]
+	public class HarmonyPrepare : Attribute
+	{
+	}
+
+	[AttributeUsage(AttributeTargets.Method)]
+	public class HarmonyCleanup : Attribute
+	{
+	}
+
+	[AttributeUsage(AttributeTargets.Method)]
+	public class HarmonyTargetMethod : Attribute
+	{
+	}
+
+	[AttributeUsage(AttributeTargets.Method)]
+	public class HarmonyTargetMethods : Attribute
+	{
+	}
+
+	[AttributeUsage(AttributeTargets.Method)]
+	public class HarmonyPrefix : Attribute
+	{
+	}
+
+	[AttributeUsage(AttributeTargets.Method)]
+	public class HarmonyPostfix : Attribute
+	{
+	}
+
+	[AttributeUsage(AttributeTargets.Method)]
+	public class HarmonyTranspiler : Attribute
+	{
+	}
+
+	[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
+	public class HarmonyArgument : Attribute
+	{
+		public string OriginalName { get; private set; }
+		public int Index { get; private set; }
+		public string NewName { get; private set; }
+
+		public HarmonyArgument(string originalName) : this(originalName, null)
+		{
+		}
+
+		public HarmonyArgument(int index) : this(index, null)
+		{
+		}
+
+		public HarmonyArgument(string originalName, string newName)
+		{
+			OriginalName = originalName;
+			Index = -1;
+			NewName = newName;
+		}
+
+		public HarmonyArgument(int index, string name)
+		{
+			OriginalName = null;
+			Index = index;
+			NewName = name;
+		}
+	}
+}

+ 62 - 0
Harmony12Interop/CodeInstruction.cs

@@ -0,0 +1,62 @@
+using Harmony.ILCopying;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection.Emit;
+
+namespace Harmony
+{
+	public class CodeInstruction
+	{
+		public OpCode opcode;
+		public object operand;
+		public List<Label> labels = new List<Label>();
+		public List<ExceptionBlock> blocks = new List<ExceptionBlock>();
+
+		public CodeInstruction(OpCode opcode, object operand = null)
+		{
+			this.opcode = opcode;
+			this.operand = operand;
+		}
+
+		public CodeInstruction(CodeInstruction instruction)
+		{
+			opcode = instruction.opcode;
+			operand = instruction.operand;
+			labels = instruction.labels.ToArray().ToList();
+		}
+
+		public CodeInstruction Clone()
+		{
+			return new CodeInstruction(this) { labels = new List<Label>() };
+		}
+
+		public CodeInstruction Clone(OpCode opcode)
+		{
+			var instruction = new CodeInstruction(this) { labels = new List<Label>() };
+			instruction.opcode = opcode;
+			return instruction;
+		}
+
+		public CodeInstruction Clone(OpCode opcode, object operand)
+		{
+			var instruction = new CodeInstruction(this) { labels = new List<Label>() };
+			instruction.opcode = opcode;
+			instruction.operand = operand;
+			return instruction;
+		}
+
+		public override string ToString()
+		{
+			var list = new List<string>();
+			foreach (var label in labels)
+				list.Add("Label" + label.GetHashCode());
+			foreach (var block in blocks)
+				list.Add("EX_" + block.blockType.ToString().Replace("Block", ""));
+
+			var extras = list.Count > 0 ? " [" + string.Join(", ", list.ToArray()) + "]" : "";
+			var operandStr = Emitter.FormatArgument(operand);
+			if (operandStr != "") operandStr = " " + operandStr;
+			return opcode + operandStr + extras;
+		}
+	}
+}

+ 43 - 0
Harmony12Interop/Extras/DelegateTypeFactory.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Harmony
+{
+	public class DelegateTypeFactory
+	{
+		readonly ModuleBuilder module;
+
+		static int counter;
+		public DelegateTypeFactory()
+		{
+			counter++;
+			var name = new AssemblyName("HarmonyDTFAssembly" + counter);
+			var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
+			module = assembly.DefineDynamicModule("HarmonyDTFModule" + counter);
+		}
+
+		public Type CreateDelegateType(MethodInfo method)
+		{
+			var attr = TypeAttributes.Sealed | TypeAttributes.Public;
+			var typeBuilder = module.DefineType("HarmonyDTFType" + counter, attr, typeof(MulticastDelegate));
+
+			var constructor = typeBuilder.DefineConstructor(
+				 MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public,
+				 CallingConventions.Standard, new[] { typeof(object), typeof(IntPtr) });
+			constructor.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
+
+			var parameters = method.GetParameters();
+
+			var invokeMethod = typeBuilder.DefineMethod(
+				 "Invoke", MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Public,
+				 method.ReturnType, parameters.Types());
+			invokeMethod.SetImplementationFlags(MethodImplAttributes.CodeTypeMask);
+
+			for (var i = 0; i < parameters.Length; i++)
+				invokeMethod.DefineParameter(i + 1, ParameterAttributes.None, parameters[i].Name);
+
+			return typeBuilder.CreateType();
+		}
+	}
+}

+ 123 - 0
Harmony12Interop/Extras/FastAccess.cs

@@ -0,0 +1,123 @@
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Harmony
+{
+	// Based on https://www.codeproject.com/Articles/14973/Dynamic-Code-Generation-vs-Reflection
+
+	public delegate object GetterHandler(object source);
+	public delegate void SetterHandler(object source, object value);
+	public delegate object InstantiationHandler();
+
+	public class FastAccess
+	{
+		public static InstantiationHandler CreateInstantiationHandler(Type type)
+		{
+			var constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[0], null);
+			if (constructorInfo == null)
+			{
+				throw new ApplicationException(string.Format("The type {0} must declare an empty constructor (the constructor may be private, internal, protected, protected internal, or public).", type));
+			}
+
+			var dynamicMethod = new DynamicMethod("InstantiateObject_" + type.Name, MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(object), null, type, true);
+			var generator = dynamicMethod.GetILGenerator();
+			generator.Emit(OpCodes.Newobj, constructorInfo);
+			generator.Emit(OpCodes.Ret);
+			return (InstantiationHandler)dynamicMethod.CreateDelegate(typeof(InstantiationHandler));
+		}
+
+		public static GetterHandler CreateGetterHandler(PropertyInfo propertyInfo)
+		{
+			var getMethodInfo = propertyInfo.GetGetMethod(true);
+			var dynamicGet = CreateGetDynamicMethod(propertyInfo.DeclaringType);
+			var getGenerator = dynamicGet.GetILGenerator();
+
+			getGenerator.Emit(OpCodes.Ldarg_0);
+			getGenerator.Emit(OpCodes.Call, getMethodInfo);
+			BoxIfNeeded(getMethodInfo.ReturnType, getGenerator);
+			getGenerator.Emit(OpCodes.Ret);
+
+			return (GetterHandler)dynamicGet.CreateDelegate(typeof(GetterHandler));
+		}
+
+		public static GetterHandler CreateGetterHandler(FieldInfo fieldInfo)
+		{
+			var dynamicGet = CreateGetDynamicMethod(fieldInfo.DeclaringType);
+			var getGenerator = dynamicGet.GetILGenerator();
+
+			getGenerator.Emit(OpCodes.Ldarg_0);
+			getGenerator.Emit(OpCodes.Ldfld, fieldInfo);
+			BoxIfNeeded(fieldInfo.FieldType, getGenerator);
+			getGenerator.Emit(OpCodes.Ret);
+
+			return (GetterHandler)dynamicGet.CreateDelegate(typeof(GetterHandler));
+		}
+
+		public static GetterHandler CreateFieldGetter(Type type, params string[] names)
+		{
+			foreach (var name in names)
+			{
+				if (AccessTools.Field(typeof(ILGenerator), name) != null)
+					return CreateGetterHandler(AccessTools.Field(type, name));
+
+				if (AccessTools.Property(typeof(ILGenerator), name) != null)
+					return CreateGetterHandler(AccessTools.Property(type, name));
+			}
+			return null;
+		}
+
+		public static SetterHandler CreateSetterHandler(PropertyInfo propertyInfo)
+		{
+			var setMethodInfo = propertyInfo.GetSetMethod(true);
+			var dynamicSet = CreateSetDynamicMethod(propertyInfo.DeclaringType);
+			var setGenerator = dynamicSet.GetILGenerator();
+
+			setGenerator.Emit(OpCodes.Ldarg_0);
+			setGenerator.Emit(OpCodes.Ldarg_1);
+			UnboxIfNeeded(setMethodInfo.GetParameters()[0].ParameterType, setGenerator);
+			setGenerator.Emit(OpCodes.Call, setMethodInfo);
+			setGenerator.Emit(OpCodes.Ret);
+
+			return (SetterHandler)dynamicSet.CreateDelegate(typeof(SetterHandler));
+		}
+
+		public static SetterHandler CreateSetterHandler(FieldInfo fieldInfo)
+		{
+			var dynamicSet = CreateSetDynamicMethod(fieldInfo.DeclaringType);
+			var setGenerator = dynamicSet.GetILGenerator();
+
+			setGenerator.Emit(OpCodes.Ldarg_0);
+			setGenerator.Emit(OpCodes.Ldarg_1);
+			UnboxIfNeeded(fieldInfo.FieldType, setGenerator);
+			setGenerator.Emit(OpCodes.Stfld, fieldInfo);
+			setGenerator.Emit(OpCodes.Ret);
+
+			return (SetterHandler)dynamicSet.CreateDelegate(typeof(SetterHandler));
+		}
+
+		//
+
+		static DynamicMethod CreateGetDynamicMethod(Type type)
+		{
+			return new DynamicMethod("DynamicGet_" + type.Name, typeof(object), new Type[] { typeof(object) }, type, true);
+		}
+
+		static DynamicMethod CreateSetDynamicMethod(Type type)
+		{
+			return new DynamicMethod("DynamicSet_" + type.Name, typeof(void), new Type[] { typeof(object), typeof(object) }, type, true);
+		}
+
+		static void BoxIfNeeded(Type type, ILGenerator generator)
+		{
+			if (type.IsValueType)
+				generator.Emit(OpCodes.Box, type);
+		}
+
+		static void UnboxIfNeeded(Type type, ILGenerator generator)
+		{
+			if (type.IsValueType)
+				generator.Emit(OpCodes.Unbox_Any, type);
+		}
+	}
+}

+ 179 - 0
Harmony12Interop/Extras/MethodInvoker.cs

@@ -0,0 +1,179 @@
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Harmony
+{
+	// Based on https://www.codeproject.com/Articles/14593/A-General-Fast-Method-Invoker
+
+	public delegate object FastInvokeHandler(object target, object[] paramters);
+
+	public class MethodInvoker
+	{
+		public static FastInvokeHandler GetHandler(DynamicMethod methodInfo, Module module)
+		{
+			return Handler(methodInfo, module);
+		}
+
+		public static FastInvokeHandler GetHandler(MethodInfo methodInfo)
+		{
+			return Handler(methodInfo, methodInfo.DeclaringType.Module);
+		}
+
+		static FastInvokeHandler Handler(MethodInfo methodInfo, Module module, bool directBoxValueAccess = false)
+		{
+			var dynamicMethod = new DynamicMethod("FastInvoke_" + methodInfo.Name + "_" + (directBoxValueAccess ? "direct" : "indirect"), typeof(object), new Type[] { typeof(object), typeof(object[]) }, module, true);
+			var il = dynamicMethod.GetILGenerator();
+
+			if (!methodInfo.IsStatic)
+			{
+				il.Emit(OpCodes.Ldarg_0);
+				EmitUnboxIfNeeded(il, methodInfo.DeclaringType);
+			}
+
+			var generateLocalBoxValuePtr = true;
+			var ps = methodInfo.GetParameters();
+			for (var i = 0; i < ps.Length; i++)
+			{
+				var argType = ps[i].ParameterType;
+				var argIsByRef = argType.IsByRef;
+				if (argIsByRef)
+					argType = argType.GetElementType();
+				var argIsValueType = argType.IsValueType;
+
+				if (argIsByRef && argIsValueType && !directBoxValueAccess)
+				{
+					// used later when storing back the reference to the new box in the array.
+					il.Emit(OpCodes.Ldarg_1);
+					EmitFastInt(il, i);
+				}
+
+				il.Emit(OpCodes.Ldarg_1);
+				EmitFastInt(il, i);
+
+				if (argIsByRef && !argIsValueType)
+				{
+					il.Emit(OpCodes.Ldelema, typeof(object));
+				}
+				else
+				{
+					il.Emit(OpCodes.Ldelem_Ref);
+					if (argIsValueType)
+					{
+						if (!argIsByRef || !directBoxValueAccess)
+						{
+							// if !directBoxValueAccess, create a new box if required
+							il.Emit(OpCodes.Unbox_Any, argType);
+							if (argIsByRef)
+							{
+								// box back
+								il.Emit(OpCodes.Box, argType);
+
+								// store new box value address to local 0
+								il.Emit(OpCodes.Dup);
+								il.Emit(OpCodes.Unbox, argType);
+								if (generateLocalBoxValuePtr)
+								{
+									generateLocalBoxValuePtr = false;
+									// Yes, you're seeing this right - a local of type void* to store the box value address!
+									il.DeclareLocal(typeof(void*), true);
+								}
+								il.Emit(OpCodes.Stloc_0);
+
+								// arr and index set up already
+								il.Emit(OpCodes.Stelem_Ref);
+
+								// load address back to stack
+								il.Emit(OpCodes.Ldloc_0);
+							}
+						}
+						else
+						{
+							// if directBoxValueAccess, emit unbox (get value address)
+							il.Emit(OpCodes.Unbox, argType);
+						}
+					}
+				}
+			}
+
+#pragma warning disable XS0001
+			if (methodInfo.IsStatic)
+				il.EmitCall(OpCodes.Call, methodInfo, null);
+			else
+				il.EmitCall(OpCodes.Callvirt, methodInfo, null);
+#pragma warning restore XS0001
+
+			if (methodInfo.ReturnType == typeof(void))
+				il.Emit(OpCodes.Ldnull);
+			else
+				EmitBoxIfNeeded(il, methodInfo.ReturnType);
+
+			il.Emit(OpCodes.Ret);
+
+			var invoder = (FastInvokeHandler)dynamicMethod.CreateDelegate(typeof(FastInvokeHandler));
+			return invoder;
+		}
+
+		static void EmitCastToReference(ILGenerator il, Type type)
+		{
+			if (type.IsValueType)
+				il.Emit(OpCodes.Unbox_Any, type);
+			else
+				il.Emit(OpCodes.Castclass, type);
+		}
+
+		static void EmitUnboxIfNeeded(ILGenerator il, Type type)
+		{
+			if (type.IsValueType)
+				il.Emit(OpCodes.Unbox_Any, type);
+		}
+
+		static void EmitBoxIfNeeded(ILGenerator il, Type type)
+		{
+			if (type.IsValueType)
+				il.Emit(OpCodes.Box, type);
+		}
+
+		static void EmitFastInt(ILGenerator il, int value)
+		{
+			switch (value)
+			{
+				case -1:
+					il.Emit(OpCodes.Ldc_I4_M1);
+					return;
+				case 0:
+					il.Emit(OpCodes.Ldc_I4_0);
+					return;
+				case 1:
+					il.Emit(OpCodes.Ldc_I4_1);
+					return;
+				case 2:
+					il.Emit(OpCodes.Ldc_I4_2);
+					return;
+				case 3:
+					il.Emit(OpCodes.Ldc_I4_3);
+					return;
+				case 4:
+					il.Emit(OpCodes.Ldc_I4_4);
+					return;
+				case 5:
+					il.Emit(OpCodes.Ldc_I4_5);
+					return;
+				case 6:
+					il.Emit(OpCodes.Ldc_I4_6);
+					return;
+				case 7:
+					il.Emit(OpCodes.Ldc_I4_7);
+					return;
+				case 8:
+					il.Emit(OpCodes.Ldc_I4_8);
+					return;
+			}
+
+			if (value > -129 && value < 128)
+				il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
+			else
+				il.Emit(OpCodes.Ldc_I4, value);
+		}
+	}
+}

+ 79 - 0
Harmony12Interop/Harmony12Interop.csproj

@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+    <PropertyGroup>
+        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+        <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+        <ProjectGuid>{92D7778F-4295-483C-AAEA-5D3F9264E917}</ProjectGuid>
+        <OutputType>Library</OutputType>
+        <AppDesignerFolder>Properties</AppDesignerFolder>
+        <RootNamespace>0Harmony12</RootNamespace>
+        <AssemblyName>0Harmony12</AssemblyName>
+        <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+        <FileAlignment>512</FileAlignment>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+        <PlatformTarget>AnyCPU</PlatformTarget>
+        <DebugSymbols>true</DebugSymbols>
+        <DebugType>full</DebugType>
+        <Optimize>false</Optimize>
+        <OutputPath>bin\Debug\</OutputPath>
+        <DefineConstants>DEBUG;TRACE</DefineConstants>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+        <PlatformTarget>AnyCPU</PlatformTarget>
+        <DebugType>pdbonly</DebugType>
+        <Optimize>true</Optimize>
+        <OutputPath>bin\Release\</OutputPath>
+        <DefineConstants>TRACE</DefineConstants>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    </PropertyGroup>
+    <ItemGroup>
+        <Reference Include="HarmonyXInterop, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
+          <HintPath>..\..\lib\HarmonyXInterop.dll</HintPath>
+        </Reference>
+        <Reference Include="System" />
+        <Reference Include="System.Core" />
+        <Reference Include="System.Data" />
+        <Reference Include="System.Xml" />
+    </ItemGroup>
+    <ItemGroup>
+        <Compile Include="Attributes.cs" />
+        <Compile Include="CodeInstruction.cs" />
+        <Compile Include="Extras\DelegateTypeFactory.cs" />
+        <Compile Include="Extras\FastAccess.cs" />
+        <Compile Include="Extras\MethodInvoker.cs" />
+        <Compile Include="HarmonyInstance.cs" />
+        <Compile Include="HarmonyMethod.cs" />
+        <Compile Include="HarmonySharedState.cs" />
+        <Compile Include="ILCopying\Emitter.cs" />
+        <Compile Include="ILCopying\ILInstruction.cs" />
+        <Compile Include="Patch.cs" />
+        <Compile Include="PatchFunctions.cs" />
+        <Compile Include="PatchProcessor.cs" />
+        <Compile Include="Priority.cs" />
+        <Compile Include="Properties\AssemblyInfo.cs" />
+        <Compile Include="Tools\AccessCache.cs" />
+        <Compile Include="Tools\AccessTools.cs" />
+        <Compile Include="Tools\Extensions.cs" />
+        <Compile Include="Tools\FileLog.cs" />
+        <Compile Include="Tools\PatchTools.cs" />
+        <Compile Include="Tools\SymbolExtensions.cs" />
+        <Compile Include="Tools\Traverse.cs" />
+        <Compile Include="Transpilers.cs" />
+    </ItemGroup>
+    <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+    <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+         Other similar extension points exist, see Microsoft.Common.targets.
+    <Target Name="BeforeBuild">
+    </Target>
+    <Target Name="AfterBuild">
+    </Target>
+    -->
+
+</Project>

+ 188 - 0
Harmony12Interop/HarmonyInstance.cs

@@ -0,0 +1,188 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Harmony
+{
+	public class Patches
+	{
+		public readonly ReadOnlyCollection<Patch> Prefixes;
+		public readonly ReadOnlyCollection<Patch> Postfixes;
+		public readonly ReadOnlyCollection<Patch> Transpilers;
+
+		public ReadOnlyCollection<string> Owners
+		{
+			get
+			{
+				var result = new HashSet<string>();
+				result.UnionWith(Prefixes.Select(p => p.owner));
+				result.UnionWith(Postfixes.Select(p => p.owner));
+				result.UnionWith(Transpilers.Select(p => p.owner));
+				return result.ToList().AsReadOnly();
+			}
+		}
+
+		public Patches(Patch[] prefixes, Patch[] postfixes, Patch[] transpilers)
+		{
+			if (prefixes == null) prefixes = new Patch[0];
+			if (postfixes == null) postfixes = new Patch[0];
+			if (transpilers == null) transpilers = new Patch[0];
+
+			Prefixes = prefixes.ToList().AsReadOnly();
+			Postfixes = postfixes.ToList().AsReadOnly();
+			Transpilers = transpilers.ToList().AsReadOnly();
+		}
+	}
+
+	public class HarmonyInstance
+	{
+		readonly string id;
+		public string Id => id;
+		public static bool DEBUG = false;
+
+		private static bool selfPatchingDone = false;
+
+		HarmonyInstance(string id)
+		{
+			if (DEBUG)
+			{
+				var assembly = typeof(HarmonyInstance).Assembly;
+				var version = assembly.GetName().Version;
+				var location = assembly.Location;
+				if (location == null || location == "") location = new Uri(assembly.CodeBase).LocalPath;
+				FileLog.Log("### Harmony id=" + id + ", version=" + version + ", location=" + location);
+				var callingMethod = GetOutsideCaller();
+				var callingAssembly = callingMethod.DeclaringType.Assembly;
+				location = callingAssembly.Location;
+				if (location == null || location == "") location = new Uri(callingAssembly.CodeBase).LocalPath;
+				FileLog.Log("### Started from " + callingMethod.FullDescription() + ", location " + location);
+				FileLog.Log("### At " + DateTime.Now.ToString("yyyy-MM-dd hh.mm.ss"));
+			}
+
+			this.id = id;
+
+			if (!selfPatchingDone)
+			{
+				selfPatchingDone = true;
+			}
+		}
+
+		public static HarmonyInstance Create(string id)
+		{
+			if (id == null) throw new Exception("id cannot be null");
+			return new HarmonyInstance(id);
+		}
+
+		private MethodBase GetOutsideCaller()
+		{
+			var trace = new StackTrace(true);
+			foreach (var frame in trace.GetFrames())
+			{
+				var method = frame.GetMethod();
+				if (method.DeclaringType.Namespace != typeof(HarmonyInstance).Namespace)
+					return method;
+			}
+			throw new Exception("Unexpected end of stack trace");
+		}
+
+		//
+
+		public void PatchAll()
+		{
+			var method = new StackTrace().GetFrame(1).GetMethod();
+			var assembly = method.ReflectedType.Assembly;
+			PatchAll(assembly);
+		}
+
+		public void PatchAll(Assembly assembly)
+		{
+			assembly.GetTypes().Do(type =>
+			{
+				var parentMethodInfos = type.GetHarmonyMethods();
+				if (parentMethodInfos != null && parentMethodInfos.Count() > 0)
+				{
+					var info = HarmonyMethod.Merge(parentMethodInfos);
+					var processor = new PatchProcessor(this, type, info);
+					processor.Patch();
+				}
+			});
+		}
+
+		public DynamicMethod Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null)
+		{
+			var processor = new PatchProcessor(this, new List<MethodBase> { original }, prefix, postfix, transpiler);
+			return processor.Patch().FirstOrDefault();
+		}
+
+		public void UnpatchAll(string harmonyID = null)
+		{
+			bool IDCheck(Patch patchInfo) => harmonyID == null || patchInfo.owner == harmonyID;
+
+			var originals = GetPatchedMethods().ToList();
+			foreach (var original in originals)
+			{
+				var info = GetPatchInfo(original);
+				info.Prefixes.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.patch));
+				info.Postfixes.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.patch));
+				info.Transpilers.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.patch));
+			}
+		}
+
+		public void Unpatch(MethodBase original, HarmonyPatchType type, string harmonyID = null)
+		{
+			var processor = new PatchProcessor(this, new List<MethodBase> { original });
+			processor.Unpatch(type, harmonyID);
+		}
+
+		public void Unpatch(MethodBase original, MethodInfo patch)
+		{
+			var processor = new PatchProcessor(this, new List<MethodBase> { original });
+			processor.Unpatch(patch);
+		}
+
+		//
+
+		public bool HasAnyPatches(string harmonyID)
+		{
+			return GetPatchedMethods()
+				.Select(original => GetPatchInfo(original))
+				.Any(info => info.Owners.Contains(harmonyID));
+		}
+
+		public Patches GetPatchInfo(MethodBase method)
+		{
+			return PatchProcessor.GetPatchInfo(method);
+		}
+
+		public IEnumerable<MethodBase> GetPatchedMethods()
+		{
+			return HarmonySharedState.GetPatchedMethods();
+		}
+
+		public Dictionary<string, Version> VersionInfo(out Version currentVersion)
+		{
+			currentVersion = typeof(HarmonyInstance).Assembly.GetName().Version;
+			var assemblies = new Dictionary<string, Assembly>();
+			GetPatchedMethods().Do(method =>
+			{
+				var info = HarmonySharedState.GetPatchInfo(method);
+				info.prefixes.Do(fix => assemblies[fix.owner] = fix.patch.DeclaringType.Assembly);
+				info.postfixes.Do(fix => assemblies[fix.owner] = fix.patch.DeclaringType.Assembly);
+				info.transpilers.Do(fix => assemblies[fix.owner] = fix.patch.DeclaringType.Assembly);
+			});
+
+			var result = new Dictionary<string, Version>();
+			assemblies.Do(info =>
+			{
+				var assemblyName = info.Value.GetReferencedAssemblies().FirstOrDefault(a => a.FullName.StartsWith("0Harmony, Version"));
+				if (assemblyName != null)
+					result[info.Key] = assemblyName.Version;
+			});
+			return result;
+		}
+	}
+}

+ 141 - 0
Harmony12Interop/HarmonyMethod.cs

@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Harmony
+{
+	public class HarmonyMethod
+	{
+		public MethodInfo method; // need to be called 'method'
+
+		public Type declaringType;
+		public string methodName;
+		public MethodType? methodType;
+		public Type[] argumentTypes;
+		public int prioritiy = -1;
+		public string[] before;
+		public string[] after;
+
+		public HarmonyMethod()
+		{
+		}
+
+		void ImportMethod(MethodInfo theMethod)
+		{
+			method = theMethod;
+			if (method != null)
+			{
+				var infos = method.GetHarmonyMethods();
+				if (infos != null)
+					Merge(infos).CopyTo(this);
+			}
+		}
+
+		public HarmonyMethod(MethodInfo method)
+		{
+			ImportMethod(method);
+		}
+
+		public HarmonyMethod(Type type, string name, Type[] parameters = null)
+		{
+			var method = AccessTools.Method(type, name, parameters);
+			ImportMethod(method);
+		}
+
+		public static List<string> HarmonyFields()
+		{
+			return AccessTools
+				.GetFieldNames(typeof(HarmonyMethod))
+				.Where(s => s != "method")
+				.ToList();
+		}
+
+		public static HarmonyMethod Merge(List<HarmonyMethod> attributes)
+		{
+			var result = new HarmonyMethod();
+			if (attributes == null) return result;
+			var resultTrv = Traverse.Create(result);
+			attributes.ForEach(attribute =>
+			{
+				var trv = Traverse.Create(attribute);
+				HarmonyFields().ForEach(f =>
+				{
+					var val = trv.Field(f).GetValue();
+					if (val != null)
+						resultTrv.Field(f).SetValue(val);
+				});
+			});
+			return result;
+		}
+
+		public override string ToString()
+		{
+			var result = "HarmonyMethod[";
+			var trv = Traverse.Create(this);
+			HarmonyFields().ForEach(f =>
+			{
+				result += f + '=' + trv.Field(f).GetValue();
+			});
+			return result + "]";
+		}
+	}
+
+	public static class HarmonyMethodExtensions
+	{
+		public static void CopyTo(this HarmonyMethod from, HarmonyMethod to)
+		{
+			if (to == null) return;
+			var fromTrv = Traverse.Create(from);
+			var toTrv = Traverse.Create(to);
+			HarmonyMethod.HarmonyFields().ForEach(f =>
+			{
+				var val = fromTrv.Field(f).GetValue();
+				if (val != null) toTrv.Field(f).SetValue(val);
+			});
+		}
+
+		public static HarmonyMethod Clone(this HarmonyMethod original)
+		{
+			var result = new HarmonyMethod();
+			original.CopyTo(result);
+			return result;
+		}
+
+		public static HarmonyMethod Merge(this HarmonyMethod master, HarmonyMethod detail)
+		{
+			if (detail == null) return master;
+			var result = new HarmonyMethod();
+			var resultTrv = Traverse.Create(result);
+			var masterTrv = Traverse.Create(master);
+			var detailTrv = Traverse.Create(detail);
+			HarmonyMethod.HarmonyFields().ForEach(f =>
+			{
+				var baseValue = masterTrv.Field(f).GetValue();
+				var detailValue = detailTrv.Field(f).GetValue();
+				resultTrv.Field(f).SetValue(detailValue ?? baseValue);
+			});
+			return result;
+		}
+
+		public static List<HarmonyMethod> GetHarmonyMethods(this Type type)
+		{
+			return type.GetCustomAttributes(true)
+						.Where(attr => attr is HarmonyAttribute)
+						.Cast<HarmonyAttribute>()
+						.Select(attr => attr.info)
+						.ToList();
+		}
+
+		public static List<HarmonyMethod> GetHarmonyMethods(this MethodBase method)
+		{
+			if (method is DynamicMethod) return new List<HarmonyMethod>();
+			return method.GetCustomAttributes(true)
+						.Where(attr => attr is HarmonyAttribute)
+						.Cast<HarmonyAttribute>()
+						.Select(attr => attr.info)
+						.ToList();
+		}
+	}
+}

+ 97 - 0
Harmony12Interop/HarmonySharedState.cs

@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using HarmonyXInterop;
+
+namespace Harmony
+{
+	internal class PatchHandler
+    {
+        private MethodBase mb;
+        private PatchInfoWrapper previousState = new PatchInfoWrapper
+        {
+            prefixes = new PatchMethod[0],
+            postfixes = new PatchMethod[0],
+            transpilers = new PatchMethod[0],
+            finalizers = new PatchMethod[0]
+        };
+        
+        public void Apply()
+        {
+            PatchMethod[] ToPatchMethod(Patch[] patches)
+            {
+                return patches.Select(p => new PatchMethod
+                {
+                    after = p.after,
+                    before = p.before,
+                    method = p.patch,
+                    priority = p.priority,
+                    owner = p.owner,
+                }).ToArray();
+            }
+
+            var info = HarmonySharedState.GetPatchInfo(mb);
+            var state = new PatchInfoWrapper
+            {
+                prefixes = ToPatchMethod(info.prefixes),
+                postfixes = ToPatchMethod(info.postfixes),
+                transpilers = ToPatchMethod(info.transpilers),
+                finalizers = new PatchMethod[0]
+            };
+            
+            var add = new PatchInfoWrapper { finalizers = new PatchMethod[0] };
+            var remove = new PatchInfoWrapper { finalizers = new PatchMethod[0] };
+            
+            Diff(previousState.prefixes, state.prefixes, out add.prefixes, out remove.prefixes);
+            Diff(previousState.postfixes, state.postfixes, out add.postfixes, out remove.postfixes);
+            Diff(previousState.transpilers, state.transpilers, out add.transpilers, out remove.transpilers);
+
+            previousState = state;
+            
+            HarmonyInterop.ApplyPatch(mb, add, remove);
+        }
+        
+        static void Diff(PatchMethod[] last, PatchMethod[] curr, out PatchMethod[] add, out PatchMethod[] remove)
+        {
+	        add = curr.Except(last, PatchMethodComparer.Instance).ToArray();
+	        remove = last.Except(curr, PatchMethodComparer.Instance).ToArray();
+        }
+        
+        static Dictionary<MethodBase, PatchHandler> patchHandlers = new Dictionary<MethodBase, PatchHandler>();
+        
+        internal static PatchHandler Get(MethodBase method)
+        {
+	        lock (patchHandlers)
+	        {
+		        if (!patchHandlers.TryGetValue(method, out var handler))
+			        patchHandlers[method] = handler = new PatchHandler {mb = method};
+		        return handler;
+	        }
+        }
+    }
+	
+	public static class HarmonySharedState
+	{
+		static Dictionary<MethodBase, PatchInfo> patchInfos = new Dictionary<MethodBase, PatchInfo>();
+
+		internal static PatchInfo GetPatchInfo(MethodBase method)
+		{
+			lock (patchInfos)
+			{
+				if (!patchInfos.TryGetValue(method, out var info))
+					patchInfos[method] = info = new PatchInfo();
+				return info;
+			}
+		}
+
+		internal static IEnumerable<MethodBase> GetPatchedMethods()
+		{
+			lock (patchInfos)
+			{
+				return patchInfos.Keys.ToList().AsEnumerable();
+			}
+		}
+	}
+}

+ 315 - 0
Harmony12Interop/ILCopying/Emitter.cs

@@ -0,0 +1,315 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.InteropServices;
+
+namespace Harmony.ILCopying
+{
+	public class LeaveTry
+	{
+		public override string ToString()
+		{
+			return "(autogenerated)";
+		}
+	}
+
+	public static class Emitter
+	{
+		static readonly GetterHandler codeLenGetter = FastAccess.CreateFieldGetter(typeof(ILGenerator), "code_len", "m_length");
+		static readonly GetterHandler localsGetter = FastAccess.CreateFieldGetter(typeof(ILGenerator), "locals");
+		static readonly GetterHandler localCountGetter = FastAccess.CreateFieldGetter(typeof(ILGenerator), "m_localCount");
+
+		public static string CodePos(ILGenerator il)
+		{
+			var offset = (int)codeLenGetter(il);
+			return string.Format("L_{0:x4}: ", offset);
+		}
+
+		public static void LogIL(ILGenerator il, OpCode opCode, object argument)
+		{
+			if (HarmonyInstance.DEBUG)
+			{
+				var argStr = FormatArgument(argument);
+				var space = argStr.Length > 0 ? " " : "";
+				FileLog.LogBuffered(string.Format("{0}{1}{2}{3}", CodePos(il), opCode, space, argStr));
+			}
+		}
+
+		public static void LogLocalVariable(ILGenerator il, LocalBuilder variable)
+		{
+			if (HarmonyInstance.DEBUG)
+			{
+				var localCount = -1;
+				var localsArray = localsGetter != null ? (LocalBuilder[])localsGetter(il) : null;
+				if (localsArray != null && localsArray.Length > 0)
+					localCount = localsArray.Length;
+				else
+					localCount = (int)localCountGetter(il);
+
+				var str = string.Format("{0}Local var {1}: {2}{3}", CodePos(il), localCount - 1, variable.LocalType.FullName, variable.IsPinned ? "(pinned)" : "");
+				FileLog.LogBuffered(str);
+			}
+		}
+
+		public static string FormatArgument(object argument)
+		{
+			if (argument == null) return "NULL";
+			var type = argument.GetType();
+
+			if (type == typeof(string))
+				return "\"" + argument + "\"";
+			if (type == typeof(Label))
+				return "Label" + ((Label)argument).GetHashCode();
+			if (type == typeof(Label[]))
+				return "Labels" + string.Join(",", ((Label[])argument).Select(l => l.GetHashCode().ToString()).ToArray());
+			if (type == typeof(LocalBuilder))
+				return ((LocalBuilder)argument).LocalIndex + " (" + ((LocalBuilder)argument).LocalType + ")";
+
+			return argument.ToString().Trim();
+		}
+
+		public static void MarkLabel(ILGenerator il, Label label)
+		{
+			if (HarmonyInstance.DEBUG) FileLog.LogBuffered(CodePos(il) + FormatArgument(label));
+			il.MarkLabel(label);
+		}
+
+		public static void MarkBlockBefore(ILGenerator il, ExceptionBlock block, out Label? label)
+		{
+			label = null;
+			switch (block.blockType)
+			{
+				case ExceptionBlockType.BeginExceptionBlock:
+					if (HarmonyInstance.DEBUG)
+					{
+						FileLog.LogBuffered(".try");
+						FileLog.LogBuffered("{");
+						FileLog.ChangeIndent(1);
+					}
+					label = il.BeginExceptionBlock();
+					return;
+
+				case ExceptionBlockType.BeginCatchBlock:
+					if (HarmonyInstance.DEBUG)
+					{
+						// fake log a LEAVE code since BeginCatchBlock() does add it
+						LogIL(il, OpCodes.Leave, new LeaveTry());
+
+						FileLog.ChangeIndent(-1);
+						FileLog.LogBuffered("} // end try");
+
+						FileLog.LogBuffered(".catch " + block.catchType);
+						FileLog.LogBuffered("{");
+						FileLog.ChangeIndent(1);
+					}
+					il.BeginCatchBlock(block.catchType);
+					return;
+
+				case ExceptionBlockType.BeginExceptFilterBlock:
+					if (HarmonyInstance.DEBUG)
+					{
+						// fake log a LEAVE code since BeginCatchBlock() does add it
+						LogIL(il, OpCodes.Leave, new LeaveTry());
+
+						FileLog.ChangeIndent(-1);
+						FileLog.LogBuffered("} // end try");
+
+						FileLog.LogBuffered(".filter");
+						FileLog.LogBuffered("{");
+						FileLog.ChangeIndent(1);
+					}
+					il.BeginExceptFilterBlock();
+					return;
+
+				case ExceptionBlockType.BeginFaultBlock:
+					if (HarmonyInstance.DEBUG)
+					{
+						// fake log a LEAVE code since BeginCatchBlock() does add it
+						LogIL(il, OpCodes.Leave, new LeaveTry());
+
+						FileLog.ChangeIndent(-1);
+						FileLog.LogBuffered("} // end try");
+
+						FileLog.LogBuffered(".fault");
+						FileLog.LogBuffered("{");
+						FileLog.ChangeIndent(1);
+					}
+					il.BeginFaultBlock();
+					return;
+
+				case ExceptionBlockType.BeginFinallyBlock:
+					if (HarmonyInstance.DEBUG)
+					{
+						// fake log a LEAVE code since BeginCatchBlock() does add it
+						LogIL(il, OpCodes.Leave, new LeaveTry());
+
+						FileLog.ChangeIndent(-1);
+						FileLog.LogBuffered("} // end try");
+
+						FileLog.LogBuffered(".finally");
+						FileLog.LogBuffered("{");
+						FileLog.ChangeIndent(1);
+					}
+					il.BeginFinallyBlock();
+					return;
+			}
+		}
+
+		public static void MarkBlockAfter(ILGenerator il, ExceptionBlock block)
+		{
+			if (block.blockType == ExceptionBlockType.EndExceptionBlock)
+			{
+				if (HarmonyInstance.DEBUG)
+				{
+					// fake log a LEAVE code since BeginCatchBlock() does add it
+					LogIL(il, OpCodes.Leave, new LeaveTry());
+
+					FileLog.ChangeIndent(-1);
+					FileLog.LogBuffered("} // end handler");
+				}
+				il.EndExceptionBlock();
+			}
+		}
+
+		// MethodCopier calls when Operand type is InlineNone
+		public static void Emit(ILGenerator il, OpCode opcode)
+		{
+			if (HarmonyInstance.DEBUG) FileLog.LogBuffered(CodePos(il) + opcode);
+			il.Emit(opcode);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, LocalBuilder local)
+		{
+			LogIL(il, opcode, local);
+			il.Emit(opcode, local);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, FieldInfo field)
+		{
+			LogIL(il, opcode, field);
+			il.Emit(opcode, field);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, Label[] labels)
+		{
+			LogIL(il, opcode, labels);
+			il.Emit(opcode, labels);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, Label label)
+		{
+			LogIL(il, opcode, label);
+			il.Emit(opcode, label);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, string str)
+		{
+			LogIL(il, opcode, str);
+			il.Emit(opcode, str);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, float arg)
+		{
+			LogIL(il, opcode, arg);
+			il.Emit(opcode, arg);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, byte arg)
+		{
+			LogIL(il, opcode, arg);
+			il.Emit(opcode, arg);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, sbyte arg)
+		{
+			LogIL(il, opcode, arg);
+			il.Emit(opcode, arg);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, double arg)
+		{
+			LogIL(il, opcode, arg);
+			il.Emit(opcode, arg);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, int arg)
+		{
+			LogIL(il, opcode, arg);
+			il.Emit(opcode, arg);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, MethodInfo meth)
+		{
+			LogIL(il, opcode, meth);
+			il.Emit(opcode, meth);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, short arg)
+		{
+			LogIL(il, opcode, arg);
+			il.Emit(opcode, arg);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, SignatureHelper signature)
+		{
+			LogIL(il, opcode, signature);
+			il.Emit(opcode, signature);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, ConstructorInfo con)
+		{
+			LogIL(il, opcode, con);
+			il.Emit(opcode, con);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, Type cls)
+		{
+			LogIL(il, opcode, cls);
+			il.Emit(opcode, cls);
+		}
+
+		// MethodCopier calls by 3rd argument type
+		public static void Emit(ILGenerator il, OpCode opcode, long arg)
+		{
+			LogIL(il, opcode, arg);
+			il.Emit(opcode, arg);
+		}
+
+		// called from MethodInvoker (calls from MethodCopier use the corresponding Emit() call above)
+		public static void EmitCall(ILGenerator il, OpCode opcode, MethodInfo methodInfo, Type[] optionalParameterTypes)
+		{
+			if (HarmonyInstance.DEBUG) FileLog.LogBuffered(string.Format("{0}Call {1} {2} {3}", CodePos(il), opcode, methodInfo, optionalParameterTypes));
+			il.EmitCall(opcode, methodInfo, optionalParameterTypes);
+		}
+
+		// not called yet
+		public static void EmitCalli(ILGenerator il, OpCode opcode, CallingConvention unmanagedCallConv, Type returnType, Type[] parameterTypes)
+		{
+			if (HarmonyInstance.DEBUG) FileLog.LogBuffered(string.Format("{0}Calli {1} {2} {3} {4}", CodePos(il), opcode, unmanagedCallConv, returnType, parameterTypes));
+			il.EmitCalli(opcode, unmanagedCallConv, returnType, parameterTypes);
+		}
+
+		// not called yet
+		public static void EmitCalli(ILGenerator il, OpCode opcode, CallingConventions callingConvention, Type returnType, Type[] parameterTypes, Type[] optionalParameterTypes)
+		{
+			if (HarmonyInstance.DEBUG) FileLog.LogBuffered(string.Format("{0}Calli {1} {2} {3} {4} {5}", CodePos(il), opcode, callingConvention, returnType, parameterTypes, optionalParameterTypes));
+			il.EmitCalli(opcode, callingConvention, returnType, parameterTypes, optionalParameterTypes);
+		}
+	}
+}

+ 148 - 0
Harmony12Interop/ILCopying/ILInstruction.cs

@@ -0,0 +1,148 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection.Emit;
+
+namespace Harmony.ILCopying
+{
+	public enum ExceptionBlockType
+	{
+		BeginExceptionBlock,
+		BeginCatchBlock,
+		BeginExceptFilterBlock,
+		BeginFaultBlock,
+		BeginFinallyBlock,
+		EndExceptionBlock
+	}
+
+	public class ExceptionBlock
+	{
+		public ExceptionBlockType blockType;
+		public Type catchType;
+
+		public ExceptionBlock(ExceptionBlockType blockType, Type catchType)
+		{
+			this.blockType = blockType;
+			this.catchType = catchType;
+		}
+	}
+
+	public class ILInstruction
+	{
+		public int offset;
+		public OpCode opcode;
+		public object operand;
+		public object argument;
+
+		public List<Label> labels = new List<Label>();
+		public List<ExceptionBlock> blocks = new List<ExceptionBlock>();
+
+		public ILInstruction(OpCode opcode, object operand = null)
+		{
+			this.opcode = opcode;
+			this.operand = operand;
+			argument = operand;
+		}
+
+		public CodeInstruction GetCodeInstruction()
+		{
+			var instr = new CodeInstruction(opcode, argument);
+			if (opcode.OperandType == OperandType.InlineNone)
+				instr.operand = null;
+			instr.labels = labels;
+			instr.blocks = blocks;
+			return instr;
+		}
+
+		public int GetSize()
+		{
+			var size = opcode.Size;
+
+			switch (opcode.OperandType)
+			{
+				case OperandType.InlineSwitch:
+					size += (1 + ((Array)operand).Length) * 4;
+					break;
+
+				case OperandType.InlineI8:
+				case OperandType.InlineR:
+					size += 8;
+					break;
+
+				case OperandType.InlineBrTarget:
+				case OperandType.InlineField:
+				case OperandType.InlineI:
+				case OperandType.InlineMethod:
+				case OperandType.InlineSig:
+				case OperandType.InlineString:
+				case OperandType.InlineTok:
+				case OperandType.InlineType:
+				case OperandType.ShortInlineR:
+					size += 4;
+					break;
+
+				case OperandType.InlineVar:
+					size += 2;
+					break;
+
+				case OperandType.ShortInlineBrTarget:
+				case OperandType.ShortInlineI:
+				case OperandType.ShortInlineVar:
+					size += 1;
+					break;
+			}
+
+			return size;
+		}
+
+		public override string ToString()
+		{
+			var instruction = "";
+
+			AppendLabel(ref instruction, this);
+			instruction = instruction + ": " + opcode.Name;
+
+			if (operand == null)
+				return instruction;
+
+			instruction = instruction + " ";
+
+			switch (opcode.OperandType)
+			{
+				case OperandType.ShortInlineBrTarget:
+				case OperandType.InlineBrTarget:
+					AppendLabel(ref instruction, operand);
+					break;
+
+				case OperandType.InlineSwitch:
+					var switchLabels = (ILInstruction[])operand;
+					for (var i = 0; i < switchLabels.Length; i++)
+					{
+						if (i > 0)
+							instruction = instruction + ",";
+
+						AppendLabel(ref instruction, switchLabels[i]);
+					}
+					break;
+
+				case OperandType.InlineString:
+					instruction = instruction + "\"" + operand + "\"";
+					break;
+
+				default:
+					instruction = instruction + operand;
+					break;
+			}
+
+			return instruction;
+		}
+
+		static void AppendLabel(ref string str, object argument)
+		{
+			var instruction = argument as ILInstruction;
+			if (instruction != null)
+				str = str + "IL_" + instruction.offset.ToString("X4");
+			else
+				str = str + "IL_" + argument;
+		}
+	}
+}

+ 194 - 0
Harmony12Interop/Patch.cs

@@ -0,0 +1,194 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+
+namespace Harmony
+{
+	public static class PatchInfoSerialization
+	{
+		class Binder : SerializationBinder
+		{
+			public override Type BindToType(string assemblyName, string typeName)
+			{
+				var types = new Type[] {
+					typeof(PatchInfo),
+					typeof(Patch[]),
+					typeof(Patch)
+				};
+				foreach (var type in types)
+					if (typeName == type.FullName)
+						return type;
+				var typeToDeserialize = Type.GetType(string.Format("{0}, {1}", typeName, assemblyName));
+				return typeToDeserialize;
+			}
+		}
+
+		public static byte[] Serialize(this PatchInfo patchInfo)
+		{
+#pragma warning disable XS0001
+			using (var streamMemory = new MemoryStream())
+			{
+				var formatter = new BinaryFormatter();
+				formatter.Serialize(streamMemory, patchInfo);
+				return streamMemory.GetBuffer();
+			}
+#pragma warning restore XS0001
+		}
+
+		public static PatchInfo Deserialize(byte[] bytes)
+		{
+			var formatter = new BinaryFormatter { Binder = new Binder() };
+#pragma warning disable XS0001
+			var streamMemory = new MemoryStream(bytes);
+#pragma warning restore XS0001
+			return (PatchInfo)formatter.Deserialize(streamMemory);
+		}
+
+		// general sorting by (in that order): before, after, priority and index
+		public static int PriorityComparer(object obj, int index, int priority, string[] before, string[] after)
+		{
+			var trv = Traverse.Create(obj);
+			var theirOwner = trv.Field("owner").GetValue<string>();
+			var theirPriority = trv.Field("priority").GetValue<int>();
+			var theirIndex = trv.Field("index").GetValue<int>();
+
+			if (before != null && Array.IndexOf(before, theirOwner) > -1)
+				return -1;
+			if (after != null && Array.IndexOf(after, theirOwner) > -1)
+				return 1;
+
+			if (priority != theirPriority)
+				return -(priority.CompareTo(theirPriority));
+
+			return index.CompareTo(theirIndex);
+		}
+	}
+
+	[Serializable]
+	public class PatchInfo
+	{
+		public Patch[] prefixes;
+		public Patch[] postfixes;
+		public Patch[] transpilers;
+
+		public PatchInfo()
+		{
+			prefixes = new Patch[0];
+			postfixes = new Patch[0];
+			transpilers = new Patch[0];
+		}
+
+		public void AddPrefix(MethodInfo patch, string owner, int priority, string[] before, string[] after)
+		{
+			var l = prefixes.ToList();
+			l.Add(new Patch(patch, prefixes.Count() + 1, owner, priority, before, after));
+			prefixes = l.ToArray();
+		}
+
+		public void RemovePrefix(string owner)
+		{
+			if (owner == "*")
+			{
+				prefixes = new Patch[0];
+				return;
+			}
+			prefixes = prefixes.Where(patch => patch.owner != owner).ToArray();
+		}
+
+		public void AddPostfix(MethodInfo patch, string owner, int priority, string[] before, string[] after)
+		{
+			var l = postfixes.ToList();
+			l.Add(new Patch(patch, postfixes.Count() + 1, owner, priority, before, after));
+			postfixes = l.ToArray();
+		}
+
+		public void RemovePostfix(string owner)
+		{
+			if (owner == "*")
+			{
+				postfixes = new Patch[0];
+				return;
+			}
+			postfixes = postfixes.Where(patch => patch.owner != owner).ToArray();
+		}
+
+		public void AddTranspiler(MethodInfo patch, string owner, int priority, string[] before, string[] after)
+		{
+			var l = transpilers.ToList();
+			l.Add(new Patch(patch, transpilers.Count() + 1, owner, priority, before, after));
+			transpilers = l.ToArray();
+		}
+
+		public void RemoveTranspiler(string owner)
+		{
+			if (owner == "*")
+			{
+				transpilers = new Patch[0];
+				return;
+			}
+			transpilers = transpilers.Where(patch => patch.owner != owner).ToArray();
+		}
+
+		public void RemovePatch(MethodInfo patch)
+		{
+			prefixes = prefixes.Where(p => p.patch != patch).ToArray();
+			postfixes = postfixes.Where(p => p.patch != patch).ToArray();
+			transpilers = transpilers.Where(p => p.patch != patch).ToArray();
+		}
+	}
+
+	[Serializable]
+	public class Patch : IComparable
+	{
+		readonly public int index;
+		readonly public string owner;
+		readonly public int priority;
+		readonly public string[] before;
+		readonly public string[] after;
+
+		readonly public MethodInfo patch;
+
+		public Patch(MethodInfo patch, int index, string owner, int priority, string[] before, string[] after)
+		{
+			if (patch is DynamicMethod) throw new Exception("Cannot directly reference dynamic method \"" + patch.FullDescription() + "\" in Harmony. Use a factory method instead that will return the dynamic method.");
+
+			this.index = index;
+			this.owner = owner;
+			this.priority = priority;
+			this.before = before;
+			this.after = after;
+			this.patch = patch;
+		}
+
+		public MethodInfo GetMethod(MethodBase original)
+		{
+			if (patch.ReturnType != typeof(DynamicMethod)) return patch;
+			if (patch.IsStatic == false) return patch;
+			var parameters = patch.GetParameters();
+			if (parameters.Count() != 1) return patch;
+			if (parameters[0].ParameterType != typeof(MethodBase)) return patch;
+
+			// we have a DynamicMethod factory, let's use it
+			return patch.Invoke(null, new object[] { original }) as DynamicMethod;
+		}
+
+		public override bool Equals(object obj)
+		{
+			return ((obj != null) && (obj is Patch) && (patch == ((Patch)obj).patch));
+		}
+
+		public int CompareTo(object obj)
+		{
+			return PatchInfoSerialization.PriorityComparer(obj, index, priority, before, after);
+		}
+
+		public override int GetHashCode()
+		{
+			return patch.GetHashCode();
+		}
+	}
+}

+ 74 - 0
Harmony12Interop/PatchFunctions.cs

@@ -0,0 +1,74 @@
+using Harmony.ILCopying;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Harmony
+{
+	public static class PatchFunctions
+	{
+		public static void AddPrefix(PatchInfo patchInfo, string owner, HarmonyMethod info)
+		{
+			if (info == null || info.method == null) return;
+
+			var priority = info.prioritiy == -1 ? Priority.Normal : info.prioritiy;
+			var before = info.before ?? new string[0];
+			var after = info.after ?? new string[0];
+
+			patchInfo.AddPrefix(info.method, owner, priority, before, after);
+		}
+
+		public static void RemovePrefix(PatchInfo patchInfo, string owner)
+		{
+			patchInfo.RemovePrefix(owner);
+		}
+
+		public static void AddPostfix(PatchInfo patchInfo, string owner, HarmonyMethod info)
+		{
+			if (info == null || info.method == null) return;
+
+			var priority = info.prioritiy == -1 ? Priority.Normal : info.prioritiy;
+			var before = info.before ?? new string[0];
+			var after = info.after ?? new string[0];
+
+			patchInfo.AddPostfix(info.method, owner, priority, before, after);
+		}
+
+		public static void RemovePostfix(PatchInfo patchInfo, string owner)
+		{
+			patchInfo.RemovePostfix(owner);
+		}
+
+		public static void AddTranspiler(PatchInfo patchInfo, string owner, HarmonyMethod info)
+		{
+			if (info == null || info.method == null) return;
+
+			var priority = info.prioritiy == -1 ? Priority.Normal : info.prioritiy;
+			var before = info.before ?? new string[0];
+			var after = info.after ?? new string[0];
+
+			patchInfo.AddTranspiler(info.method, owner, priority, before, after);
+		}
+
+		public static void RemoveTranspiler(PatchInfo patchInfo, string owner)
+		{
+			patchInfo.RemoveTranspiler(owner);
+		}
+
+		public static void RemovePatch(PatchInfo patchInfo, MethodInfo patch)
+		{
+			patchInfo.RemovePatch(patch);
+		}
+
+		public static List<MethodInfo> GetSortedPatchMethods(MethodBase original, Patch[] patches)
+		{
+			return patches
+				.Where(p => p.patch != null)
+				.OrderBy(p => p)
+				.Select(p => p.GetMethod(original))
+				.ToList();
+		}
+	}
+}

+ 301 - 0
Harmony12Interop/PatchProcessor.cs

@@ -0,0 +1,301 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Harmony
+{
+	public class PatchProcessor
+	{
+		static object locker = new object();
+
+		readonly HarmonyInstance instance;
+
+		readonly Type container;
+		readonly HarmonyMethod containerAttributes;
+
+		List<MethodBase> originals = new List<MethodBase>();
+		HarmonyMethod prefix;
+		HarmonyMethod postfix;
+		HarmonyMethod transpiler;
+
+		public PatchProcessor(HarmonyInstance instance, Type type, HarmonyMethod attributes)
+		{
+			this.instance = instance;
+			container = type;
+			containerAttributes = attributes ?? new HarmonyMethod(null);
+			prefix = containerAttributes.Clone();
+			postfix = containerAttributes.Clone();
+			transpiler = containerAttributes.Clone();
+			PrepareType();
+		}
+
+		public PatchProcessor(HarmonyInstance instance, List<MethodBase> originals, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null)
+		{
+			this.instance = instance;
+			this.originals = originals;
+			this.prefix = prefix ?? new HarmonyMethod(null);
+			this.postfix = postfix ?? new HarmonyMethod(null);
+			this.transpiler = transpiler ?? new HarmonyMethod(null);
+		}
+
+		public static Patches GetPatchInfo(MethodBase method)
+		{
+			lock (locker)
+			{
+				var patchInfo = HarmonySharedState.GetPatchInfo(method);
+				if (patchInfo == null) return null;
+				return new Patches(patchInfo.prefixes, patchInfo.postfixes, patchInfo.transpilers);
+			}
+		}
+
+		public static IEnumerable<MethodBase> AllPatchedMethods()
+		{
+			lock (locker)
+			{
+				return HarmonySharedState.GetPatchedMethods();
+			}
+		}
+
+		public List<DynamicMethod> Patch()
+		{
+			lock (locker)
+			{
+				var dynamicMethods = new List<DynamicMethod>();
+				foreach (var original in originals)
+				{
+					if (original == null)
+						throw new NullReferenceException("original");
+
+					var individualPrepareResult = RunMethod<HarmonyPrepare, bool>(true, original);
+					if (individualPrepareResult)
+					{
+						var patchInfo = HarmonySharedState.GetPatchInfo(original);
+						if (patchInfo == null) patchInfo = new PatchInfo();
+
+						PatchFunctions.AddPrefix(patchInfo, instance.Id, prefix);
+						PatchFunctions.AddPostfix(patchInfo, instance.Id, postfix);
+						PatchFunctions.AddTranspiler(patchInfo, instance.Id, transpiler);
+						
+						PatchHandler.Get(original).Apply();
+
+						RunMethod<HarmonyCleanup>(original);
+					}
+				}
+				return dynamicMethods;
+			}
+		}
+
+		public void Unpatch(HarmonyPatchType type, string harmonyID)
+		{
+			lock (locker)
+			{
+				foreach (var original in originals)
+				{
+					var patchInfo = HarmonySharedState.GetPatchInfo(original);
+					if (patchInfo == null) patchInfo = new PatchInfo();
+
+					if (type == HarmonyPatchType.All || type == HarmonyPatchType.Prefix)
+						PatchFunctions.RemovePrefix(patchInfo, harmonyID);
+					if (type == HarmonyPatchType.All || type == HarmonyPatchType.Postfix)
+						PatchFunctions.RemovePostfix(patchInfo, harmonyID);
+					if (type == HarmonyPatchType.All || type == HarmonyPatchType.Transpiler)
+						PatchFunctions.RemoveTranspiler(patchInfo, harmonyID);
+					
+					PatchHandler.Get(original).Apply();
+				}
+			}
+		}
+
+		public void Unpatch(MethodInfo patch)
+		{
+			lock (locker)
+			{
+				foreach (var original in originals)
+				{
+					var patchInfo = HarmonySharedState.GetPatchInfo(original);
+					if (patchInfo == null) patchInfo = new PatchInfo();
+
+					PatchFunctions.RemovePatch(patchInfo, patch);
+					PatchHandler.Get(original).Apply();
+				}
+			}
+		}
+
+		void PrepareType()
+		{
+			var mainPrepareResult = RunMethod<HarmonyPrepare, bool>(true);
+			if (mainPrepareResult == false)
+				return;
+
+			var customOriginals = RunMethod<HarmonyTargetMethods, IEnumerable<MethodBase>>(null);
+			if (customOriginals != null)
+			{
+				originals = customOriginals.ToList();
+			}
+			else
+			{
+				var originalMethodType = containerAttributes.methodType;
+
+				// MethodType default is Normal
+				if (containerAttributes.methodType == null)
+					containerAttributes.methodType = MethodType.Normal;
+
+				var isPatchAll = Attribute.GetCustomAttribute(container, typeof(HarmonyPatchAll)) != null;
+				if (isPatchAll)
+				{
+					var type = containerAttributes.declaringType;
+					originals.AddRange(AccessTools.GetDeclaredConstructors(type).Cast<MethodBase>());
+					originals.AddRange(AccessTools.GetDeclaredMethods(type).Cast<MethodBase>());
+				}
+				else
+				{
+					var original = RunMethod<HarmonyTargetMethod, MethodBase>(null);
+
+					if (original == null)
+						original = GetOriginalMethod();
+
+					if (original == null)
+					{
+						var info = "(";
+						info += "declaringType=" + containerAttributes.declaringType + ", ";
+						info += "methodName =" + containerAttributes.methodName + ", ";
+						info += "methodType=" + originalMethodType + ", ";
+						info += "argumentTypes=" + containerAttributes.argumentTypes.Description();
+						info += ")";
+						throw new ArgumentException("No target method specified for class " + container.FullName + " " + info);
+					}
+
+					originals.Add(original);
+				}
+			}
+
+			PatchTools.GetPatches(container, out prefix.method, out postfix.method, out transpiler.method);
+
+			if (prefix.method != null)
+			{
+				if (prefix.method.IsStatic == false)
+					throw new ArgumentException("Patch method " + prefix.method.FullDescription() + " must be static");
+
+				var prefixAttributes = prefix.method.GetHarmonyMethods();
+				containerAttributes.Merge(HarmonyMethod.Merge(prefixAttributes)).CopyTo(prefix);
+			}
+
+			if (postfix.method != null)
+			{
+				if (postfix.method.IsStatic == false)
+					throw new ArgumentException("Patch method " + postfix.method.FullDescription() + " must be static");
+
+				var postfixAttributes = postfix.method.GetHarmonyMethods();
+				containerAttributes.Merge(HarmonyMethod.Merge(postfixAttributes)).CopyTo(postfix);
+			}
+
+			if (transpiler.method != null)
+			{
+				if (transpiler.method.IsStatic == false)
+					throw new ArgumentException("Patch method " + transpiler.method.FullDescription() + " must be static");
+
+				var infixAttributes = transpiler.method.GetHarmonyMethods();
+				containerAttributes.Merge(HarmonyMethod.Merge(infixAttributes)).CopyTo(transpiler);
+			}
+		}
+
+		MethodBase GetOriginalMethod()
+		{
+			var attr = containerAttributes;
+			if (attr.declaringType == null) return null;
+
+			switch (attr.methodType)
+			{
+				case MethodType.Normal:
+					if (attr.methodName == null)
+						return null;
+					return AccessTools.DeclaredMethod(attr.declaringType, attr.methodName, attr.argumentTypes);
+
+				case MethodType.Getter:
+					if (attr.methodName == null)
+						return null;
+					return AccessTools.DeclaredProperty(attr.declaringType, attr.methodName).GetGetMethod(true);
+
+				case MethodType.Setter:
+					if (attr.methodName == null)
+						return null;
+					return AccessTools.DeclaredProperty(attr.declaringType, attr.methodName).GetSetMethod(true);
+
+				case MethodType.Constructor:
+					return AccessTools.DeclaredConstructor(attr.declaringType, attr.argumentTypes);
+
+				case MethodType.StaticConstructor:
+					return AccessTools.GetDeclaredConstructors(attr.declaringType)
+						.Where(c => c.IsStatic)
+						.FirstOrDefault();
+			}
+
+			return null;
+		}
+
+		T RunMethod<S, T>(T defaultIfNotExisting, params object[] parameters)
+		{
+			if (container == null)
+				return defaultIfNotExisting;
+
+			var methodName = typeof(S).Name.Replace("Harmony", "");
+
+			var paramList = new List<object> { instance };
+			paramList.AddRange(parameters);
+			var paramTypes = AccessTools.GetTypes(paramList.ToArray());
+			var method = PatchTools.GetPatchMethod<S>(container, methodName, paramTypes);
+			if (method != null && typeof(T).IsAssignableFrom(method.ReturnType))
+				return (T)method.Invoke(null, paramList.ToArray());
+
+			method = PatchTools.GetPatchMethod<S>(container, methodName, new Type[] { typeof(HarmonyInstance) });
+			if (method != null && typeof(T).IsAssignableFrom(method.ReturnType))
+				return (T)method.Invoke(null, new object[] { instance });
+
+			method = PatchTools.GetPatchMethod<S>(container, methodName, Type.EmptyTypes);
+			if (method != null)
+			{
+				if (typeof(T).IsAssignableFrom(method.ReturnType))
+					return (T)method.Invoke(null, Type.EmptyTypes);
+
+				method.Invoke(null, Type.EmptyTypes);
+				return defaultIfNotExisting;
+			}
+
+			return defaultIfNotExisting;
+		}
+
+		void RunMethod<S>(params object[] parameters)
+		{
+			if (container == null)
+				return;
+
+			var methodName = typeof(S).Name.Replace("Harmony", "");
+
+			var paramList = new List<object> { instance };
+			paramList.AddRange(parameters);
+			var paramTypes = AccessTools.GetTypes(paramList.ToArray());
+			var method = PatchTools.GetPatchMethod<S>(container, methodName, paramTypes);
+			if (method != null)
+			{
+				method.Invoke(null, paramList.ToArray());
+				return;
+			}
+
+			method = PatchTools.GetPatchMethod<S>(container, methodName, new Type[] { typeof(HarmonyInstance) });
+			if (method != null)
+			{
+				method.Invoke(null, new object[] { instance });
+				return;
+			}
+
+			method = PatchTools.GetPatchMethod<S>(container, methodName, Type.EmptyTypes);
+			if (method != null)
+			{
+				method.Invoke(null, Type.EmptyTypes);
+				return;
+			}
+		}
+	}
+}

+ 15 - 0
Harmony12Interop/Priority.cs

@@ -0,0 +1,15 @@
+namespace Harmony
+{
+	public static class Priority
+	{
+		public const int Last = 0;
+		public const int VeryLow = 100;
+		public const int Low = 200;
+		public const int LowerThanNormal = 300;
+		public const int Normal = 400;
+		public const int HigherThanNormal = 500;
+		public const int High = 600;
+		public const int VeryHigh = 700;
+		public const int First = 800;
+	}
+}

+ 10 - 0
Harmony12Interop/Properties/AssemblyInfo.cs

@@ -0,0 +1,10 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("Harmony12Interop")]
+[assembly: AssemblyProduct("Harmony12Interop")]
+
+[assembly: ComVisible(false)]
+
+[assembly: AssemblyVersion("1.2.0.0")]
+[assembly: AssemblyFileVersion("1.2.0.0")]

+ 95 - 0
Harmony12Interop/Tools/AccessCache.cs

@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace Harmony
+{
+	public class AccessCache
+	{
+		Dictionary<Type, Dictionary<string, FieldInfo>> fields = new Dictionary<Type, Dictionary<string, FieldInfo>>();
+		Dictionary<Type, Dictionary<string, PropertyInfo>> properties = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
+		readonly Dictionary<Type, Dictionary<string, Dictionary<int, MethodBase>>> methods = new Dictionary<Type, Dictionary<string, Dictionary<int, MethodBase>>>();
+
+		public FieldInfo GetFieldInfo(Type type, string name)
+		{
+			Dictionary<string, FieldInfo> fieldsByType = null;
+			if (fields.TryGetValue(type, out fieldsByType) == false)
+			{
+				fieldsByType = new Dictionary<string, FieldInfo>();
+				fields.Add(type, fieldsByType);
+			}
+
+			FieldInfo field = null;
+			if (fieldsByType.TryGetValue(name, out field) == false)
+			{
+				field = AccessTools.Field(type, name);
+				fieldsByType.Add(name, field);
+			}
+			return field;
+		}
+
+		public PropertyInfo GetPropertyInfo(Type type, string name)
+		{
+			Dictionary<string, PropertyInfo> propertiesByType = null;
+			if (properties.TryGetValue(type, out propertiesByType) == false)
+			{
+				propertiesByType = new Dictionary<string, PropertyInfo>();
+				properties.Add(type, propertiesByType);
+			}
+
+			PropertyInfo property = null;
+			if (propertiesByType.TryGetValue(name, out property) == false)
+			{
+				property = AccessTools.Property(type, name);
+				propertiesByType.Add(name, property);
+			}
+			return property;
+		}
+
+		static int CombinedHashCode(IEnumerable<object> objects)
+		{
+			var hash1 = (5381 << 16) + 5381;
+			var hash2 = hash1;
+			var i = 0;
+			foreach (var obj in objects)
+			{
+				if (i % 2 == 0)
+					hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ obj.GetHashCode();
+				else
+					hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ obj.GetHashCode();
+				++i;
+			}
+			return hash1 + (hash2 * 1566083941);
+		}
+
+		public MethodBase GetMethodInfo(Type type, string name, Type[] arguments)
+		{
+			Dictionary<string, Dictionary<int, MethodBase>> methodsByName = null;
+			methods.TryGetValue(type, out methodsByName);
+			if (methodsByName == null)
+			{
+				methodsByName = new Dictionary<string, Dictionary<int, MethodBase>>();
+				methods.Add(type, methodsByName);
+			}
+
+			Dictionary<int, MethodBase> methodsByArguments = null;
+			methodsByName.TryGetValue(name, out methodsByArguments);
+			if (methodsByArguments == null)
+			{
+				methodsByArguments = new Dictionary<int, MethodBase>();
+				methodsByName.Add(name, methodsByArguments);
+			}
+
+			MethodBase method = null;
+			var argumentsHash = CombinedHashCode(arguments);
+			methodsByArguments.TryGetValue(argumentsHash, out method);
+			if (method == null)
+			{
+				method = AccessTools.Method(type, name, arguments);
+				methodsByArguments.Add(argumentsHash, method);
+			}
+
+			return method;
+		}
+	}
+}

+ 398 - 0
Harmony12Interop/Tools/AccessTools.cs

@@ -0,0 +1,398 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.Serialization;
+
+namespace Harmony
+{
+	public static class AccessTools
+	{
+		public static BindingFlags all = BindingFlags.Public
+			| BindingFlags.NonPublic
+			| BindingFlags.Instance
+			| BindingFlags.Static
+			| BindingFlags.GetField
+			| BindingFlags.SetField
+			| BindingFlags.GetProperty
+			| BindingFlags.SetProperty;
+
+		public static Type TypeByName(string name)
+		{
+			var type = Type.GetType(name, false);
+			if (type == null)
+				type = AppDomain.CurrentDomain.GetAssemblies()
+					.SelectMany(x => x.GetTypes())
+					.FirstOrDefault(x => x.FullName == name);
+			if (type == null)
+				type = AppDomain.CurrentDomain.GetAssemblies()
+					.SelectMany(x => x.GetTypes())
+					.FirstOrDefault(x => x.Name == name);
+			return type;
+		}
+
+		public static T FindIncludingBaseTypes<T>(Type type, Func<Type, T> action)
+		{
+			while (true)
+			{
+				var result = action(type);
+				if (result != null) return result;
+				if (type == typeof(object)) return default(T);
+				type = type.BaseType;
+			}
+		}
+
+		public static T FindIncludingInnerTypes<T>(Type type, Func<Type, T> action)
+		{
+			var result = action(type);
+			if (result != null) return result;
+			foreach (var subType in type.GetNestedTypes(all))
+			{
+				result = FindIncludingInnerTypes(subType, action);
+				if (result != null)
+					break;
+			}
+			return result;
+		}
+
+		public static FieldInfo Field(Type type, string name)
+		{
+			if (type == null || name == null) return null;
+			return FindIncludingBaseTypes(type, t => t.GetField(name, all));
+		}
+
+		public static FieldInfo Field(Type type, int idx)
+		{
+			return GetDeclaredFields(type).ElementAtOrDefault(idx);
+		}
+
+		public static PropertyInfo DeclaredProperty(Type type, string name)
+		{
+			if (type == null || name == null) return null;
+			return type.GetProperty(name, all);
+		}
+
+		public static PropertyInfo Property(Type type, string name)
+		{
+			if (type == null || name == null) return null;
+			return FindIncludingBaseTypes(type, t => t.GetProperty(name, all));
+		}
+
+		public static MethodInfo DeclaredMethod(Type type, string name, Type[] parameters = null, Type[] generics = null)
+		{
+			if (type == null || name == null) return null;
+			MethodInfo result;
+			var modifiers = new ParameterModifier[] { };
+
+			if (parameters == null)
+				result = type.GetMethod(name, all);
+			else
+				result = type.GetMethod(name, all, null, parameters, modifiers);
+
+			if (result == null) return null;
+			if (generics != null) result = result.MakeGenericMethod(generics);
+			return result;
+		}
+
+		public static MethodInfo Method(Type type, string name, Type[] parameters = null, Type[] generics = null)
+		{
+			if (type == null || name == null) return null;
+			MethodInfo result;
+			var modifiers = new ParameterModifier[] { };
+			if (parameters == null)
+			{
+				try
+				{
+					result = FindIncludingBaseTypes(type, t => t.GetMethod(name, all));
+				}
+				catch (AmbiguousMatchException)
+				{
+					result = FindIncludingBaseTypes(type, t => t.GetMethod(name, all, null, new Type[0], modifiers));
+				}
+			}
+			else
+			{
+				result = FindIncludingBaseTypes(type, t => t.GetMethod(name, all, null, parameters, modifiers));
+			}
+			if (result == null) return null;
+			if (generics != null) result = result.MakeGenericMethod(generics);
+			return result;
+		}
+
+		public static MethodInfo Method(string typeColonMethodname, Type[] parameters = null, Type[] generics = null)
+		{
+			if (typeColonMethodname == null) return null;
+			var parts = typeColonMethodname.Split(':');
+			if (parts.Length != 2)
+				throw new ArgumentException("Method must be specified as 'Namespace.Type1.Type2:MethodName", nameof(typeColonMethodname));
+
+			var type = TypeByName(parts[0]);
+			return Method(type, parts[1], parameters, generics);
+		}
+
+		public static List<string> GetMethodNames(Type type)
+		{
+			if (type == null) return new List<string>();
+			return type.GetMethods(all).Select(m => m.Name).ToList();
+		}
+
+		public static List<string> GetMethodNames(object instance)
+		{
+			if (instance == null) return new List<string>();
+			return GetMethodNames(instance.GetType());
+		}
+
+		public static ConstructorInfo DeclaredConstructor(Type type, Type[] parameters = null)
+		{
+			if (type == null) return null;
+			if (parameters == null) parameters = new Type[0];
+			return type.GetConstructor(all, null, parameters, new ParameterModifier[] { });
+		}
+
+		public static ConstructorInfo Constructor(Type type, Type[] parameters = null)
+		{
+			if (type == null) return null;
+			if (parameters == null) parameters = new Type[0];
+			return FindIncludingBaseTypes(type, t => t.GetConstructor(all, null, parameters, new ParameterModifier[] { }));
+		}
+
+		public static List<ConstructorInfo> GetDeclaredConstructors(Type type)
+		{
+			return type.GetConstructors(all).Where(method => method.DeclaringType == type).ToList();
+		}
+
+		public static List<MethodInfo> GetDeclaredMethods(Type type)
+		{
+			return type.GetMethods(all).Where(method => method.DeclaringType == type).ToList();
+		}
+
+		public static List<PropertyInfo> GetDeclaredProperties(Type type)
+		{
+			return type.GetProperties(all).Where(property => property.DeclaringType == type).ToList();
+		}
+
+		public static List<FieldInfo> GetDeclaredFields(Type type)
+		{
+			return type.GetFields(all).Where(field => field.DeclaringType == type).ToList();
+		}
+
+		public static Type GetReturnedType(MethodBase method)
+		{
+			var constructor = method as ConstructorInfo;
+			if (constructor != null) return typeof(void);
+			return ((MethodInfo)method).ReturnType;
+		}
+
+		public static Type Inner(Type type, string name)
+		{
+			if (type == null || name == null) return null;
+			return FindIncludingBaseTypes(type, t => t.GetNestedType(name, all));
+		}
+
+		public static Type FirstInner(Type type, Func<Type, bool> predicate)
+		{
+			if (type == null || predicate == null) return null;
+			return type.GetNestedTypes(all).FirstOrDefault(subType => predicate(subType));
+		}
+
+		public static MethodInfo FirstMethod(Type type, Func<MethodInfo, bool> predicate)
+		{
+			if (type == null || predicate == null) return null;
+			return type.GetMethods(all).FirstOrDefault(method => predicate(method));
+		}
+
+		public static ConstructorInfo FirstConstructor(Type type, Func<ConstructorInfo, bool> predicate)
+		{
+			if (type == null || predicate == null) return null;
+			return type.GetConstructors(all).FirstOrDefault(constructor => predicate(constructor));
+		}
+
+		public static PropertyInfo FirstProperty(Type type, Func<PropertyInfo, bool> predicate)
+		{
+			if (type == null || predicate == null) return null;
+			return type.GetProperties(all).FirstOrDefault(property => predicate(property));
+		}
+
+		public static Type[] GetTypes(object[] parameters)
+		{
+			if (parameters == null) return new Type[0];
+			return parameters.Select(p => p == null ? typeof(object) : p.GetType()).ToArray();
+		}
+
+		public static List<string> GetFieldNames(Type type)
+		{
+			if (type == null) return new List<string>();
+			return type.GetFields(all).Select(f => f.Name).ToList();
+		}
+
+		public static List<string> GetFieldNames(object instance)
+		{
+			if (instance == null) return new List<string>();
+			return GetFieldNames(instance.GetType());
+		}
+
+		public static List<string> GetPropertyNames(Type type)
+		{
+			if (type == null) return new List<string>();
+			return type.GetProperties(all).Select(f => f.Name).ToList();
+		}
+
+		public static List<string> GetPropertyNames(object instance)
+		{
+			if (instance == null) return new List<string>();
+			return GetPropertyNames(instance.GetType());
+		}
+
+		public delegate ref U FieldRef<T, U>(T obj);
+		public static FieldRef<T, U> FieldRefAccess<T, U>(string fieldName)
+		{
+			const BindingFlags bf = BindingFlags.NonPublic |
+											BindingFlags.Instance |
+											BindingFlags.DeclaredOnly;
+
+			var fi = typeof(T).GetField(fieldName, bf);
+			if (fi == null)
+				throw new MissingFieldException(typeof(T).Name, fieldName);
+
+			var s_name = "__refget_" + typeof(T).Name + "_fi_" + fi.Name;
+
+			// workaround for using ref-return with DynamicMethod:
+			// a.) initialize with dummy return value
+			var dm = new DynamicMethod(s_name, typeof(U), new[] { typeof(T) }, typeof(T), true);
+
+			// b.) replace with desired 'ByRef' return value
+			var trv = Traverse.Create(dm);
+			trv.Field("returnType").SetValue(typeof(U).MakeByRefType());
+			trv.Field("m_returnType").SetValue(typeof(U).MakeByRefType());
+
+			var il = dm.GetILGenerator();
+			il.Emit(OpCodes.Ldarg_0);
+			il.Emit(OpCodes.Ldflda, fi);
+			il.Emit(OpCodes.Ret);
+			return (FieldRef<T, U>)dm.CreateDelegate(typeof(FieldRef<T, U>));
+		}
+
+		public static ref U FieldRefAccess<T, U>(T instance, string fieldName)
+		{
+			return ref FieldRefAccess<T, U>(fieldName)(instance);
+		}
+
+		public static void ThrowMissingMemberException(Type type, params string[] names)
+		{
+			var fields = string.Join(",", GetFieldNames(type).ToArray());
+			var properties = string.Join(",", GetPropertyNames(type).ToArray());
+			throw new MissingMemberException(string.Join(",", names) + "; available fields: " + fields + "; available properties: " + properties);
+		}
+
+		public static object GetDefaultValue(Type type)
+		{
+			if (type == null) return null;
+			if (type == typeof(void)) return null;
+			if (type.IsValueType)
+				return Activator.CreateInstance(type);
+			return null;
+		}
+
+		public static object CreateInstance(Type type)
+		{
+			if (type == null)
+				throw new NullReferenceException("Cannot create instance for NULL type");
+			var ctor = type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, new Type[0], null);
+			if (ctor != null)
+				return Activator.CreateInstance(type);
+			return FormatterServices.GetUninitializedObject(type);
+		}
+
+		public static object MakeDeepCopy(object source, Type resultType, Func<string, Traverse, Traverse, object> processor = null, string pathRoot = "")
+		{
+			if (source == null)
+				return null;
+
+			var type = source.GetType();
+
+			if (type.IsPrimitive)
+				return source;
+
+			if (type.IsEnum)
+				return Enum.ToObject(resultType, (int)source);
+
+			if (type.IsGenericType && resultType.IsGenericType)
+			{
+				var addOperation = FirstMethod(resultType, m => m.Name == "Add" && m.GetParameters().Count() == 1);
+				if (addOperation != null)
+				{
+					var addableResult = Activator.CreateInstance(resultType);
+					var addInvoker = MethodInvoker.GetHandler(addOperation);
+					var newElementType = resultType.GetGenericArguments()[0];
+					var i = 0;
+					foreach (var element in source as IEnumerable)
+					{
+						var iStr = (i++).ToString();
+						var path = pathRoot.Length > 0 ? pathRoot + "." + iStr : iStr;
+						var newElement = MakeDeepCopy(element, newElementType, processor, path);
+						addInvoker(addableResult, new object[] { newElement });
+					}
+					return addableResult;
+				}
+
+				// TODO: add dictionaries support
+				// maybe use methods in Dictionary<KeyValuePair<TKey,TVal>>
+			}
+
+			if (type.IsArray && resultType.IsArray)
+			{
+				var elementType = resultType.GetElementType();
+				var length = ((Array)source).Length;
+				var arrayResult = Activator.CreateInstance(resultType, new object[] { length }) as object[];
+				var originalArray = source as object[];
+				for (var i = 0; i < length; i++)
+				{
+					var iStr = i.ToString();
+					var path = pathRoot.Length > 0 ? pathRoot + "." + iStr : iStr;
+					arrayResult[i] = MakeDeepCopy(originalArray[i], elementType, processor, path);
+				}
+				return arrayResult;
+			}
+
+			var ns = type.Namespace;
+			if (ns == "System" || (ns?.StartsWith("System.") ?? false))
+				return source;
+
+			var result = CreateInstance(resultType);
+			Traverse.IterateFields(source, result, (name, src, dst) =>
+			{
+				var path = pathRoot.Length > 0 ? pathRoot + "." + name : name;
+				var value = processor != null ? processor(path, src, dst) : src.GetValue();
+				dst.SetValue(MakeDeepCopy(value, dst.GetValueType(), processor, path));
+			});
+			return result;
+		}
+
+		public static void MakeDeepCopy<T>(object source, out T result, Func<string, Traverse, Traverse, object> processor = null, string pathRoot = "")
+		{
+			result = (T)MakeDeepCopy(source, typeof(T), processor, pathRoot);
+		}
+
+		public static bool IsStruct(Type type)
+		{
+			return type.IsValueType && !IsValue(type) && !IsVoid(type);
+		}
+
+		public static bool IsClass(Type type)
+		{
+			return !type.IsValueType;
+		}
+
+		public static bool IsValue(Type type)
+		{
+			return type.IsPrimitive || type.IsEnum;
+		}
+
+		public static bool IsVoid(Type type)
+		{
+			return type == typeof(void);
+		}
+	}
+}

+ 82 - 0
Harmony12Interop/Tools/Extensions.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text.RegularExpressions;
+
+namespace Harmony
+{
+	public static class GeneralExtensions
+	{
+		public static string Join<T>(this IEnumerable<T> enumeration, Func<T, string> converter = null, string delimiter = ", ")
+		{
+			if (converter == null) converter = t => t.ToString();
+			return enumeration.Aggregate("", (prev, curr) => prev + (prev != "" ? delimiter : "") + converter(curr));
+		}
+
+		public static string Description(this Type[] parameters)
+		{
+			if (parameters == null) return "NULL";
+			var pattern = @", \w+, Version=[0-9.]+, Culture=neutral, PublicKeyToken=[0-9a-f]+";
+			return "(" + parameters.Join(p => p?.FullName == null ? "null" : Regex.Replace(p.FullName, pattern, "")) + ")";
+		}
+
+		public static string FullDescription(this MethodBase method)
+		{
+			var parameters = method.GetParameters().Select(p => p.ParameterType).ToArray();
+			return method.DeclaringType.FullName + "." + method.Name + parameters.Description();
+		}
+
+		public static Type[] Types(this ParameterInfo[] pinfo)
+		{
+			return pinfo.Select(pi => pi.ParameterType).ToArray();
+		}
+
+		public static T GetValueSafe<S, T>(this Dictionary<S, T> dictionary, S key)
+		{
+			T result;
+			if (dictionary.TryGetValue(key, out result))
+				return result;
+			return default(T);
+		}
+
+		public static T GetTypedValue<T>(this Dictionary<string, object> dictionary, string key)
+		{
+			object result;
+			if (dictionary.TryGetValue(key, out result))
+				if (result is T)
+					return (T)result;
+			return default(T);
+		}
+	}
+
+	public static class CollectionExtensions
+	{
+		public static void Do<T>(this IEnumerable<T> sequence, Action<T> action)
+		{
+			if (sequence == null) return;
+			var enumerator = sequence.GetEnumerator();
+			while (enumerator.MoveNext()) action(enumerator.Current);
+		}
+
+		public static void DoIf<T>(this IEnumerable<T> sequence, Func<T, bool> condition, Action<T> action)
+		{
+			sequence.Where(condition).Do(action);
+		}
+
+		public static IEnumerable<T> Add<T>(this IEnumerable<T> sequence, T item)
+		{
+			return (sequence ?? Enumerable.Empty<T>()).Concat(new[] { item });
+		}
+
+		public static T[] AddRangeToArray<T>(this T[] sequence, T[] items)
+		{
+			return (sequence ?? Enumerable.Empty<T>()).Concat(items).ToArray();
+		}
+
+		public static T[] AddToArray<T>(this T[] sequence, T item)
+		{
+			return Add(sequence, item).ToArray();
+		}
+	}
+}

+ 119 - 0
Harmony12Interop/Tools/FileLog.cs

@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Harmony
+{
+	public static class FileLog
+	{
+		public static string logPath;
+		public static char indentChar = '\t';
+		public static int indentLevel = 0;
+		static List<string> buffer = new List<string>();
+
+		static FileLog()
+		{
+			logPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + Path.DirectorySeparatorChar + "harmony.log.txt";
+		}
+
+		static string IndentString()
+		{
+			return new string(indentChar, indentLevel);
+		}
+
+		public static void ChangeIndent(int delta)
+		{
+			indentLevel = Math.Max(0, indentLevel + delta);
+		}
+
+		// use this method only if you are sure that FlushBuffer will be called
+		// or else logging information is incomplete in case of a crash
+		//
+		public static void LogBuffered(string str)
+		{
+			lock (logPath)
+			{
+				buffer.Add(IndentString() + str);
+			}
+		}
+
+		public static void FlushBuffer()
+		{
+			lock (logPath)
+			{
+				if (buffer.Count > 0)
+				{
+					using (var writer = File.AppendText(logPath))
+					{
+						foreach (var str in buffer)
+							writer.WriteLine(str);
+					}
+					buffer.Clear();
+				}
+			}
+		}
+
+		// this is the slower method that flushes changes directly to the file
+		// to prevent missing information in case of a cache
+		//
+		public static void Log(string str)
+		{
+			lock (logPath)
+			{
+				using (var writer = File.AppendText(logPath))
+				{
+					writer.WriteLine(IndentString() + str);
+				}
+			}
+		}
+
+		public static void Reset()
+		{
+			lock (logPath)
+			{
+				var path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + Path.DirectorySeparatorChar + "harmony.log.txt";
+				File.Delete(path);
+			}
+		}
+
+		public static unsafe void LogBytes(long ptr, int len)
+		{
+			lock (logPath)
+			{
+				var p = (byte*)ptr;
+				var s = "";
+				for (var i = 1; i <= len; i++)
+				{
+					if (s == "") s = "#  ";
+					s = s + (*p).ToString("X2") + " ";
+					if (i > 1 || len == 1)
+					{
+						if (i % 8 == 0 || i == len)
+						{
+							Log(s);
+							s = "";
+						}
+						else if (i % 4 == 0)
+							s = s + " ";
+					}
+					p++;
+				}
+
+				var arr = new byte[len];
+				Marshal.Copy((IntPtr)ptr, arr, 0, len);
+				var md5Hash = MD5.Create();
+				var hash = md5Hash.ComputeHash(arr);
+#pragma warning disable XS0001
+				var sBuilder = new StringBuilder();
+#pragma warning restore XS0001
+				for (var i = 0; i < hash.Length; i++)
+					sBuilder.Append(hash[i].ToString("X2"));
+				Log("HASH: " + sBuilder);
+			}
+		}
+	}
+}

+ 26 - 0
Harmony12Interop/Tools/PatchTools.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace Harmony
+{
+	public static class PatchTools
+	{
+		public static MethodInfo GetPatchMethod<T>(Type patchType, string name, Type[] parameters = null)
+		{
+			var method = patchType.GetMethods(AccessTools.all)
+				.FirstOrDefault(m => m.GetCustomAttributes(typeof(T), true).Any());
+			if (method == null)
+				method = AccessTools.Method(patchType, name, parameters);
+			return method;
+		}
+
+		public static void GetPatches(Type patchType, out MethodInfo prefix, out MethodInfo postfix, out MethodInfo transpiler)
+		{
+			prefix = GetPatchMethod<HarmonyPrefix>(patchType, "Prefix");
+			postfix = GetPatchMethod<HarmonyPostfix>(patchType, "Postfix");
+			transpiler = GetPatchMethod<HarmonyTranspiler>(patchType, "Transpiler");
+		}
+	}
+}

+ 61 - 0
Harmony12Interop/Tools/SymbolExtensions.cs

@@ -0,0 +1,61 @@
+using System.Linq.Expressions;
+using System.Reflection;
+using System;
+
+namespace Harmony
+{
+	public static class SymbolExtensions
+	{
+		/// <summary>
+		/// Given a lambda expression that calls a method, returns the method info.
+		/// </summary>
+		/// <typeparam name="T"></typeparam>
+		/// <param name="expression">The expression.</param>
+		/// <returns></returns>
+		public static MethodInfo GetMethodInfo(Expression<Action> expression)
+		{
+			return GetMethodInfo((LambdaExpression)expression);
+		}
+
+		/// <summary>
+		/// Given a lambda expression that calls a method, returns the method info.
+		/// </summary>
+		/// <typeparam name="T"></typeparam>
+		/// <param name="expression">The expression.</param>
+		/// <returns></returns>
+		public static MethodInfo GetMethodInfo<T>(Expression<Action<T>> expression)
+		{
+			return GetMethodInfo((LambdaExpression)expression);
+		}
+
+		/// <summary>
+		/// Given a lambda expression that calls a method, returns the method info.
+		/// </summary>
+		/// <typeparam name="T"></typeparam>
+		/// <param name="expression">The expression.</param>
+		/// <returns></returns>
+		public static MethodInfo GetMethodInfo<T, TResult>(Expression<Func<T, TResult>> expression)
+		{
+			return GetMethodInfo((LambdaExpression)expression);
+		}
+
+		/// <summary>
+		/// Given a lambda expression that calls a method, returns the method info.
+		/// </summary>
+		/// <param name="expression">The expression.</param>
+		/// <returns></returns>
+		public static MethodInfo GetMethodInfo(LambdaExpression expression)
+		{
+			var outermostExpression = expression.Body as MethodCallExpression;
+
+			if (outermostExpression == null)
+				throw new ArgumentException("Invalid Expression. Expression should consist of a Method call only.");
+
+			var method = outermostExpression.Method;
+			if (method == null)
+				throw new Exception("Cannot find method for expression " + expression);
+
+			return method;
+		}
+	}
+}

+ 298 - 0
Harmony12Interop/Tools/Traverse.cs

@@ -0,0 +1,298 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+namespace Harmony
+{
+	public class Traverse<T>
+	{
+		private Traverse traverse;
+
+		Traverse()
+		{
+		}
+
+		public Traverse(Traverse traverse)
+		{
+			this.traverse = traverse;
+		}
+
+		public T Value
+		{
+			get => traverse.GetValue<T>();
+			set => traverse.SetValue(value);
+		}
+	}
+
+	public class Traverse
+	{
+		static AccessCache Cache;
+
+		Type _type;
+		object _root;
+		MemberInfo _info;
+		MethodBase _method;
+		object[] _params;
+
+		[MethodImpl(MethodImplOptions.Synchronized)]
+		static Traverse()
+		{
+			if (Cache == null)
+				Cache = new AccessCache();
+		}
+
+		public static Traverse Create(Type type)
+		{
+			return new Traverse(type);
+		}
+
+		public static Traverse Create<T>()
+		{
+			return Create(typeof(T));
+		}
+
+		public static Traverse Create(object root)
+		{
+			return new Traverse(root);
+		}
+
+		public static Traverse CreateWithType(string name)
+		{
+			return new Traverse(AccessTools.TypeByName(name));
+		}
+
+		Traverse()
+		{
+		}
+
+		public Traverse(Type type)
+		{
+			_type = type;
+		}
+
+		public Traverse(object root)
+		{
+			_root = root;
+			_type = root?.GetType();
+		}
+
+		Traverse(object root, MemberInfo info, object[] index)
+		{
+			_root = root;
+			_type = root?.GetType();
+			_info = info;
+			_params = index;
+		}
+
+		Traverse(object root, MethodInfo method, object[] parameter)
+		{
+			_root = root;
+			_type = method.ReturnType;
+			_method = method;
+			_params = parameter;
+		}
+
+		public object GetValue()
+		{
+			if (_info is FieldInfo)
+				return ((FieldInfo)_info).GetValue(_root);
+			if (_info is PropertyInfo)
+				return ((PropertyInfo)_info).GetValue(_root, AccessTools.all, null, _params, CultureInfo.CurrentCulture);
+			if (_method != null)
+				return _method.Invoke(_root, _params);
+			if (_root == null && _type != null) return _type;
+			return _root;
+		}
+
+		public T GetValue<T>()
+		{
+			var value = GetValue();
+			if (value == null) return default(T);
+			return (T)value;
+		}
+
+		public object GetValue(params object[] arguments)
+		{
+			if (_method == null)
+				throw new Exception("cannot get method value without method");
+			return _method.Invoke(_root, arguments);
+		}
+
+		public T GetValue<T>(params object[] arguments)
+		{
+			if (_method == null)
+				throw new Exception("cannot get method value without method");
+			return (T)_method.Invoke(_root, arguments);
+		}
+
+		public Traverse SetValue(object value)
+		{
+			if (_info is FieldInfo)
+				((FieldInfo)_info).SetValue(_root, value, AccessTools.all, null, CultureInfo.CurrentCulture);
+			if (_info is PropertyInfo)
+				((PropertyInfo)_info).SetValue(_root, value, AccessTools.all, null, _params, CultureInfo.CurrentCulture);
+			if (_method != null)
+				throw new Exception("cannot set value of method " + _method.FullDescription());
+			return this;
+		}
+
+		public Type GetValueType()
+		{
+			if (_info is FieldInfo)
+				return ((FieldInfo)_info).FieldType;
+			if (_info is PropertyInfo)
+				return ((PropertyInfo)_info).PropertyType;
+			return null;
+		}
+
+		Traverse Resolve()
+		{
+			if (_root == null && _type != null) return this;
+			return new Traverse(GetValue());
+		}
+
+		public Traverse Type(string name)
+		{
+			if (name == null) throw new ArgumentNullException("name cannot be null");
+			if (_type == null) return new Traverse();
+			var type = AccessTools.Inner(_type, name);
+			if (type == null) return new Traverse();
+			return new Traverse(type);
+		}
+
+		public Traverse Field(string name)
+		{
+			if (name == null) throw new ArgumentNullException("name cannot be null");
+			var resolved = Resolve();
+			if (resolved._type == null) return new Traverse();
+			var info = Cache.GetFieldInfo(resolved._type, name);
+			if (info == null) return new Traverse();
+			if (info.IsStatic == false && resolved._root == null) return new Traverse();
+			return new Traverse(resolved._root, info, null);
+		}
+
+		public Traverse<T> Field<T>(string name)
+		{
+			return new Traverse<T>(Field(name));
+		}
+
+		public List<string> Fields()
+		{
+			var resolved = Resolve();
+			return AccessTools.GetFieldNames(resolved._type);
+		}
+
+		public Traverse Property(string name, object[] index = null)
+		{
+			if (name == null) throw new ArgumentNullException("name cannot be null");
+			var resolved = Resolve();
+			if (resolved._root == null || resolved._type == null) return new Traverse();
+			var info = Cache.GetPropertyInfo(resolved._type, name);
+			if (info == null) return new Traverse();
+			return new Traverse(resolved._root, info, index);
+		}
+
+		public Traverse<T> Property<T>(string name, object[] index = null)
+		{
+			return new Traverse<T>(Property(name, index));
+		}
+
+		public List<string> Properties()
+		{
+			var resolved = Resolve();
+			return AccessTools.GetPropertyNames(resolved._type);
+		}
+
+		public Traverse Method(string name, params object[] arguments)
+		{
+			if (name == null) throw new ArgumentNullException("name cannot be null");
+			var resolved = Resolve();
+			if (resolved._type == null) return new Traverse();
+			var types = AccessTools.GetTypes(arguments);
+			var method = Cache.GetMethodInfo(resolved._type, name, types);
+			if (method == null) return new Traverse();
+			return new Traverse(resolved._root, (MethodInfo)method, arguments);
+		}
+
+		public Traverse Method(string name, Type[] paramTypes, object[] arguments = null)
+		{
+			if (name == null) throw new ArgumentNullException("name cannot be null");
+			var resolved = Resolve();
+			if (resolved._type == null) return new Traverse();
+			var method = Cache.GetMethodInfo(resolved._type, name, paramTypes);
+			if (method == null) return new Traverse();
+			return new Traverse(resolved._root, (MethodInfo)method, arguments);
+		}
+
+		public List<string> Methods()
+		{
+			var resolved = Resolve();
+			return AccessTools.GetMethodNames(resolved._type);
+		}
+
+		public bool FieldExists()
+		{
+			return _info != null;
+		}
+
+		public bool MethodExists()
+		{
+			return _method != null;
+		}
+
+		public bool TypeExists()
+		{
+			return _type != null;
+		}
+
+		public static void IterateFields(object source, Action<Traverse> action)
+		{
+			var sourceTrv = Create(source);
+			AccessTools.GetFieldNames(source).ForEach(f => action(sourceTrv.Field(f)));
+		}
+
+		public static void IterateFields(object source, object target, Action<Traverse, Traverse> action)
+		{
+			var sourceTrv = Create(source);
+			var targetTrv = Create(target);
+			AccessTools.GetFieldNames(source).ForEach(f => action(sourceTrv.Field(f), targetTrv.Field(f)));
+		}
+
+		public static void IterateFields(object source, object target, Action<string, Traverse, Traverse> action)
+		{
+			var sourceTrv = Create(source);
+			var targetTrv = Create(target);
+			AccessTools.GetFieldNames(source).ForEach(f => action(f, sourceTrv.Field(f), targetTrv.Field(f)));
+		}
+
+		public static void IterateProperties(object source, Action<Traverse> action)
+		{
+			var sourceTrv = Create(source);
+			AccessTools.GetPropertyNames(source).ForEach(f => action(sourceTrv.Property(f)));
+		}
+
+		public static void IterateProperties(object source, object target, Action<Traverse, Traverse> action)
+		{
+			var sourceTrv = Create(source);
+			var targetTrv = Create(target);
+			AccessTools.GetPropertyNames(source).ForEach(f => action(sourceTrv.Property(f), targetTrv.Property(f)));
+		}
+
+		public static void IterateProperties(object source, object target, Action<string, Traverse, Traverse> action)
+		{
+			var sourceTrv = Create(source);
+			var targetTrv = Create(target);
+			AccessTools.GetPropertyNames(source).ForEach(f => action(f, sourceTrv.Property(f), targetTrv.Property(f)));
+		}
+
+		public static Action<Traverse, Traverse> CopyFields = (from, to) => { to.SetValue(from.GetValue()); };
+
+		public override string ToString()
+		{
+			var value = _method ?? GetValue();
+			return value?.ToString();
+		}
+	}
+}

+ 38 - 0
Harmony12Interop/Transpilers.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace Harmony
+{
+	public static class Transpilers
+	{
+		public static IEnumerable<CodeInstruction> MethodReplacer(this IEnumerable<CodeInstruction> instructions, MethodBase from, MethodBase to)
+		{
+			if (from == null)
+				throw new ArgumentException("Unexpected null argument", nameof(from));
+			if (to == null)
+				throw new ArgumentException("Unexpected null argument", nameof(to));
+
+			foreach (var instruction in instructions)
+			{
+				var method = instruction.operand as MethodBase;
+				if (method == from)
+				{
+					instruction.opcode = to.IsConstructor ? OpCodes.Newobj : OpCodes.Call;
+					instruction.operand = to;
+				}
+				yield return instruction;
+			}
+		}
+
+		public static IEnumerable<CodeInstruction> DebugLogger(this IEnumerable<CodeInstruction> instructions, string text)
+		{
+			yield return new CodeInstruction(OpCodes.Ldstr, text);
+			yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(FileLog), nameof(FileLog.Log)));
+			foreach (var instruction in instructions) yield return instruction;
+		}
+
+		// more added soon
+	}
+}