2 次代碼提交 c6167878f8 ... fce123cea2

作者 SHA1 備註 提交日期
  randoman fce123cea2 Version 3.1.0 6 年之前
  randoman 73f73c683a Fixed various problems 6 年之前
共有 53 個文件被更改,包括 2385 次插入334 次删除
  1. 4 1
      .gitignore
  2. 9 1
      CHANGELOG.md
  3. 30 1
      README.md
  4. 144 2
      XUnity.AutoTranslator.sln
  5. 74 27
      src/Translators/Lec.ExtProtocol/LecTranslationLibrary.cs
  6. 3 6
      src/Translators/Lec.ExtProtocol/UnmanagedLibraryLoader.cs
  7. 4 29
      src/Translators/Lec.ExtProtocol/UnmanagedTranslationLibrary.cs
  8. 1 1
      src/XUnity.AutoTranslator.Patcher/Patcher.cs
  9. 2 2
      src/XUnity.AutoTranslator.Plugin.BepIn/XUnity.AutoTranslator.Plugin.BepIn.csproj
  10. 17 45
      src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs
  11. 14 3
      src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs
  12. 6 1
      src/XUnity.AutoTranslator.Plugin.Core/Constants/ClrTypes.cs
  13. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Constants/PluginData.cs
  14. 40 15
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/AssemblyLoader.cs
  15. 22 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/ExceptionExtensions.cs
  16. 64 46
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/HarmonyInstanceExtensions.cs
  17. 81 9
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/TextComponentExtensions.cs
  18. 3 6
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/HooksSetup.cs
  19. 3 10
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/ImageHooks.cs
  20. 0 52
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/JumpedMethodCaller.cs
  21. 100 13
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/UtageHooks.cs
  22. 0 31
      src/XUnity.AutoTranslator.Plugin.Core/Kernel32.cs
  23. 10 4
      src/XUnity.AutoTranslator.Plugin.Core/Parsing/RichTextParser.cs
  24. 2 2
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/TextGetterCompatModeHelper.cs
  25. 4 3
      src/XUnity.AutoTranslator.Plugin.Core/XUnity.AutoTranslator.Plugin.Core.csproj
  26. 2 2
      src/XUnity.AutoTranslator.Plugin.IPA/XUnity.AutoTranslator.Plugin.IPA.csproj
  27. 2 2
      src/XUnity.AutoTranslator.Plugin.UnityInjector/XUnity.AutoTranslator.Plugin.UnityInjector.csproj
  28. 2 0
      src/XUnity.AutoTranslator.Setup/Program.cs
  29. 20 0
      src/XUnity.AutoTranslator.Setup/Properties/Resources.Designer.cs
  30. 6 0
      src/XUnity.AutoTranslator.Setup/Properties/Resources.resx
  31. 1 1
      src/XUnity.AutoTranslator.Setup/XUnity.AutoTranslator.Setup.csproj
  32. 169 0
      src/XUnity.RuntimeHooker.Core/HookCallbackInvoker.cs
  33. 25 0
      src/XUnity.RuntimeHooker.Core/HookMethod.cs
  34. 15 0
      src/XUnity.RuntimeHooker.Core/HookPriority.cs
  35. 21 0
      src/XUnity.RuntimeHooker.Core/JumpedMethodInfo.cs
  36. 18 0
      src/XUnity.RuntimeHooker.Core/TrampolineData.cs
  37. 82 0
      src/XUnity.RuntimeHooker.Core/TrampolineHandler.cs
  38. 13 0
      src/XUnity.RuntimeHooker.Core/Unmanaged/Kernel32.cs
  39. 34 0
      src/XUnity.RuntimeHooker.Core/Unmanaged/Protection.cs
  40. 81 0
      src/XUnity.RuntimeHooker.Core/Utilities/ExpressionHelper.cs
  41. 15 18
      src/XUnity.RuntimeHooker.Core/Utilities/MemoryHelper.cs
  42. 8 0
      src/XUnity.RuntimeHooker.Core/XUnity.RuntimeHooker.Core.csproj
  43. 142 0
      src/XUnity.RuntimeHooker.Trampolines/TrampolineInitializer.cs
  44. 555 0
      src/XUnity.RuntimeHooker.Trampolines/Trampolines.cs
  45. 12 0
      src/XUnity.RuntimeHooker.Trampolines/XUnity.RuntimeHooker.Trampolines.csproj
  46. 73 0
      src/XUnity.RuntimeHooker/Properties/Resources.Designer.cs
  47. 124 0
      src/XUnity.RuntimeHooker/Properties/Resources.resx
  48. 147 0
      src/XUnity.RuntimeHooker/RuntimeMethodPatcher.cs
  49. 30 0
      src/XUnity.RuntimeHooker/XUnity.RuntimeHooker.csproj
  50. 53 0
      test/XUnity.RuntimeHooker.Benchmark/Program.cs
  51. 12 0
      test/XUnity.RuntimeHooker.Benchmark/XUnity.RuntimeHooker.Benchmark.csproj
  52. 72 0
      test/XUnity.RuntimeHooker.ConsoleTests/Program.cs
  53. 13 0
      test/XUnity.RuntimeHooker.ConsoleTests/XUnity.RuntimeHooker.ConsoleTests.csproj

+ 4 - 1
.gitignore

@@ -1,4 +1,4 @@
-## Ignore Visual Studio temporary files, build results, and
+## Ignore Visual Studio temporary files, build results, and
 ## files generated by popular Visual Studio add-ons.
 
 # User-specific files
@@ -252,3 +252,6 @@ paket-files/
 
 # Distribution
 dist/
+
+# Trampoline dll
+**/b979b301af8b4ef48f224de6dcf2ddf6.dll

+ 9 - 1
CHANGELOG.md

@@ -1,4 +1,12 @@
-### 3.0.2
+### 3.1.0
+ * FEATURE - Support for games with 'netstandard2.0' API surface through config option 'EnableExperimentalHooks'
+ * BUG FIX - Bug fixes and improvements to Utage hooking implementation - EnableUtage config option also removed (always on now)
+ * BUG FIX - Rich text parser bug fixes when only a single tag with no ending was used
+ * BUG FIX - Fixed potential NullReferenceException in TextGetterCompatibilityMode
+ * BUG FIX - Load translator assemblies even if a '#' is present in file path
+ * MISC - Determine whether to disable certificate checks at config initialization based on scripting backend and unity version
+
+### 3.0.2
  * BUG FIX - UnityInjector installer package now uses correct folder structure (Translators has been moved into Config folder) and ExIni is no longer distributed
  * BUG FIX - Fixed harmony priority usage, which was incorrectly used in 3.0.1
  * MISC - Plugin should no longer translate text input fields for NGUI

+ 30 - 1
README.md

@@ -8,6 +8,7 @@
  * [Text Frameworks](#text-frameworks)
  * [Plugin Frameworks](#plugin-frameworks)
  * [Configuration](#configuration)
+ * [Frequently Asked Questions](#frequently-asked-questions)
  * [Translating Mods](#translating-mods)
  * [Manual Translations](#manual-translations)
  * [Regarding Redistribution](#regarding-redistribution)
@@ -210,7 +211,6 @@ EnableUGUI=True                  ;Enable or disable UGUI translation
 EnableNGUI=True                  ;Enable or disable NGUI translation
 EnableTextMeshPro=True           ;Enable or disable TextMeshPro translation
 EnableIMGUI=False                ;Enable or disable IMGUI translation
-EnableUtage=True                 ;Enable or disable Utage-specific translation
 AllowPluginHookOverride=True     ;Allow other text translation plugins to override this plugin's hooks
 
 [Behaviour]
@@ -236,6 +236,7 @@ GameLogTextPaths=                ;Indicates specific paths for game objects that
 RomajiPostProcessing=ReplaceMacronWithCircumflex;RemoveApostrophes ;Indicates what type of post processing to do on 'translated' romaji texts. This can be important in certain games because the font used does not support various diacritics properly. This is a list seperated by ';'. Possible values: ["RemoveAllDiacritics", "ReplaceMacronWithCircumflex", "RemoveApostrophes"]
 TranslationPostProcessing=ReplaceMacronWithCircumflex ;Indicates what type of post processing to do on translated texts (not romaji). Possible values: ["RemoveAllDiacritics", "ReplaceMacronWithCircumflex", "RemoveApostrophes"]
 EnableExperimentalHooks=False    ;Indicates whether to use experimental hooks to improve the hooking capability of the plugin (currently being tested)
+ForceExperimentalHooks=False     ;Indicates that the plugin must use experimental hooks instead of harmony hooks. Only used for debugging
 
 [Texture]
 TextureDirectory=Translation\Texture ;Directory to dump textures to, and root of directories to load images from. Can use placeholder: {GameExeName}
@@ -334,12 +335,39 @@ To rememdy this, post processing can be applied to translations when 'romaji' is
 
 This type of post processing is also applied to normal translations, but instead uses the option `TranslationPostProcessing`, which can use the same values.
 
+#### Experimental Hooks
+Experimental hooks are hooks are created at runtime, but not through the Harmony dependency. Harmony has two primary problems that these hooks attempt to solve:
+ * Harmony cannot hook methods with no body.
+ * Harmony cannot hook methods under the `netstandard2.0` API surface, which later versions of Unity can be build under.
+
+The experimental hooks implemented in this library can do this. However, unless it is absolutely required by the game, it is not recommended to enable them.
+
+The following configuration controls the experimental hooks:
+ * `EnableExperimentalHooks`: Will allow the plugin to use these hooks, if a standard Harmony hook cannot be applied.
+ * `ForceExperimentalHooks`: Forces the plugin to use experimental hooks over Harmony hooks. `EnableExperimentalHooks` must also be enabled for this to work. Only used for debugging.
+
 #### Other Options
  * `TextGetterCompatibilityMode`: This mode fools the game into thinking that the text displayed is not translated. This is required if the game uses text displayed to the user to determine what logic to execute. You can easily determine if this is required if you can see the functionality works fine if you toggle the translation off (hotkey: ALT+T).
  * `IgnoreTextStartingWith`: Disable translation for any texts starting with values in this ';-separated' setting. The [default value](https://www.charbase.com/180e-unicode-mongolian-vowel-separator) is an invisible character that takes up no space.
  * `CopyToClipboard`: Copy text to translate to the clipboard to support tools such as Translation Aggregator.
  * `Delay`: Required delay from a text appears until a translation request is queued in seconds. IMGUI not supported.
 
+## Frequently Asked Questions
+> **Q: Why doesn't this plugin work in game X?**  
+A: If the plugin does not work in a game, you can try to set the following configuration parameter `DisableCertificateValidation=True`. If it still does not work, it likely uses a technique to display text that this plugin is not aware of. In that case you can make an issue and maybe it will get fixed in a later version.
+
+> **Q: The game stops working when this plugin applies translations.**  
+A: Try setting the following configuration parameter `TextGetterCompatibilityMode=True`.
+
+> **Q: Can this plugin translate other plugins/mods?**  
+A: Likely yes, see [here](#translating-mods).
+
+> **Q: How do I use CustomTranslate?**  
+A: If you have to ask, you probably can't. CustomTranslate is intended for developers of a translation service. They would be able to expose an API that conforms to CustomTranslate's API specification without needing to implement a custom ITranslateEndpoint in this plugin as well.
+
+> **Q: Please provide support for translation service X.**  
+A: For now, additional support for services that does not require some form of authentication is unlikely. Do note though, that it is possible to implement custom translators independently of this plugin. And it takes remarkably little code to do so.
+
 ## Translating Mods
 Often other mods UI are implemented through IMGUI. As you can see above, this is disabled by default. By changing the "EnableIMGUI" value to "True", it will start translating IMGUI as well, which likely means that other mods UI will be translated.
 
@@ -356,6 +384,7 @@ In this context, the `Translation\_AutoGeneratedTranslations.{lang}.txt` (Output
 Redistributing this plugin for various games is absolutely encouraged. However, if you do so, please keep the following in mind:
  * **Distribute the _AutoGeneratedTranslations.{lang}.txt file along with the redistribution with as many translations as possible to ensure the online translator is hit as little as possible.**
  * **Do not redistribute the mod with the configuration option `EnableIMGUI=True`.**
+ * **Test your redistribution with logging/console enabled to ensure the game does not exhibit undesirable behaviour such as spamming the endpoints.**
  * Ensure you keep the plugin up-to-date, as much as reasonably possible.
  * If you use image loading feature, make sure you read [this section](#texture-translation).
 

+ 144 - 2
XUnity.AutoTranslator.sln

@@ -64,96 +64,233 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GoogleTranslateLegitimate",
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.AutoTranslator.Plugin.ExtProtocol", "src\XUnity.AutoTranslator.Plugin.ExtProtocol\XUnity.AutoTranslator.Plugin.ExtProtocol.csproj", "{65500234-7AD4-48B8-B51B-945ADBFF1133}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnity.AutoTranslator.Setup.Build", "src\XUnity.AutoTranslator.Setup.Build\XUnity.AutoTranslator.Setup.Build.csproj", "{848D741E-A4F2-4680-AFD2-74C8723CE3C4}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.AutoTranslator.Setup.Build", "src\XUnity.AutoTranslator.Setup.Build\XUnity.AutoTranslator.Setup.Build.csproj", "{848D741E-A4F2-4680-AFD2-74C8723CE3C4}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnity.AutoTranslator.Setup.Build-x86", "src\XUnity.AutoTranslator.Setup.Build-x86\XUnity.AutoTranslator.Setup.Build-x86.csproj", "{1C574D77-FCC8-4249-8890-0B00EF092705}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.AutoTranslator.Setup.Build-x86", "src\XUnity.AutoTranslator.Setup.Build-x86\XUnity.AutoTranslator.Setup.Build-x86.csproj", "{1C574D77-FCC8-4249-8890-0B00EF092705}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{2A4A3DDF-338C-40C0-8E26-2A810BAAADD6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.RuntimeHooker", "src\XUnity.RuntimeHooker\XUnity.RuntimeHooker.csproj", "{15A1D255-25F3-4D10-8053-F24B32C1CB7B}"
+	ProjectSection(ProjectDependencies) = postProject
+		{BE7ED338-106C-4CC6-BA19-8ADB2534326A} = {BE7ED338-106C-4CC6-BA19-8ADB2534326A}
+	EndProjectSection
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.RuntimeHooker.Core", "src\XUnity.RuntimeHooker.Core\XUnity.RuntimeHooker.Core.csproj", "{921151C6-C532-44E3-92A1-283E5094171F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.RuntimeHooker.Trampolines", "src\XUnity.RuntimeHooker.Trampolines\XUnity.RuntimeHooker.Trampolines.csproj", "{BE7ED338-106C-4CC6-BA19-8ADB2534326A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.RuntimeHooker.ConsoleTests", "test\XUnity.RuntimeHooker.ConsoleTests\XUnity.RuntimeHooker.ConsoleTests.csproj", "{20E57B16-F3C7-4D80-88FC-09D6C5A4CCC3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.RuntimeHooker.Benchmark", "test\XUnity.RuntimeHooker.Benchmark\XUnity.RuntimeHooker.Benchmark.csproj", "{E2F50278-9134-4DC8-9C50-4C0A52063A89}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
+		Debug|x86 = Debug|x86
 		Release|Any CPU = Release|Any CPU
+		Release|x86 = Release|x86
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{718A3B1D-A5E5-4223-AD53-45C60C874150}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{718A3B1D-A5E5-4223-AD53-45C60C874150}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{718A3B1D-A5E5-4223-AD53-45C60C874150}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{718A3B1D-A5E5-4223-AD53-45C60C874150}.Debug|x86.Build.0 = Debug|Any CPU
 		{718A3B1D-A5E5-4223-AD53-45C60C874150}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{718A3B1D-A5E5-4223-AD53-45C60C874150}.Release|Any CPU.Build.0 = Release|Any CPU
+		{718A3B1D-A5E5-4223-AD53-45C60C874150}.Release|x86.ActiveCfg = Release|Any CPU
+		{718A3B1D-A5E5-4223-AD53-45C60C874150}.Release|x86.Build.0 = Release|Any CPU
 		{5442ED94-2800-47A4-BBAC-C00FBA676D02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{5442ED94-2800-47A4-BBAC-C00FBA676D02}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5442ED94-2800-47A4-BBAC-C00FBA676D02}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{5442ED94-2800-47A4-BBAC-C00FBA676D02}.Debug|x86.Build.0 = Debug|Any CPU
 		{5442ED94-2800-47A4-BBAC-C00FBA676D02}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{5442ED94-2800-47A4-BBAC-C00FBA676D02}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5442ED94-2800-47A4-BBAC-C00FBA676D02}.Release|x86.ActiveCfg = Release|Any CPU
+		{5442ED94-2800-47A4-BBAC-C00FBA676D02}.Release|x86.Build.0 = Release|Any CPU
 		{C749698C-9E49-4CC3-8B45-62AE3AD0C938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{C749698C-9E49-4CC3-8B45-62AE3AD0C938}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C749698C-9E49-4CC3-8B45-62AE3AD0C938}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{C749698C-9E49-4CC3-8B45-62AE3AD0C938}.Debug|x86.Build.0 = Debug|Any CPU
 		{C749698C-9E49-4CC3-8B45-62AE3AD0C938}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{C749698C-9E49-4CC3-8B45-62AE3AD0C938}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C749698C-9E49-4CC3-8B45-62AE3AD0C938}.Release|x86.ActiveCfg = Release|Any CPU
+		{C749698C-9E49-4CC3-8B45-62AE3AD0C938}.Release|x86.Build.0 = Release|Any CPU
 		{0A2A6B66-91D4-4A4E-AC77-80C6DD748FCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{0A2A6B66-91D4-4A4E-AC77-80C6DD748FCD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0A2A6B66-91D4-4A4E-AC77-80C6DD748FCD}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{0A2A6B66-91D4-4A4E-AC77-80C6DD748FCD}.Debug|x86.Build.0 = Debug|Any CPU
 		{0A2A6B66-91D4-4A4E-AC77-80C6DD748FCD}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{0A2A6B66-91D4-4A4E-AC77-80C6DD748FCD}.Release|Any CPU.Build.0 = Release|Any CPU
+		{0A2A6B66-91D4-4A4E-AC77-80C6DD748FCD}.Release|x86.ActiveCfg = Release|Any CPU
+		{0A2A6B66-91D4-4A4E-AC77-80C6DD748FCD}.Release|x86.Build.0 = Release|Any CPU
 		{86BF1F46-44C1-4301-8314-6EC32F74575F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{86BF1F46-44C1-4301-8314-6EC32F74575F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{86BF1F46-44C1-4301-8314-6EC32F74575F}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{86BF1F46-44C1-4301-8314-6EC32F74575F}.Debug|x86.Build.0 = Debug|Any CPU
 		{86BF1F46-44C1-4301-8314-6EC32F74575F}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{86BF1F46-44C1-4301-8314-6EC32F74575F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{86BF1F46-44C1-4301-8314-6EC32F74575F}.Release|x86.ActiveCfg = Release|Any CPU
+		{86BF1F46-44C1-4301-8314-6EC32F74575F}.Release|x86.Build.0 = Release|Any CPU
 		{12F1D16B-B8E1-4A9D-B65A-044650F15440}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{12F1D16B-B8E1-4A9D-B65A-044650F15440}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{12F1D16B-B8E1-4A9D-B65A-044650F15440}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{12F1D16B-B8E1-4A9D-B65A-044650F15440}.Debug|x86.Build.0 = Debug|Any CPU
 		{12F1D16B-B8E1-4A9D-B65A-044650F15440}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{12F1D16B-B8E1-4A9D-B65A-044650F15440}.Release|Any CPU.Build.0 = Release|Any CPU
+		{12F1D16B-B8E1-4A9D-B65A-044650F15440}.Release|x86.ActiveCfg = Release|Any CPU
+		{12F1D16B-B8E1-4A9D-B65A-044650F15440}.Release|x86.Build.0 = Release|Any CPU
 		{7493BA4A-C688-4103-B161-7E578FBB6C0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{7493BA4A-C688-4103-B161-7E578FBB6C0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{7493BA4A-C688-4103-B161-7E578FBB6C0E}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{7493BA4A-C688-4103-B161-7E578FBB6C0E}.Debug|x86.Build.0 = Debug|Any CPU
 		{7493BA4A-C688-4103-B161-7E578FBB6C0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{7493BA4A-C688-4103-B161-7E578FBB6C0E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{7493BA4A-C688-4103-B161-7E578FBB6C0E}.Release|x86.ActiveCfg = Release|Any CPU
+		{7493BA4A-C688-4103-B161-7E578FBB6C0E}.Release|x86.Build.0 = Release|Any CPU
 		{5D9A4F4D-D2D2-4D2B-A58A-F7529DDAF1D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{5D9A4F4D-D2D2-4D2B-A58A-F7529DDAF1D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5D9A4F4D-D2D2-4D2B-A58A-F7529DDAF1D0}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{5D9A4F4D-D2D2-4D2B-A58A-F7529DDAF1D0}.Debug|x86.Build.0 = Debug|Any CPU
 		{5D9A4F4D-D2D2-4D2B-A58A-F7529DDAF1D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{5D9A4F4D-D2D2-4D2B-A58A-F7529DDAF1D0}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5D9A4F4D-D2D2-4D2B-A58A-F7529DDAF1D0}.Release|x86.ActiveCfg = Release|Any CPU
+		{5D9A4F4D-D2D2-4D2B-A58A-F7529DDAF1D0}.Release|x86.Build.0 = Release|Any CPU
 		{95C83913-8D7F-4FA1-877E-288251A2A461}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{95C83913-8D7F-4FA1-877E-288251A2A461}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{95C83913-8D7F-4FA1-877E-288251A2A461}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{95C83913-8D7F-4FA1-877E-288251A2A461}.Debug|x86.Build.0 = Debug|Any CPU
 		{95C83913-8D7F-4FA1-877E-288251A2A461}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{95C83913-8D7F-4FA1-877E-288251A2A461}.Release|Any CPU.Build.0 = Release|Any CPU
+		{95C83913-8D7F-4FA1-877E-288251A2A461}.Release|x86.ActiveCfg = Release|Any CPU
+		{95C83913-8D7F-4FA1-877E-288251A2A461}.Release|x86.Build.0 = Release|Any CPU
 		{DDE97F5D-E021-45C0-87B6-FAD35BC8BCFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{DDE97F5D-E021-45C0-87B6-FAD35BC8BCFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DDE97F5D-E021-45C0-87B6-FAD35BC8BCFB}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{DDE97F5D-E021-45C0-87B6-FAD35BC8BCFB}.Debug|x86.Build.0 = Debug|Any CPU
 		{DDE97F5D-E021-45C0-87B6-FAD35BC8BCFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{DDE97F5D-E021-45C0-87B6-FAD35BC8BCFB}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DDE97F5D-E021-45C0-87B6-FAD35BC8BCFB}.Release|x86.ActiveCfg = Release|Any CPU
+		{DDE97F5D-E021-45C0-87B6-FAD35BC8BCFB}.Release|x86.Build.0 = Release|Any CPU
 		{F49BA6D1-FAFB-414C-97B1-28B77FC21286}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{F49BA6D1-FAFB-414C-97B1-28B77FC21286}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F49BA6D1-FAFB-414C-97B1-28B77FC21286}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{F49BA6D1-FAFB-414C-97B1-28B77FC21286}.Debug|x86.Build.0 = Debug|Any CPU
 		{F49BA6D1-FAFB-414C-97B1-28B77FC21286}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{F49BA6D1-FAFB-414C-97B1-28B77FC21286}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F49BA6D1-FAFB-414C-97B1-28B77FC21286}.Release|x86.ActiveCfg = Release|Any CPU
+		{F49BA6D1-FAFB-414C-97B1-28B77FC21286}.Release|x86.Build.0 = Release|Any CPU
 		{CEA7D2D8-C2CF-4FA3-8184-DD485160CDAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{CEA7D2D8-C2CF-4FA3-8184-DD485160CDAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{CEA7D2D8-C2CF-4FA3-8184-DD485160CDAC}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{CEA7D2D8-C2CF-4FA3-8184-DD485160CDAC}.Debug|x86.Build.0 = Debug|Any CPU
 		{CEA7D2D8-C2CF-4FA3-8184-DD485160CDAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{CEA7D2D8-C2CF-4FA3-8184-DD485160CDAC}.Release|Any CPU.Build.0 = Release|Any CPU
+		{CEA7D2D8-C2CF-4FA3-8184-DD485160CDAC}.Release|x86.ActiveCfg = Release|Any CPU
+		{CEA7D2D8-C2CF-4FA3-8184-DD485160CDAC}.Release|x86.Build.0 = Release|Any CPU
 		{42DEC139-7E8E-4E2E-9BC9-0B5661F444C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{42DEC139-7E8E-4E2E-9BC9-0B5661F444C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{42DEC139-7E8E-4E2E-9BC9-0B5661F444C5}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{42DEC139-7E8E-4E2E-9BC9-0B5661F444C5}.Debug|x86.Build.0 = Debug|Any CPU
 		{42DEC139-7E8E-4E2E-9BC9-0B5661F444C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{42DEC139-7E8E-4E2E-9BC9-0B5661F444C5}.Release|Any CPU.Build.0 = Release|Any CPU
+		{42DEC139-7E8E-4E2E-9BC9-0B5661F444C5}.Release|x86.ActiveCfg = Release|Any CPU
+		{42DEC139-7E8E-4E2E-9BC9-0B5661F444C5}.Release|x86.Build.0 = Release|Any CPU
 		{A74F9486-2BA0-42FF-8BBC-9F07ECFBDCE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{A74F9486-2BA0-42FF-8BBC-9F07ECFBDCE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A74F9486-2BA0-42FF-8BBC-9F07ECFBDCE5}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{A74F9486-2BA0-42FF-8BBC-9F07ECFBDCE5}.Debug|x86.Build.0 = Debug|Any CPU
 		{A74F9486-2BA0-42FF-8BBC-9F07ECFBDCE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{A74F9486-2BA0-42FF-8BBC-9F07ECFBDCE5}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A74F9486-2BA0-42FF-8BBC-9F07ECFBDCE5}.Release|x86.ActiveCfg = Release|Any CPU
+		{A74F9486-2BA0-42FF-8BBC-9F07ECFBDCE5}.Release|x86.Build.0 = Release|Any CPU
 		{AE28F88E-E877-456B-98AB-BD03A59A3E44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{AE28F88E-E877-456B-98AB-BD03A59A3E44}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{AE28F88E-E877-456B-98AB-BD03A59A3E44}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{AE28F88E-E877-456B-98AB-BD03A59A3E44}.Debug|x86.Build.0 = Debug|Any CPU
 		{AE28F88E-E877-456B-98AB-BD03A59A3E44}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{AE28F88E-E877-456B-98AB-BD03A59A3E44}.Release|Any CPU.Build.0 = Release|Any CPU
+		{AE28F88E-E877-456B-98AB-BD03A59A3E44}.Release|x86.ActiveCfg = Release|Any CPU
+		{AE28F88E-E877-456B-98AB-BD03A59A3E44}.Release|x86.Build.0 = Release|Any CPU
 		{AD0C7EF2-D394-43B5-9CCE-FA8A0A820076}.Debug|Any CPU.ActiveCfg = Debug|x86
 		{AD0C7EF2-D394-43B5-9CCE-FA8A0A820076}.Debug|Any CPU.Build.0 = Debug|x86
+		{AD0C7EF2-D394-43B5-9CCE-FA8A0A820076}.Debug|x86.ActiveCfg = Debug|x86
+		{AD0C7EF2-D394-43B5-9CCE-FA8A0A820076}.Debug|x86.Build.0 = Debug|x86
 		{AD0C7EF2-D394-43B5-9CCE-FA8A0A820076}.Release|Any CPU.ActiveCfg = Release|x86
 		{AD0C7EF2-D394-43B5-9CCE-FA8A0A820076}.Release|Any CPU.Build.0 = Release|x86
+		{AD0C7EF2-D394-43B5-9CCE-FA8A0A820076}.Release|x86.ActiveCfg = Release|x86
+		{AD0C7EF2-D394-43B5-9CCE-FA8A0A820076}.Release|x86.Build.0 = Release|x86
 		{41B612A5-2974-4988-A82E-84EEF2212A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{41B612A5-2974-4988-A82E-84EEF2212A61}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{41B612A5-2974-4988-A82E-84EEF2212A61}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{41B612A5-2974-4988-A82E-84EEF2212A61}.Debug|x86.Build.0 = Debug|Any CPU
 		{41B612A5-2974-4988-A82E-84EEF2212A61}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{41B612A5-2974-4988-A82E-84EEF2212A61}.Release|Any CPU.Build.0 = Release|Any CPU
+		{41B612A5-2974-4988-A82E-84EEF2212A61}.Release|x86.ActiveCfg = Release|Any CPU
+		{41B612A5-2974-4988-A82E-84EEF2212A61}.Release|x86.Build.0 = Release|Any CPU
 		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Debug|x86.Build.0 = Debug|Any CPU
 		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Release|Any CPU.Build.0 = Release|Any CPU
+		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Release|x86.ActiveCfg = Release|Any CPU
+		{65500234-7AD4-48B8-B51B-945ADBFF1133}.Release|x86.Build.0 = Release|Any CPU
 		{848D741E-A4F2-4680-AFD2-74C8723CE3C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{848D741E-A4F2-4680-AFD2-74C8723CE3C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{848D741E-A4F2-4680-AFD2-74C8723CE3C4}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{848D741E-A4F2-4680-AFD2-74C8723CE3C4}.Debug|x86.Build.0 = Debug|Any CPU
 		{848D741E-A4F2-4680-AFD2-74C8723CE3C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{848D741E-A4F2-4680-AFD2-74C8723CE3C4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{848D741E-A4F2-4680-AFD2-74C8723CE3C4}.Release|x86.ActiveCfg = Release|Any CPU
+		{848D741E-A4F2-4680-AFD2-74C8723CE3C4}.Release|x86.Build.0 = Release|Any CPU
 		{1C574D77-FCC8-4249-8890-0B00EF092705}.Debug|Any CPU.ActiveCfg = Debug|x86
 		{1C574D77-FCC8-4249-8890-0B00EF092705}.Debug|Any CPU.Build.0 = Debug|x86
+		{1C574D77-FCC8-4249-8890-0B00EF092705}.Debug|x86.ActiveCfg = Debug|x86
+		{1C574D77-FCC8-4249-8890-0B00EF092705}.Debug|x86.Build.0 = Debug|x86
 		{1C574D77-FCC8-4249-8890-0B00EF092705}.Release|Any CPU.ActiveCfg = Release|x86
 		{1C574D77-FCC8-4249-8890-0B00EF092705}.Release|Any CPU.Build.0 = Release|x86
+		{1C574D77-FCC8-4249-8890-0B00EF092705}.Release|x86.ActiveCfg = Release|x86
+		{1C574D77-FCC8-4249-8890-0B00EF092705}.Release|x86.Build.0 = Release|x86
+		{15A1D255-25F3-4D10-8053-F24B32C1CB7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{15A1D255-25F3-4D10-8053-F24B32C1CB7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{15A1D255-25F3-4D10-8053-F24B32C1CB7B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{15A1D255-25F3-4D10-8053-F24B32C1CB7B}.Debug|x86.Build.0 = Debug|Any CPU
+		{15A1D255-25F3-4D10-8053-F24B32C1CB7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{15A1D255-25F3-4D10-8053-F24B32C1CB7B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{15A1D255-25F3-4D10-8053-F24B32C1CB7B}.Release|x86.ActiveCfg = Release|Any CPU
+		{15A1D255-25F3-4D10-8053-F24B32C1CB7B}.Release|x86.Build.0 = Release|Any CPU
+		{921151C6-C532-44E3-92A1-283E5094171F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{921151C6-C532-44E3-92A1-283E5094171F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{921151C6-C532-44E3-92A1-283E5094171F}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{921151C6-C532-44E3-92A1-283E5094171F}.Debug|x86.Build.0 = Debug|Any CPU
+		{921151C6-C532-44E3-92A1-283E5094171F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{921151C6-C532-44E3-92A1-283E5094171F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{921151C6-C532-44E3-92A1-283E5094171F}.Release|x86.ActiveCfg = Release|Any CPU
+		{921151C6-C532-44E3-92A1-283E5094171F}.Release|x86.Build.0 = Release|Any CPU
+		{BE7ED338-106C-4CC6-BA19-8ADB2534326A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{BE7ED338-106C-4CC6-BA19-8ADB2534326A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{BE7ED338-106C-4CC6-BA19-8ADB2534326A}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{BE7ED338-106C-4CC6-BA19-8ADB2534326A}.Debug|x86.Build.0 = Debug|Any CPU
+		{BE7ED338-106C-4CC6-BA19-8ADB2534326A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{BE7ED338-106C-4CC6-BA19-8ADB2534326A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{BE7ED338-106C-4CC6-BA19-8ADB2534326A}.Release|x86.ActiveCfg = Release|Any CPU
+		{BE7ED338-106C-4CC6-BA19-8ADB2534326A}.Release|x86.Build.0 = Release|Any CPU
+		{20E57B16-F3C7-4D80-88FC-09D6C5A4CCC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{20E57B16-F3C7-4D80-88FC-09D6C5A4CCC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{20E57B16-F3C7-4D80-88FC-09D6C5A4CCC3}.Debug|x86.ActiveCfg = Debug|x86
+		{20E57B16-F3C7-4D80-88FC-09D6C5A4CCC3}.Debug|x86.Build.0 = Debug|x86
+		{20E57B16-F3C7-4D80-88FC-09D6C5A4CCC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{20E57B16-F3C7-4D80-88FC-09D6C5A4CCC3}.Release|Any CPU.Build.0 = Release|Any CPU
+		{20E57B16-F3C7-4D80-88FC-09D6C5A4CCC3}.Release|x86.ActiveCfg = Release|x86
+		{20E57B16-F3C7-4D80-88FC-09D6C5A4CCC3}.Release|x86.Build.0 = Release|x86
+		{E2F50278-9134-4DC8-9C50-4C0A52063A89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E2F50278-9134-4DC8-9C50-4C0A52063A89}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E2F50278-9134-4DC8-9C50-4C0A52063A89}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{E2F50278-9134-4DC8-9C50-4C0A52063A89}.Debug|x86.Build.0 = Debug|Any CPU
+		{E2F50278-9134-4DC8-9C50-4C0A52063A89}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E2F50278-9134-4DC8-9C50-4C0A52063A89}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E2F50278-9134-4DC8-9C50-4C0A52063A89}.Release|x86.ActiveCfg = Release|Any CPU
+		{E2F50278-9134-4DC8-9C50-4C0A52063A89}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -180,6 +317,11 @@ Global
 		{65500234-7AD4-48B8-B51B-945ADBFF1133} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
 		{848D741E-A4F2-4680-AFD2-74C8723CE3C4} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
 		{1C574D77-FCC8-4249-8890-0B00EF092705} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
+		{15A1D255-25F3-4D10-8053-F24B32C1CB7B} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
+		{921151C6-C532-44E3-92A1-283E5094171F} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
+		{BE7ED338-106C-4CC6-BA19-8ADB2534326A} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
+		{20E57B16-F3C7-4D80-88FC-09D6C5A4CCC3} = {2A4A3DDF-338C-40C0-8E26-2A810BAAADD6}
+		{E2F50278-9134-4DC8-9C50-4C0A52063A89} = {2A4A3DDF-338C-40C0-8E26-2A810BAAADD6}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {EE803FED-4447-4D19-B3D6-88C56E8DFCCA}

+ 74 - 27
src/Translators/Lec.ExtProtocol/LecTranslationLibrary.cs

@@ -7,17 +7,39 @@ namespace Lec.ExtProtocol
 {
    class LecTranslationLibrary : UnmanagedTranslationLibrary
    {
-      public const int JapaneseCodepage = 932;
-
       [UnmanagedFunctionPointer( CallingConvention.Cdecl )]
       private delegate int eg_init( string path );
+
       [UnmanagedFunctionPointer( CallingConvention.Cdecl )]
       private delegate int eg_init2( string path, int i );
+
       [UnmanagedFunctionPointer( CallingConvention.Cdecl )]
       private delegate int eg_end();
+
       [UnmanagedFunctionPointer( CallingConvention.Cdecl )]
       private delegate int eg_translate_multi( int i, IntPtr in_str, int out_size, IntPtr out_str );
 
+      public const int ShiftJIS = 932;
+
+      private static IntPtr ConvertStringToNative( string managedString, int codepage )
+      {
+         var encoding = Encoding.GetEncoding( codepage );
+         var buffer = encoding.GetBytes( managedString );
+         IntPtr nativeUtf8 = Marshal.AllocHGlobal( buffer.Length + 1 );
+         Marshal.Copy( buffer, 0, nativeUtf8, buffer.Length );
+         Marshal.WriteByte( nativeUtf8, buffer.Length, 0 );
+         return nativeUtf8;
+      }
+
+      private static string ConvertNativeToString( IntPtr nativeUtf8, int codepage )
+      {
+         int len = 0;
+         while( Marshal.ReadByte( nativeUtf8, len ) != 0 ) ++len;
+         byte[] buffer = new byte[ len ];
+         Marshal.Copy( nativeUtf8, buffer, 0, buffer.Length );
+         return Encoding.GetEncoding( codepage ).GetString( buffer );
+      }
+
       private eg_end _end;
       private eg_translate_multi _translate;
       private eg_init _init;
@@ -35,35 +57,39 @@ namespace Lec.ExtProtocol
 
          foreach( var c in str )
          {
-            char o;
-
             switch( c )
             {
                case '『':
                case '「':
                case '「':
-                  o = '['; break;
+                  builder.Append( '[' );
+                  break;
                case '』':
                case '」':
                case '」':
-                  o = ']'; break;
+                  builder.Append( ']' );
+                  break;
                case '≪':
                case '(':
-                  o = '('; break;
+                  builder.Append( '(' );
+                  break;
                case '≫':
                case ')':
-                  o = ')'; break;
+                  builder.Append( ')' );
+                  break;
                case '…':
-                  o = ' '; break;
+                  builder.Append( "..." );
+                  break;
                case ':':
-                  o = '¦'; break;
+                  builder.Append( '¦' );
+                  break;
                case '・':
-                  o = '.'; break;
+                  builder.Append( '.' );
+                  break;
                default:
-                  o = c;
+                  builder.Append( c );
                   break;
             }
-            builder.Append( c );
          }
 
          return builder.ToString();
@@ -72,41 +98,56 @@ namespace Lec.ExtProtocol
       public override string Translate( string toTranslate )
       {
          toTranslate = PreprocessString( toTranslate );
+
+         // allocate three times the size of the untranslated text for the result (initial)
          var size = toTranslate.Length * 3;
-         int translatedSize;
          IntPtr ptr = IntPtr.Zero;
+         IntPtr str = IntPtr.Zero;
 
          // we can't know the output size, so just try until we have a big enough buffer
          try
          {
+            // get an unmanaged version of the untranslated text encoded as ShiftJIS
+            str = ConvertStringToNative( toTranslate, ShiftJIS );
+
+            int translatedSize;
             do
             {
+               // double the size of the allocated unmanaged memory
                size = size * 2;
-               // give up when we reach 10 MB string
+
+               // give up when we reach 10 MB
                if( size > 10 * 1024 * 1024 )
                {
                   return null;
                }
 
+               // free up previous allocated memory for the result
                if( ptr != IntPtr.Zero )
                {
                   Marshal.FreeHGlobal( ptr );
                }
+
+               // allocate memory for result
                ptr = Marshal.AllocHGlobal( size );
 
-               var str = ConvertStringToNative( toTranslate, JapaneseCodepage );
+               // perform translation
                translatedSize = _translate( 0, str, size, ptr );
 
-               Marshal.FreeHGlobal( str );
-
             } while( translatedSize > size );
 
-            var result = ConvertNativeToString( ptr, JapaneseCodepage );
+            // convert the unamanged ShiftJIS string to a managed C# string
+            var result = ConvertNativeToString( ptr, ShiftJIS );
 
             return result;
          }
          finally
          {
+            if( str != null )
+            {
+               Marshal.FreeHGlobal( str );
+            }
+
             if( ptr != IntPtr.Zero )
             {
                Marshal.FreeHGlobal( ptr );
@@ -114,16 +155,16 @@ namespace Lec.ExtProtocol
          }
       }
 
-      protected override bool OnInitialize( string libraryPath )
+      protected override void Initialize( string libraryPath )
       {
          try
          {
             _end = Loader.LoadFunction<eg_end>( "eg_end" );
             _translate = Loader.LoadFunction<eg_translate_multi>( "eg_translate_multi" );
          }
-         catch( Exception )
+         catch( Exception e )
          {
-            return false;
+            throw new Exception( $"Could not load functions from LEC library '{libraryPath}'.", e );
          }
 
          try
@@ -136,18 +177,24 @@ namespace Lec.ExtProtocol
             {
                _init = Loader.LoadFunction<eg_init>( "eg_init" );
             }
-            catch
+            catch( Exception e )
             {
-               return false;
+               throw new Exception( $"Could not load functions from LEC library '{libraryPath}'.", e );
             }
          }
 
          var directory = Path.GetDirectoryName( libraryPath ) + '\\';
 
-         var succeded = _init2?.Invoke( directory, 0 );
-         var initialized = succeded ?? _init( directory );
+         var initializationCode = _init2?.Invoke( directory, 0 );
+         if( initializationCode == null )
+         {
+            initializationCode = _init( directory );
+         }
 
-         return initialized == 0;
+         if( initializationCode != 0 )
+         {
+            throw new Exception( $"Could not initialize LEC library. Received code '{initializationCode}'." );
+         }
       }
 
       protected override void Dispose( bool disposing )

+ 3 - 6
src/Translators/Lec.ExtProtocol/UnmanagedLibraryLoader.cs

@@ -8,19 +8,16 @@ namespace Lec.ExtProtocol
       private IntPtr _libraryPointer;
       private bool _disposed = false;
 
-      public bool LoadLibrary( string path )
+      public void LoadLibrary( string path )
       {
          _libraryPointer = Kernel32.LoadLibrary( path );
-
-         return _libraryPointer != IntPtr.Zero;
+         if( _libraryPointer == IntPtr.Zero ) throw new Exception( $"Could not load the unmanaged library '{path}'." );
       }
 
       public TDelegate LoadFunction<TDelegate>( string name )
       {
-         if( _libraryPointer == IntPtr.Zero ) throw new InvalidOperationException( $"Could not load the function {name} because no library has been loaded!" );
-
          var addr = Kernel32.GetProcAddress( _libraryPointer, name );
-         if( addr == IntPtr.Zero ) throw new InvalidOperationException( $"Could not find the function pointer {name}!" );
+         if( addr == IntPtr.Zero ) throw new Exception( $"Could not find the function pointer for '{name}'." );
 
          return (TDelegate)(object)Marshal.GetDelegateForFunctionPointer( addr, typeof( TDelegate ) );
       }

+ 4 - 29
src/Translators/Lec.ExtProtocol/UnmanagedTranslationLibrary.cs

@@ -13,41 +13,16 @@ namespace Lec.ExtProtocol
 
       public UnmanagedTranslationLibrary( string libraryPath )
       {
-         var ok = Initialize( libraryPath );
+         if( !File.Exists( libraryPath ) ) throw new FileNotFoundException( "Could not find file.", libraryPath );
 
-         if( !ok ) throw new Exception( "Could not load library." );
+         Loader.LoadLibrary( libraryPath );
+         Initialize( libraryPath );
       }
 
-      public bool Initialize( string libraryPath )
-      {
-         if( !File.Exists( libraryPath ) ) return false;
-
-         return Loader.LoadLibrary( libraryPath ) && OnInitialize( libraryPath );
-      }
-
-      protected abstract bool OnInitialize( string libraryPath );
+      protected abstract void Initialize( string libraryPath );
 
       public abstract string Translate( string untranslatedText );
 
-      public static IntPtr ConvertStringToNative( string managedString, int codepage )
-      {
-         var encoding = Encoding.GetEncoding( codepage );
-         var buffer = encoding.GetBytes( managedString );
-         IntPtr nativeUtf8 = Marshal.AllocHGlobal( buffer.Length + 1 );
-         Marshal.Copy( buffer, 0, nativeUtf8, buffer.Length );
-         Marshal.WriteByte( nativeUtf8, buffer.Length, 0 );
-         return nativeUtf8;
-      }
-
-      public static string ConvertNativeToString( IntPtr nativeUtf8, int codepage )
-      {
-         int len = 0;
-         while( Marshal.ReadByte( nativeUtf8, len ) != 0 ) ++len;
-         byte[] buffer = new byte[ len ];
-         Marshal.Copy( nativeUtf8, buffer, 0, buffer.Length );
-         return Encoding.GetEncoding( codepage ).GetString( buffer );
-      }
-
       protected virtual void Dispose( bool disposing )
       {
          if( disposing )

+ 1 - 1
src/XUnity.AutoTranslator.Patcher/Patcher.cs

@@ -29,7 +29,7 @@ namespace XUnity.AutoTranslator.Patcher
       {
          get
          {
-            return "3.0.2";
+            return "3.1.0";
          }
       }
 

+ 2 - 2
src/XUnity.AutoTranslator.Plugin.BepIn/XUnity.AutoTranslator.Plugin.BepIn.csproj

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.0.2</Version>
+      <Version>3.1.0</Version>
    </PropertyGroup>
 
    <ItemGroup>
@@ -31,7 +31,7 @@
       <ItemGroup>
          <VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^(.+?)(\.0+)$&quot;, &quot;$1&quot;))" />
       </ItemGroup>
-      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)ExIni.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.ExtProtocol.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\BepIn\BepInEx' -DestinationPath '$(SolutionDir)dist\BepIn\XUnity.AutoTranslator-BepIn-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
+      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)ExIni.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.ExtProtocol.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.RuntimeHooker.Core.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.RuntimeHooker.dll&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\BepIn\BepInEx\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\BepIn\BepInEx' -DestinationPath '$(SolutionDir)dist\BepIn\XUnity.AutoTranslator-BepIn-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
    </Target>
 
 </Project>

+ 17 - 45
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs

@@ -99,9 +99,6 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private Dictionary<string, byte[]> _translatedImages = new Dictionary<string, byte[]>( StringComparer.InvariantCultureIgnoreCase );
       private HashSet<string> _untranslatedImages = new HashSet<string>();
 
-      private Component _advEngine;
-      private float? _nextAdvUpdate;
-
       private HttpSecurity _httpSecurity;
       private List<ConfiguredEndpoint> _configuredEndpoints;
       private ConfiguredEndpoint _endpoint;
@@ -213,12 +210,12 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
             XuaLogger.Current.Error( $"Could not find the configured endpoint '{Settings.ServiceEndpoint}'." );
          }
-         
+
          if( Settings.DisableCertificateValidation )
          {
             XuaLogger.Current.Info( $"Disabling certificate checks for endpoints because of configuration." );
 
-            ServicePointManager.ServerCertificateValidationCallback += (a1, a2, a3, a4) => true;
+            ServicePointManager.ServerCertificateValidationCallback += ( a1, a2, a3, a4 ) => true;
          }
          else
          {
@@ -278,7 +275,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
          catch( Exception e )
          {
-            XuaLogger.Current.Error( e, "An error occurred while settings up texture scene-load scans." );
+            XuaLogger.Current.Error( e, "An error occurred while settings up scene-load scans." );
          }
 
          LoadTranslations();
@@ -328,6 +325,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
          var t2 = new Thread( SaveTranslationsLoop );
          t2.IsBackground = true;
          t2.Start();
+
+         XuaLogger.Current.Info( $"Loaded XUnity.AutoTranslator into Unity [{Application.unityVersion}] game." );
       }
 
       private void OnEndpointSelected( ConfiguredEndpoint endpoint )
@@ -416,8 +415,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
          XuaLogger.Current.Info( "Probing whether OnLevelWasLoaded or SceneManager is supported in this version of Unity. Any warnings related to OnLevelWasLoaded coming from Unity can safely be ignored." );
          if( Features.SupportsSceneManager )
          {
-            XuaLogger.Current.Info( "SceneManager is supported in this version of Unity." );
             EnableSceneLoadScanInternal();
+            XuaLogger.Current.Info( "SceneManager is supported in this version of Unity." );
          }
          else
          {
@@ -1042,14 +1041,15 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          return _reverseTranslations.TryGetValue( value, out key );
       }
-      
+
       internal string Hook_TextChanged_WithResult( object ui, string text )
       {
          if( !ui.IsKnownTextType() ) return null;
 
          if( _textHooksEnabled && !_temporarilyDisabled )
          {
-            return TranslateOrQueueWebJob( ui, text, false );
+            var translation = TranslateOrQueueWebJob( ui, text, false );
+            return _isInTranslatedMode ? translation : null;
          }
          return null;
       }
@@ -1610,7 +1610,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private string TranslateOrQueueWebJobImmediate( object ui, string text, TextTranslationInfo info, bool supportsStabilization, bool ignoreComponentState, TranslationContext context = null )
       {
          text = text ?? ui.GetText();
-
+         
          // make sure text exists
          var originalText = text;
          if( context == null )
@@ -1667,18 +1667,18 @@ namespace XUnity.AutoTranslator.Plugin.Core
                      var result = UnityTextParsers.RichTextParser.Parse( text );
                      if( result.Succeeded )
                      {
-                        var isWhitelisted = ui.IsWhitelistedForImmediateRichTextTranslation();
+                        //var isWhitelisted = ui.IsWhitelistedForImmediateRichTextTranslation();
 
-                        translation = TranslateOrQueueWebJobImmediateByParserResult( ui, result, isWhitelisted );
+                        translation = TranslateOrQueueWebJobImmediateByParserResult( ui, result, false );
                         if( translation != null )
                         {
                            SetTranslatedText( ui, translation, info );
                            return translation;
                         }
-                        else if( isWhitelisted )
-                        {
-                           return null;
-                        }
+                        //else if( isWhitelisted )
+                        //{
+                        //   return null;
+                        //}
                      }
                   }
                }
@@ -2026,12 +2026,6 @@ namespace XUnity.AutoTranslator.Plugin.Core
                ResetThresholdTimerIfRequired();
                KickoffTranslations();
                FinishTranslations();
-
-               if( ClrTypes.AdvEngine != null && _nextAdvUpdate.HasValue && Time.time > _nextAdvUpdate )
-               {
-                  _nextAdvUpdate = null;
-                  UpdateUtageText();
-               }
             }
 
             // perform this check every 100 frames!
@@ -2212,7 +2206,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                   var endpoint = _endpoint;
                   _ongoingJobs[ key ] = job;
 
-                  XuaLogger.Current.Debug( "Started: " + untranslatedText );
+                  XuaLogger.Current.Debug( "Started: '" + untranslatedText + "'" );
                   StartCoroutine(
                      endpoint.Translate(
                         new[] { untranslatedText },
@@ -2494,32 +2488,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
                      }
                   }
                }
-
-
-               // Utage support
-               if( ClrTypes.AdvEngine != null
-                  && job.OriginalSources.Any( x => ClrTypes.AdvCommand.IsAssignableFrom( x.GetType() ) ) )
-               {
-                  _nextAdvUpdate = Time.time + 0.5f;
-               }
             }
          }
       }
 
-      private void UpdateUtageText()
-      {
-         // After an object is destroyed, an equality check with null will return true. The variable does not go to null, you can still call GetInstanceID() on it, but the "==" operator is overloaded and behaves as expected.
-         if( _advEngine == null || _advEngine?.gameObject == null )
-         {
-            _advEngine = (Component)GameObject.FindObjectOfType( Constants.ClrTypes.AdvEngine );
-         }
-
-         if( _advEngine != null )
-         {
-            AccessTools.Method( Constants.ClrTypes.AdvEngine, "ChangeLanguage" )?.Invoke( _advEngine, new object[ 0 ] );
-         }
-      }
-
       private void ReloadTranslations()
       {
          LoadTranslations();

+ 14 - 3
src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs

@@ -4,6 +4,7 @@ using System.Globalization;
 using System.IO;
 using System.Linq;
 using System.Text;
+using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Debugging;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
@@ -34,6 +35,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static string ApplicationName;
       public static float Timeout = 50.0f;
 
+      public static bool InvokeEvents = true;
+      public static Action<object> RemakeTextData = null;
 
       public static bool IsShutdown = false;
       public static bool IsShutdownFatal = false;
@@ -60,7 +63,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static bool EnableUGUI;
       public static bool EnableNGUI;
       public static bool EnableTextMeshPro;
-      public static bool EnableUtage;
       public static bool AllowPluginHookOverride;
       public static bool IgnoreWhitespaceInDialogue;
       public static bool IgnoreWhitespaceInNGUI;
@@ -84,6 +86,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static TextPostProcessing RomajiPostProcessing;
       public static TextPostProcessing TranslationPostProcessing;
       public static bool EnableExperimentalHooks;
+      public static bool ForceExperimentalHooks;
 
       public static string TextureDirectory;
       public static bool EnableTextureTranslation;
@@ -129,7 +132,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
          EnableUGUI = PluginEnvironment.Current.Preferences.GetOrDefault( "TextFrameworks", "EnableUGUI", true );
          EnableNGUI = PluginEnvironment.Current.Preferences.GetOrDefault( "TextFrameworks", "EnableNGUI", true );
          EnableTextMeshPro = PluginEnvironment.Current.Preferences.GetOrDefault( "TextFrameworks", "EnableTextMeshPro", true );
-         EnableUtage = PluginEnvironment.Current.Preferences.GetOrDefault( "TextFrameworks", "EnableUtage", true );
          AllowPluginHookOverride = PluginEnvironment.Current.Preferences.GetOrDefault( "TextFrameworks", "AllowPluginHookOverride", true );
 
          Delay = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "Delay", 0f );
@@ -157,6 +159,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
          RomajiPostProcessing = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "RomajiPostProcessing", TextPostProcessing.ReplaceMacronWithCircumflex | TextPostProcessing.RemoveApostrophes );
          TranslationPostProcessing = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "TranslationPostProcessing", TextPostProcessing.ReplaceMacronWithCircumflex );
          EnableExperimentalHooks = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "EnableExperimentalHooks", false );
+         ForceExperimentalHooks = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "ForceExperimentalHooks", false );
 
          TextureDirectory = PluginEnvironment.Current.Preferences.GetOrDefault( "Texture", "TextureDirectory", @"Translation\Texture" );
          EnableTextureTranslation = PluginEnvironment.Current.Preferences.GetOrDefault( "Texture", "EnableTextureTranslation", false );
@@ -174,7 +177,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
          }
 
          UserAgent = PluginEnvironment.Current.Preferences.GetOrDefault( "Http", "UserAgent", string.Empty );
-         DisableCertificateValidation = PluginEnvironment.Current.Preferences.GetOrDefault( "Http", "DisableCertificateValidation", false );
+         DisableCertificateValidation = PluginEnvironment.Current.Preferences.GetOrDefault( "Http", "DisableCertificateValidation", GetInitialDisableCertificateChecks() );
 
          EnablePrintHierarchy = PluginEnvironment.Current.Preferences.GetOrDefault( "Debug", "EnablePrintHierarchy", false );
          EnableConsole = PluginEnvironment.Current.Preferences.GetOrDefault( "Debug", "EnableConsole", false );
@@ -200,5 +203,13 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       private static void Migrate()
       {
       }
+
+      private static bool GetInitialDisableCertificateChecks()
+      {
+         var is2017 = Application.unityVersion.StartsWith( "2017" );
+         var isNet4x = Features.SupportsNet4x;
+
+         return is2017 && isNet4x;
+      }
    }
 }

+ 6 - 1
src/XUnity.AutoTranslator.Plugin.Core/Constants/ClrTypes.cs

@@ -38,6 +38,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
       public static readonly Type CustomYieldInstruction = FindType( "UnityEngine.CustomYieldInstruction" );
       public static readonly Type SceneManager = FindType( "UnityEngine.SceneManagement.SceneManager" );
       public static readonly Type Scene = FindType( "UnityEngine.SceneManagement.Scene" );
+      public static readonly Type UnityEventBase = FindType( "UnityEngine.Events.UnityEventBase" );
+      public static readonly Type BaseInvokableCall = FindType( "UnityEngine.Events.BaseInvokableCall" );
       //public static readonly Type TextMesh = FindType( "UnityEngine.TextMesh" );
       //public static readonly Type GraphicRaycaster = FindType( "UnityEngine.UI.GraphicRaycaster" );
 
@@ -45,9 +47,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
       public static readonly Type Typewriter = FindType( "Typewriter" );
 
       // Utage
+      //public static readonly Type AdvCommand = FindType( "Utage.AdvCommand" );
       public static readonly Type UguiNovelText = FindType( "Utage.UguiNovelText" );
-      public static readonly Type AdvCommand = FindType( "Utage.AdvCommand" );
       public static readonly Type AdvEngine = FindType( "Utage.AdvEngine" );
+      public static readonly Type AdvPage = FindType( "Utage.AdvPage" );
+      public static readonly Type TextData = FindType( "Utage.TextData" );
+      public static readonly Type AdvUguiMessageWindow = FindType( "Utage.AdvUguiMessageWindow" );
       public static readonly Type AdvDataManager = FindType( "Utage.AdvDataManager" );
       public static readonly Type AdvScenarioData = FindType( "Utage.AdvScenarioData" );
       public static readonly Type AdvScenarioLabelData = FindType( "Utage.AdvScenarioLabelData" );

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/Constants/PluginData.cs

@@ -23,6 +23,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
       /// <summary>
       /// Gets the version of the plugin.
       /// </summary>
-      public const string Version = "3.0.2";
+      public const string Version = "3.1.0";
    }
 }

+ 40 - 15
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/AssemblyLoader.cs

@@ -16,26 +16,51 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
          var files = Directory.GetFiles( directory, "*.dll" );
          var allTypes = new HashSet<Type>();
 
-         foreach( var file in files )
+         var currentDirectory = Environment.CurrentDirectory;
+         foreach( var relativeFilePath in files )
          {
-            try
-            {
-               var assemblyName = AssemblyName.GetAssemblyName( file );
-               var assembly = Assembly.Load( assemblyName );
-               var types = GetAllTypesOf<TService>( assembly );
-
-               foreach( var type in types )
-               {
-                  allTypes.Add( type );
-               }
-            }
-            catch( Exception e )
+            LoadAssembliesInFile<TService>( relativeFilePath, allTypes );
+         }
+
+         return allTypes.ToList();
+      }
+
+      private static bool LoadAssembliesInFile<TService>( string file, HashSet<Type> allTypes )
+      {
+         try
+         {
+            var assembly = LoadAssembly( file );
+            var types = GetAllTypesOf<TService>( assembly );
+
+            foreach( var type in types )
             {
-               XuaLogger.Current.Error( e, "An error occurred while loading types in assembly: " + file );
+               allTypes.Add( type );
             }
+
+            return true;
+         }
+         catch( Exception e )
+         {
+            XuaLogger.Current.Error( e, "An error occurred while loading types in assembly: " + file );
          }
 
-         return allTypes.ToList();
+         return false;
+      }
+
+      private static Assembly LoadAssembly( string file )
+      {
+         try
+         {
+            var assemblyName = AssemblyName.GetAssemblyName( file );
+            var assembly = Assembly.Load( assemblyName );
+            return assembly;
+         }
+         catch
+         {
+            // fallback to legacy API
+            var assembly = Assembly.LoadFrom( file );
+            return assembly;
+         }
       }
 
       internal static List<Type> GetAllTypesOf<TService>( Assembly assembly )

+ 22 - 0
src/XUnity.AutoTranslator.Plugin.Core/Extensions/ExceptionExtensions.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Extensions
+{
+   internal static class ExceptionExtensions
+   {
+      public static bool IsCausedBy<TException>( this Exception e )
+      {
+         var current = e;
+         while( current != null )
+         {
+            if( current is TException ) return true;
+            current = current.InnerException;
+         }
+
+         return false;
+      }
+   }
+}

+ 64 - 46
src/XUnity.AutoTranslator.Plugin.Core/Extensions/HarmonyInstanceExtensions.cs

@@ -9,6 +9,8 @@ using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Hooks;
 using XUnity.AutoTranslator.Plugin.Core.Utilities;
+using XUnity.RuntimeHooker;
+using XUnity.RuntimeHooker.Core;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 {
@@ -26,73 +28,82 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 
       public static void PatchType( this HarmonyInstance instance, Type type )
       {
+         MethodBase original = null;
          try
          {
-            var prepare = type.GetMethod( "Prepare", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public );
+            var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
+            var prepare = type.GetMethod( "Prepare", flags );
             if( prepare == null || (bool)prepare.Invoke( null, new object[] { instance } ) )
             {
-               var original = (MethodBase)type.GetMethod( "TargetMethod", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public ).Invoke( null, new object[] { instance } );
+               original = (MethodBase)type.GetMethod( "TargetMethod", flags ).Invoke( null, new object[] { instance } );
                if( original != null )
                {
-                  var overrider = type.GetMethod( "Override", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public );
-                  var callerProperty = type.GetProperty( "Caller", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public );
+                  var requireRuntimeHooker = (bool?)type.GetProperty( "RequireRuntimeHooker", flags )?.GetValue( null, null ) == true;
 
-                  if( overrider != null && callerProperty != null )
+                  var priority = type.GetCustomAttributes( typeof( HarmonyPriority ), false )
+                     .OfType<HarmonyPriority>()
+                     .FirstOrDefault()
+                     ?.info.prioritiy;
+
+                  var prefix = type.GetMethod( "Prefix", flags );
+                  var postfix = type.GetMethod( "Postfix", flags );
+                  var transpiler = type.GetMethod( "Transpiler", flags );
+
+                  var harmonyPrefix = prefix != null ? new HarmonyMethod( prefix ) : null;
+                  var harmonyPostfix = postfix != null ? new HarmonyMethod( postfix ) : null;
+                  var harmonyTranspiler = transpiler != null ? new HarmonyMethod( transpiler ) : null;
+
+                  if( priority.HasValue )
                   {
-                     if( Settings.EnableExperimentalHooks )
+                     if( harmonyPrefix != null )
                      {
-                        long replacementMethodLocation = MemoryHelper.GetMethodStartLocation( overrider );
-                        long originalMethodLocation = MemoryHelper.GetMethodStartLocation( original );
-                        byte[] originalCode = null;
+                        harmonyPrefix.prioritiy = priority.Value;
+                     }
 
-                        try
-                        {
-                           originalCode = MemoryHelper.GetInstructionsAtLocationRequiredToWriteJump( originalMethodLocation );
-                           var caller = new JumpedMethodCaller( originalMethodLocation, replacementMethodLocation, originalCode );
-                           callerProperty.SetValue( null, caller, null );
+                     if( harmonyPostfix != null )
+                     {
+                        harmonyPostfix.prioritiy = priority.Value;
+                     }
+                  }
 
-                           MemoryHelper.WriteJump( true, originalMethodLocation, replacementMethodLocation );
-                        }
-                        catch
-                        {
-                           if( originalCode != null )
-                           {
-                              MemoryHelper.RestoreInstructionsAtLocation( true, originalMethodLocation, originalCode );
-                           }
+                  if( requireRuntimeHooker || Settings.ForceExperimentalHooks )
+                  {
+                     if( Settings.EnableExperimentalHooks )
+                     {
+                        XuaLogger.Current.Info( $"Hooking '{original.DeclaringType.FullName}.{original.Name}' through experimental hooks." );
 
-                           throw;
-                        }
+                        var hookPrefix = harmonyPrefix != null ? new HookMethod( harmonyPrefix.method, harmonyPrefix.prioritiy ) : null;
+                        var hookPostfix = harmonyPostfix != null ? new HookMethod( harmonyPostfix.method, harmonyPostfix.prioritiy ) : null;
+
+                        RuntimeMethodPatcher.Patch( original, hookPrefix, hookPostfix );
+                     }
+                     else
+                     {
+                        XuaLogger.Current.Info( $"Skipping hook on '{original.DeclaringType.FullName}.{original.Name}' because it requires 'EnableExperimentalHooks' enabled is config file." );
                      }
                   }
                   else
                   {
-                     var priority = type.GetCustomAttributes( typeof( HarmonyPriority ), false )
-                        .OfType<HarmonyPriority>()
-                        .FirstOrDefault()
-                        ?.info.prioritiy;
-
-                     var prefix = type.GetMethod( "Prefix", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public );
-                     var postfix = type.GetMethod( "Postfix", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public );
-                     var transpiler = type.GetMethod( "Transpiler", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public );
-
-                     var harmonyPrefix = prefix != null ? new HarmonyMethod( prefix ) : null;
-                     var harmonyPostfix = postfix != null ? new HarmonyMethod( postfix ) : null;
-                     var harmonyTranspiler = transpiler != null ? new HarmonyMethod( transpiler ) : null;
-
-                     if( priority.HasValue )
+                     try
+                     {
+                        PatchMethod.Invoke( instance, new object[] { original, harmonyPrefix, harmonyPostfix, harmonyTranspiler } );
+                     }
+                     catch( Exception e ) when( e.IsCausedBy<PlatformNotSupportedException>() )
                      {
-                        if( harmonyPrefix != null )
+                        if( Settings.EnableExperimentalHooks )
                         {
-                           harmonyPrefix.prioritiy = priority.Value;
-                        }
+                           XuaLogger.Current.Info( $"Harmony is not supported in this runtime. Hooking '{original.DeclaringType.FullName}.{original.Name}' through experimental hooks." );
 
-                        if( harmonyPostfix != null )
+                           var hookPrefix = harmonyPrefix != null ? new HookMethod( harmonyPrefix.method, harmonyPrefix.prioritiy ) : null;
+                           var hookPostfix = harmonyPostfix != null ? new HookMethod( harmonyPostfix.method, harmonyPostfix.prioritiy ) : null;
+
+                           RuntimeMethodPatcher.Patch( original, hookPrefix, hookPostfix );
+                        }
+                        else
                         {
-                           harmonyPostfix.prioritiy = priority.Value;
+                           XuaLogger.Current.Error( $"Cannot hook '{original.DeclaringType.FullName}.{original.Name}'. Harmony is not supported in this runtime. You can try to 'EnableExperimentalHooks' in the config file to enable support for this game." );
                         }
                      }
-
-                     PatchMethod.Invoke( instance, new object[] { original, harmonyPrefix, harmonyPostfix, harmonyTranspiler } );
                   }
                }
                else
@@ -103,7 +114,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          }
          catch( Exception e )
          {
-            XuaLogger.Current.Warn( e, "An error occurred while patching a property/method on a class. Failing class: " + type.Name );
+            if( original != null )
+            {
+               XuaLogger.Current.Warn( e, $"An error occurred while patching property/method '{original.DeclaringType.FullName}.{original.Name}'." );
+            }
+            else
+            {
+               XuaLogger.Current.Warn( e, $"An error occurred while patching a property/method. Failing hook: '{type.Name}'." );
+            }
          }
       }
    }

+ 81 - 9
src/XUnity.AutoTranslator.Plugin.Core/Extensions/TextComponentExtensions.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;
+using System.Reflection;
 using System.Text;
 using UnityEngine;
 using UnityEngine.UI;
@@ -25,7 +26,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
             || ( Settings.EnableIMGUI && ui is GUIContent )
             || ( Settings.EnableNGUI && ClrTypes.UILabel != null && ClrTypes.UILabel.IsAssignableFrom( type ) )
             || ( Settings.EnableTextMeshPro && ClrTypes.TMP_Text != null && ClrTypes.TMP_Text.IsAssignableFrom( type ) )
-            || ( Settings.EnableUtage && ClrTypes.AdvCommand != null && ClrTypes.AdvCommand.IsAssignableFrom( type ) );
+            /*|| ( ClrTypes.AdvCommand != null && ClrTypes.AdvCommand.IsAssignableFrom( type ) )*/;
       }
 
       public static bool SupportsRichText( this object ui )
@@ -36,8 +37,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 
          return ( ui as Text )?.supportRichText == true
             || ( ClrTypes.TMP_Text != null && ClrTypes.TMP_Text.IsAssignableFrom( type ) && Equals( type.GetProperty( RichTextPropertyName )?.GetValue( ui, null ), true ) )
-            || ( ClrTypes.AdvCommand != null && ClrTypes.AdvCommand.IsAssignableFrom( type ) )
-            || ( ClrTypes.UguiNovelText != null && ClrTypes.UguiNovelText.IsAssignableFrom( type ) );
+            || ( ClrTypes.UguiNovelText != null && ClrTypes.UguiNovelText.IsAssignableFrom( type ) )
+            /*|| ( ClrTypes.AdvCommand != null && ClrTypes.AdvCommand.IsAssignableFrom( type ) )*/;
       }
 
       public static bool SupportsStabilization( this object ui )
@@ -66,14 +67,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          return ui is GUIContent;
       }
 
-      public static bool IsWhitelistedForImmediateRichTextTranslation( this object ui )
-      {
-         if( ui == null ) return false;
+      //public static bool IsWhitelistedForImmediateRichTextTranslation( this object ui )
+      //{
+      //   if( ui == null ) return false;
 
-         var type = ui.GetType();
+      //   var type = ui.GetType();
 
-         return ClrTypes.AdvCommand != null && ClrTypes.AdvCommand.IsAssignableFrom( type );
-      }
+      //   return ClrTypes.AdvCommand != null && ClrTypes.AdvCommand.IsAssignableFrom( type );
+      //}
 
       public static bool IsNGUI( this object ui )
       {
@@ -114,6 +115,77 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 
          var type = ui.GetType();
 
+         if( ClrTypes.UguiNovelText?.IsAssignableFrom( type ) == true )
+         {
+            var uguiMessageWindow = GameObject.FindObjectOfType( ClrTypes.AdvUguiMessageWindow );
+            if( uguiMessageWindow != null )
+            {
+               var flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
+
+               var uguiNovelText = uguiMessageWindow.GetType().GetProperty( "Text" ).GetValue( uguiMessageWindow, null );
+               if( Equals( uguiNovelText, ui ) )
+               {
+                  string previousNameText = null;
+                  var nameText = (Text)uguiMessageWindow.GetType().GetField( "nameText", flags ).GetValue( uguiMessageWindow );
+                  if( nameText )
+                  {
+                     previousNameText = nameText.text;
+                  }
+
+                  var engine = uguiMessageWindow.GetType().GetProperty( "Engine", flags ).GetValue( uguiMessageWindow, null );
+                  var page = engine.GetType().GetProperty( "Page", flags ).GetValue( engine, null );
+                  var textData = ClrTypes.TextData.GetConstructor( new[] { typeof( string ) } ).Invoke( new[] { text } );
+                  var length = (int)textData.GetType().GetProperty( "Length", flags ).GetValue( textData, null );
+
+                  var remakeTextData = page.GetType().GetMethod( "RemakeTextData", flags );
+                  if( remakeTextData == null )
+                  {
+                     try
+                     {
+                        Settings.InvokeEvents = false;
+
+                        page.GetType().GetProperty( "TextData", flags ).SetValue( page, textData, null );
+                        page.GetType().GetProperty( "CurrentTextLengthMax", flags ).GetSetMethod( true ).Invoke( page, new object[] { length } );
+                        page.GetType().GetProperty( "Status", flags ).GetSetMethod( true ).Invoke( page, new object[] { 1 /*SendChar*/ } );
+
+                        var messageWindowManager = engine.GetType().GetProperty( "MessageWindowManager", flags ).GetValue( engine, null );
+                        messageWindowManager.GetType().GetMethod( "OnPageTextChange", flags ).Invoke( messageWindowManager, new object[] { page } );
+                     }
+                     finally
+                     {
+                        Settings.InvokeEvents = true;
+                     }
+                  }
+                  else
+                  {
+                     try
+                     {
+                        Settings.InvokeEvents = false;
+                        Settings.RemakeTextData = advPage =>
+                        {
+                           advPage.GetType().GetProperty( "TextData", flags ).SetValue( page, textData, null );
+                           advPage.GetType().GetProperty( "CurrentTextLengthMax", flags ).GetSetMethod( true ).Invoke( page, new object[] { length } );
+                        };
+
+                        page.GetType().GetMethod( "RemakeText" ).Invoke( page, null );
+                     }
+                     finally
+                     {
+                        Settings.InvokeEvents = true;
+                        Settings.RemakeTextData = null;
+                     }
+                  }
+
+                  if( nameText )
+                  {
+                     nameText.text = previousNameText;
+                  }
+
+                  return;
+               }
+            }
+         }
+
          if( ui is Text )
          {
             ( (Text)ui ).text = text;

+ 3 - 6
src/XUnity.AutoTranslator.Plugin.Core/Hooks/HooksSetup.cs

@@ -29,7 +29,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       public static void InstallOverrideTextHooks()
       {
-         if( Settings.EnableUGUI || Settings.EnableUtage )
+         if( Settings.EnableUGUI )
          {
             UGUIHooks.HooksOverriden = SetupHook( KnownEvents.OnUnableToTranslateUGUI, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
          }
@@ -82,7 +82,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
          try
          {
-            if( Settings.EnableUGUI || Settings.EnableUtage )
+            if( Settings.EnableUGUI )
             {
                _harmony.PatchAll( UGUIHooks.All );
             }
@@ -130,10 +130,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
          try
          {
-            if( Settings.EnableUtage )
-            {
-               _harmony.PatchAll( UtageHooks.All );
-            }
+            _harmony.PatchAll( UtageHooks.All );
          }
          catch( Exception e )
          {

+ 3 - 10
src/XUnity.AutoTranslator.Plugin.Core/Hooks/ImageHooks.cs

@@ -58,21 +58,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          return AccessTools.Property( ClrTypes.Sprite, "texture" )?.GetGetMethod();
       }
 
-      static object Override( object __instance )
+      static void Postfix( Sprite __instance )
       {
-         return Caller.Func( () => ActualOverride( __instance ) );
-      }
-
-      static object ActualOverride( object __instance )
-      {
-         var texture = ( (Sprite)__instance ).texture;
+         var texture = __instance.texture;
 
          AutoTranslationPlugin.Current.Hook_ImageChanged( texture, true );
-
-         return texture;
       }
 
-      static JumpedMethodCaller Caller { get; set; }
+      static bool RequireRuntimeHooker => true;
    }
 
    [Harmony]

+ 0 - 52
src/XUnity.AutoTranslator.Plugin.Core/Hooks/JumpedMethodCaller.cs

@@ -1,52 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Harmony.ILCopying;
-using XUnity.AutoTranslator.Plugin.Core.Extensions;
-using XUnity.AutoTranslator.Plugin.Core.Utilities;
-
-namespace XUnity.AutoTranslator.Plugin.Core.Hooks
-{
-   internal class JumpedMethodCaller
-   {
-      private readonly long _originalMethodLocation;
-      private readonly long _replacementMethodLocation;
-      private readonly byte[] _originalCode;
-
-      public JumpedMethodCaller( long originalMethodLocation, long replacementMethodLocation, byte[] originalCode )
-      {
-         _originalMethodLocation = originalMethodLocation;
-         _replacementMethodLocation = replacementMethodLocation;
-         _originalCode = originalCode;
-      }
-
-      public object Func( Func<object> call )
-      {
-         try
-         {
-            MemoryHelper.RestoreInstructionsAtLocation( false, _originalMethodLocation, _originalCode );
-
-            return call();
-         }
-         finally
-         {
-            MemoryHelper.WriteJump( false, _originalMethodLocation, _replacementMethodLocation );
-         }
-      }
-
-      public void Action( Action call )
-      {
-         try
-         {
-            MemoryHelper.RestoreInstructionsAtLocation( false, _originalMethodLocation, _originalCode );
-
-            call();
-         }
-         finally
-         {
-            MemoryHelper.WriteJump( false, _originalMethodLocation, _replacementMethodLocation );
-         }
-      }
-   }
-}

+ 100 - 13
src/XUnity.AutoTranslator.Plugin.Core/Hooks/UtageHooks.cs

@@ -4,6 +4,8 @@ using System.Linq;
 using System.Reflection;
 using System.Text;
 using Harmony;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Utilities;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Hooks
@@ -11,50 +13,135 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    internal static class UtageHooks
    {
       public static readonly Type[] All = new[] {
-         typeof( AdvCommand_ParseCellLocalizedText_Hook ),
+         //typeof( AdvCommand_ParseCellLocalizedText_Hook ),
          typeof( AdvEngine_JumpScenario_Hook ),
+         typeof( UnityEventBase_Invoke_Hook ),
+         typeof( UnityEventBase_PrepareInvoke_Hook ),
+         typeof( AdvPage_RemakeTextData_Hook ),
       };
    }
 
+   //[Harmony]
+   //internal static class AdvCommand_ParseCellLocalizedText_Hook
+   //{
+   //   static bool Prepare( HarmonyInstance instance )
+   //   {
+   //      return ClrTypes.AdvCommand != null && AdvPage_RemakeTextData_Hook.TargetMethod( instance ) == null;
+   //   }
+
+   //   static MethodBase TargetMethod( HarmonyInstance instance )
+   //   {
+   //      return AccessTools.Method( ClrTypes.AdvCommand, "ParseCellLocalizedText", new Type[] { } );
+   //   }
+
+   //   static void Postfix( object __instance, ref string __result )
+   //   {
+   //      var result = AutoTranslationPlugin.Current.Hook_TextChanged_WithResult( __instance, __result );
+   //      if( !string.IsNullOrEmpty( result ) )
+   //      {
+   //         __result = result;
+   //      }
+   //   }
+   //}
+
+   [Harmony]
+   internal static class AdvEngine_JumpScenario_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.AdvEngine != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Method( ClrTypes.AdvEngine, "JumpScenario", new Type[] { typeof( string ) } );
+      }
+
+      static void Prefix( ref string label )
+      {
+         UtageHelper.FixLabel( ref label );
+      }
+   }
+
+   [Harmony]
+   internal static class UnityEventBase_Invoke_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UnityEventBase != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Method( ClrTypes.UnityEventBase, "Invoke", new Type[] { typeof( object[] ) } );
+      }
+
+      static bool Prefix()
+      {
+         return Settings.InvokeEvents;
+      }
+   }
+
    [Harmony]
-   internal static class AdvCommand_ParseCellLocalizedText_Hook
+   internal static class UnityEventBase_PrepareInvoke_Hook
    {
+      private static MethodInfo Method;
+      private static object DefaultResult;
+
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.ClrTypes.AdvCommand != null;
+         try
+         {
+            Method = AccessTools.Method( ClrTypes.UnityEventBase, "PrepareInvoke" );
+            DefaultResult = Activator.CreateInstance( typeof( List<> ).MakeGenericType( ClrTypes.BaseInvokableCall ) );
+
+            return Method != null;
+         }
+         catch
+         {
+            
+         }
+
+         return false;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.ClrTypes.AdvCommand, "ParseCellLocalizedText", new Type[] { } );
+         return Method;
       }
 
-      static void Postfix( object __instance, ref string __result )
+      static void Postfix( ref object __result )
       {
-         var result = AutoTranslationPlugin.Current.Hook_TextChanged_WithResult( __instance, __result );
-         if( !string.IsNullOrEmpty( result ) )
+         if( !Settings.InvokeEvents )
          {
-            __result = result;
+            __result = DefaultResult;
          }
       }
    }
 
    [Harmony]
-   internal static class AdvEngine_JumpScenario_Hook
+   internal static class AdvPage_RemakeTextData_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.ClrTypes.AdvEngine != null;
+         return ClrTypes.AdvPage != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.ClrTypes.AdvEngine, "JumpScenario", new Type[] { typeof( string ) } );
+         return AccessTools.Method( ClrTypes.AdvPage, "RemakeTextData" );
       }
 
-      static void Prefix( ref string label )
+      static bool Prefix( object __instance )
       {
-         UtageHelper.FixLabel( ref label );
+         if( Settings.RemakeTextData != null )
+         {
+            Settings.RemakeTextData( __instance );
+
+            return false;
+         }
+
+         return true;
       }
    }
 }

+ 0 - 31
src/XUnity.AutoTranslator.Plugin.Core/Kernel32.cs

@@ -55,36 +55,5 @@ namespace XUnity.AutoTranslator.Plugin.Core
           int cbMultiByte,
           [Out, MarshalAs( UnmanagedType.LPArray )] char[] lpWideCharStr,
           int cchWideChar );
-
-      [DllImport( "kernel32.dll" )]
-      internal static extern bool VirtualProtect( IntPtr lpAddress, UIntPtr dwSize, Protection flNewProtect, out Protection lpflOldProtect );
-   }
-
-   /// <summary>A bit-field of flags for protections</summary>
-   [Flags]
-   internal enum Protection
-   {
-      /// <summary>No access</summary>
-      PAGE_NOACCESS = 0x01,
-      /// <summary>Read only</summary>
-      PAGE_READONLY = 0x02,
-      /// <summary>Read write</summary>
-      PAGE_READWRITE = 0x04,
-      /// <summary>Write copy</summary>
-      PAGE_WRITECOPY = 0x08,
-      /// <summary>No access</summary>
-      PAGE_EXECUTE = 0x10,
-      /// <summary>Execute read</summary>
-      PAGE_EXECUTE_READ = 0x20,
-      /// <summary>Execute read write</summary>
-      PAGE_EXECUTE_READWRITE = 0x40,
-      /// <summary>Execute write copy</summary>
-      PAGE_EXECUTE_WRITECOPY = 0x80,
-      /// <summary>guard</summary>
-      PAGE_GUARD = 0x100,
-      /// <summary>No cache</summary>
-      PAGE_NOCACHE = 0x200,
-      /// <summary>Write combine</summary>
-      PAGE_WRITECOMBINE = 0x400
    }
 }

+ 10 - 4
src/XUnity.AutoTranslator.Plugin.Core/Parsing/RichTextParser.cs

@@ -11,7 +11,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
       private static readonly char[] TagNameEnders = new char[] { '=', ' ' };
       private static readonly Regex TagRegex = new Regex( "<.*?>" );
       private static readonly HashSet<string> IgnoreTags = new HashSet<string> { "ruby", "group" };
-      private static readonly HashSet<string> KnownTags = new HashSet<string> { "b", "i", "size", "color", "ruby", "em", "sup", "sub", "dash", "space", "group", "u", "strike", "param", "format", "emoji", "speed", "sound" };
+      private static readonly HashSet<string> KnownTags = new HashSet<string> { "b", "i", "size", "color", "ruby", "em", "sup", "sub", "dash", "space", "group", "u", "strike", "param", "format", "emoji", "speed", "sound", "line-height" };
 
       public RichTextParser()
       {
@@ -32,6 +32,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
          var template = new StringBuilder( input.Length );
          var offset = 0;
          var arg = 'A';
+         var foundIgnoredTag = false;
 
          foreach( Match m in matches )
          {
@@ -57,7 +58,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
                for( int j = 0 ; j < endIdx ; j++ )
                {
                   var c = value[ j ];
-                  if( !( ( c >= '\u0041' && c <= '\u005a' ) || ( c >= '\u0061' && c <= '\u007a' ) ) )
+                  if( !( ( c >= '\u0041' && c <= '\u005a' ) || ( c >= '\u0061' && c <= '\u007a' ) || c == '-' || c == '_' ) )
                   {
                      allLatin = false;
                      break;
@@ -94,13 +95,17 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
                {
                   template.Append( m.Value );
                }
+               else
+               {
+                  foundIgnoredTag = true;
+               }
             }
          }
 
          // catch any remaining text
          if( offset < input.Length )
          {
-            var argument = "{{" + ( arg ) + "}}";
+            var argument = "{{" + ( arg++ ) + "}}";
             var text = input.Substring( offset, input.Length - offset );
             args.Add( argument, text );
             template.Append( argument );
@@ -130,7 +135,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
             templateString = templateString.Replace( fullKey, newKey );
          }
 
-         return new ParserResult( input, templateString, arg != 'A', true, args );
+         var success = foundIgnoredTag || ( arg != 'A' && templateString.Length > 5 );
+         return new ParserResult( input, templateString, success, true, args );
       }
    }
 }

+ 2 - 2
src/XUnity.AutoTranslator.Plugin.Core/Utilities/TextGetterCompatModeHelper.cs

@@ -20,11 +20,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Utilities
          if( !Settings.TextGetterCompatibilityMode ) return;
 
          var tti = instance.GetTextTranslationInfo();
-         if( tti.IsTranslated )
+         if( tti?.IsTranslated == true )
          {
             // 0. This method
             // 1. Postfix
-            // 2. Harmony-related method
+            // 2. Harmony-related trampoline method
             // 3. Original method
             var callingMethod = new StackFrame( 3 ).GetMethod();
 

+ 4 - 3
src/XUnity.AutoTranslator.Plugin.Core/XUnity.AutoTranslator.Plugin.Core.csproj

@@ -2,12 +2,12 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.0.2</Version>
+      <Version>3.1.0</Version>
+      <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    </PropertyGroup>
 
    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
-     <DocumentationFile>C:\Proj\Secret\XUnity.AutoTranslator\src\XUnity.AutoTranslator.Plugin.Core\bin\Release\net35\XUnity.AutoTranslator.Plugin.Core.xml</DocumentationFile>
-     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+     <DocumentationFile>bin\Release\net35\XUnity.AutoTranslator.Plugin.Core.xml</DocumentationFile>
    </PropertyGroup>
 
    <ItemGroup>
@@ -27,6 +27,7 @@
 
    <ItemGroup>
      <ProjectReference Include="..\XUnity.AutoTranslator.Plugin.ExtProtocol\XUnity.AutoTranslator.Plugin.ExtProtocol.csproj" />
+     <ProjectReference Include="..\XUnity.RuntimeHooker\XUnity.RuntimeHooker.csproj" />
    </ItemGroup>
 
    <ItemGroup>

+ 2 - 2
src/XUnity.AutoTranslator.Plugin.IPA/XUnity.AutoTranslator.Plugin.IPA.csproj

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.0.2</Version>
+      <Version>3.1.0</Version>
    </PropertyGroup>
 
    <ItemGroup>
@@ -28,7 +28,7 @@
       <ItemGroup>
          <VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^(.+?)(\.0+)$&quot;, &quot;$1&quot;))" />
       </ItemGroup>
-      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\IPA\Plugins\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)ExIni.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)0Harmony.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.ExtProtocol.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\IPA\Plugins\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\IPA\Plugins' -DestinationPath '$(SolutionDir)dist\IPA\XUnity.AutoTranslator-IPA-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
+      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\IPA\Plugins\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)ExIni.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)0Harmony.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.ExtProtocol.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.RuntimeHooker.Core.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.RuntimeHooker.dll&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\IPA\Plugins\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\IPA\Plugins\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\IPA\Plugins' -DestinationPath '$(SolutionDir)dist\IPA\XUnity.AutoTranslator-IPA-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
    </Target>
 
 </Project>

+ 2 - 2
src/XUnity.AutoTranslator.Plugin.UnityInjector/XUnity.AutoTranslator.Plugin.UnityInjector.csproj

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.0.2</Version>
+      <Version>3.1.0</Version>
    </PropertyGroup>
 
    <ItemGroup>
@@ -28,7 +28,7 @@
       <ItemGroup>
          <VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace(&quot;%(Targets.Version)&quot;, &quot;^(.+?)(\.0+)$&quot;, &quot;$1&quot;))" />
       </ItemGroup>
-      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\Config\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)0Harmony.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.ExtProtocol.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\UnityInjector\UnityInjector' -DestinationPath '$(SolutionDir)dist\UnityInjector\XUnity.AutoTranslator-UnityInjector-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
+      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   for %%f in (&quot;$(SolutionDir)dist\Translators\*&quot;) do XCOPY /Y /I &quot;%%f&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\Config\Translators\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)0Harmony.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.Core.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.AutoTranslator.Plugin.ExtProtocol.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.RuntimeHooker.Core.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)XUnity.RuntimeHooker.dll&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\&quot;&#xD;&#xA;   COPY /Y &quot;$(SolutionDir)README.md&quot; &quot;$(SolutionDir)dist\UnityInjector\UnityInjector\README (AutoTranslator).md&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\UnityInjector\UnityInjector' -DestinationPath '$(SolutionDir)dist\UnityInjector\XUnity.AutoTranslator-UnityInjector-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
    </Target>
 
 </Project>

+ 2 - 0
src/XUnity.AutoTranslator.Setup/Program.cs

@@ -61,6 +61,8 @@ namespace XUnity.AutoTranslator.Setup
             AddFile( Path.Combine( managedDir, "ReiPatcher.exe" ), Resources.ReiPatcher );
             AddFile( Path.Combine( managedDir, "XUnity.AutoTranslator.Plugin.Core.dll" ), Resources.XUnity_AutoTranslator_Plugin_Core, true );
             AddFile( Path.Combine( managedDir, "XUnity.AutoTranslator.Plugin.ExtProtocol.dll" ), Resources.XUnity_AutoTranslator_Plugin_ExtProtocol, true );
+            AddFile( Path.Combine( managedDir, "XUnity.RuntimeHooker.dll" ), Resources.XUnity_RuntimeHooker, true );
+            AddFile( Path.Combine( managedDir, "XUnity.RuntimeHooker.Core.dll" ), Resources.XUnity_RuntimeHooker_Core, true );
 
             // create an .ini file for each launcher, if it does not already exist
             var iniInfo = new FileInfo( Path.Combine( reiPath, Path.GetFileNameWithoutExtension( launcher.Executable.Name ) + ".ini" ) );

+ 20 - 0
src/XUnity.AutoTranslator.Setup/Properties/Resources.Designer.cs

@@ -260,6 +260,26 @@ namespace XUnity.AutoTranslator.Setup.Properties {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized resource of type System.Byte[].
+        /// </summary>
+        internal static byte[] XUnity_RuntimeHooker {
+            get {
+                object obj = ResourceManager.GetObject("XUnity_RuntimeHooker", resourceCulture);
+                return ((byte[])(obj));
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized resource of type System.Byte[].
+        /// </summary>
+        internal static byte[] XUnity_RuntimeHooker_Core {
+            get {
+                object obj = ResourceManager.GetObject("XUnity_RuntimeHooker_Core", resourceCulture);
+                return ((byte[])(obj));
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized resource of type System.Byte[].
         /// </summary>

+ 6 - 0
src/XUnity.AutoTranslator.Setup/Properties/Resources.resx

@@ -151,6 +151,12 @@
    <data name="XUnity_AutoTranslator_Plugin_ExtProtocol" type="System.Resources.ResXFileRef, System.Windows.Forms">
       <value>..\..\XUnity.AutoTranslator.Setup.Build\bin\net35\XUnity.AutoTranslator.Plugin.ExtProtocol.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
    </data>
+   <data name="XUnity_RuntimeHooker_Core" type="System.Resources.ResXFileRef, System.Windows.Forms">
+      <value>..\..\XUnity.AutoTranslator.Setup.Build\bin\net35\XUnity.RuntimeHooker.Core.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+   </data>
+   <data name="XUnity_RuntimeHooker" type="System.Resources.ResXFileRef, System.Windows.Forms">
+      <value>..\..\XUnity.AutoTranslator.Setup.Build\bin\net35\XUnity.RuntimeHooker.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+   </data>
    <data name="BaiduTranslate" type="System.Resources.ResXFileRef, System.Windows.Forms">
       <value>..\..\XUnity.AutoTranslator.Setup.Build\bin\net35\BaiduTranslate.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
    </data>

+ 1 - 1
src/XUnity.AutoTranslator.Setup/XUnity.AutoTranslator.Setup.csproj

@@ -4,7 +4,7 @@
       <OutputType>Exe</OutputType>
       <TargetFramework>net40</TargetFramework>
       <AssemblyName>SetupReiPatcherAndAutoTranslator</AssemblyName>
-      <Version>3.0.2</Version>
+      <Version>3.1.0</Version>
       <ApplicationIcon>icon.ico</ApplicationIcon>
       <Win32Resource />
    </PropertyGroup>

+ 169 - 0
src/XUnity.RuntimeHooker.Core/HookCallbackInvoker.cs

@@ -0,0 +1,169 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using XUnity.RuntimeHooker.Core;
+using XUnity.RuntimeHooker.Core.Utilities;
+
+namespace XUnity.RuntimeHooker.Core
+{
+   public class HookCallbackInvoker : IComparable<HookCallbackInvoker>
+   {
+      private readonly HookMethod _hookMethod;
+      private readonly MethodBase _method;
+      private readonly Func<object, object[], object> _fastInvoke;
+      private readonly object[] _boundParameters;
+      private readonly int[] _parameterIndices;
+      private readonly int _returnValueIndex;
+      private readonly int _instanceIndex;
+
+      public HookCallbackInvoker( MethodBase originalMethod, HookMethod hookMethod )
+      {
+         var method = hookMethod.Method;
+         _hookMethod = hookMethod;
+         _method = method;
+         _returnValueIndex = -1;
+         _instanceIndex = -1;
+
+         var originalParameterNames = originalMethod.GetParameters().Select( x => x.Name ).ToArray();
+         var callbackParameters = method.GetParameters();
+         _parameterIndices = new int[ callbackParameters.Length ];
+         _boundParameters = new object[ callbackParameters.Length ];
+
+         // create 'parameter binding map' and determine if function can be fast-invoked
+         bool hasRefOrOut = false;
+         for( int i = 0 ; i < callbackParameters.Length ; i++ )
+         {
+            var callbackParameter = callbackParameters[ i ];
+            if( callbackParameter.IsOut || callbackParameter.ParameterType.IsByRef )
+            {
+               hasRefOrOut = true;
+            }
+
+            var parameterName = callbackParameter.Name;
+            if( parameterName == "__instance" )
+            {
+               if( originalMethod.IsStatic )
+               {
+                  throw new InvalidOperationException( "Cannot get instance of static method." );
+               }
+
+               _instanceIndex = i;
+            }
+            else if( parameterName == "__result" )
+            {
+               _returnValueIndex = i;
+            }
+            else
+            {
+               // find the parameter name in the originalMethod
+               var index = Array.IndexOf( originalParameterNames, parameterName );
+               if( index > -1 )
+               {
+                  _parameterIndices[ i ] = index;
+               }
+            }
+         }
+
+         if( !hasRefOrOut && _method is MethodInfo methodInfo )
+         {
+            _fastInvoke = ExpressionHelper.CreateFastInvoke( methodInfo );
+         }
+      }
+
+      public bool PrefixInvoke( object[] parameters, object __instance )
+      {
+         var parameterIndices = _parameterIndices;
+         var boundParameters = _boundParameters;
+         var len = boundParameters.Length;
+
+         try
+         {
+            // bind parameters from original method call to hook call
+            for( int i = 0 ; i < len ; i++ )
+            {
+               if( i == _instanceIndex )
+               {
+                  boundParameters[ i ] = __instance;
+               }
+               else
+               {
+                  boundParameters[ i ] = parameters[ parameterIndices[ i ] ];
+               }
+            }
+
+            object retval;
+            if( _fastInvoke != null )
+            {
+               retval = _fastInvoke( null, boundParameters );
+            }
+            else
+            {
+               retval = _method.Invoke( null, boundParameters );
+
+               // rebind parameters back to original array to support ref types
+               for( int i = 0 ; i < len ; i++ )
+               {
+                  parameters[ parameterIndices[ i ] ] = boundParameters[ i ];
+               }
+            }
+
+            return retval == null || (bool)retval == true;
+         }
+         finally
+         {
+            for( int i = 0 ; i < len ; i++ )
+            {
+               boundParameters[ i ] = null;
+            }
+         }
+      }
+
+      public void PostfixInvoke( object[] parameters, object __instance, ref object __result )
+      {
+         var parameterIndices = _parameterIndices;
+         var boundParameters = _boundParameters;
+         var len = boundParameters.Length;
+
+         try
+         {
+            // bind parameters from original method call to hook call
+            for( int i = 0 ; i < len ; i++ )
+            {
+               if( i == _returnValueIndex )
+               {
+                  boundParameters[ i ] = __result;
+               }
+               else if( i == _instanceIndex )
+               {
+                  boundParameters[ i ] = __instance;
+               }
+               else
+               {
+                  boundParameters[ i ] = parameters[ parameterIndices[ i ] ];
+               }
+            }
+
+            object retval = _fastInvoke != null
+               ? _fastInvoke( null, boundParameters )
+               : _method.Invoke( null, boundParameters );
+
+            if( _returnValueIndex > -1 )
+            {
+               __result = boundParameters[ _returnValueIndex ];
+            }
+         }
+         finally
+         {
+            for( int i = 0 ; i < len ; i++ )
+            {
+               boundParameters[ i ] = null;
+            }
+         }
+      }
+
+      public int CompareTo( HookCallbackInvoker other )
+      {
+         return other._hookMethod.Priority.CompareTo( _hookMethod.Priority );
+      }
+   }
+}

+ 25 - 0
src/XUnity.RuntimeHooker.Core/HookMethod.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+
+namespace XUnity.RuntimeHooker.Core
+{
+   public class HookMethod
+   {
+      public HookMethod( MethodBase method )
+         : this( method, HookPriority.Normal )
+      {
+      }
+
+      public HookMethod( MethodBase method, int priority )
+      {
+         Method = method;
+         Priority = priority;
+      }
+
+      public MethodBase Method { get; set; }
+
+      public int Priority { get; set; }
+   }
+}

+ 15 - 0
src/XUnity.RuntimeHooker.Core/HookPriority.cs

@@ -0,0 +1,15 @@
+namespace XUnity.RuntimeHooker.Core
+{
+   public static class HookPriority
+   {
+      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;
+   }
+}

+ 21 - 0
src/XUnity.RuntimeHooker.Core/JumpedMethodInfo.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace XUnity.RuntimeHooker.Core
+{
+   public class JumpedMethodInfo
+   {
+      public readonly long OriginalMethodLocation;
+      public readonly long ReplacementMethodLocation;
+      public readonly byte[] OriginalCode;
+
+      public JumpedMethodInfo( long originalMethodLocation, long replacementMethodLocation, byte[] originalCode )
+      {
+         OriginalMethodLocation = originalMethodLocation;
+         ReplacementMethodLocation = replacementMethodLocation;
+         OriginalCode = originalCode;
+      }
+   }
+
+}

+ 18 - 0
src/XUnity.RuntimeHooker.Core/TrampolineData.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace XUnity.RuntimeHooker.Core
+{
+   public class TrampolineData
+   {
+      public JumpedMethodInfo JumpedMethodInfo;
+      public MethodBase Method;
+      public Func<object, object[], object> FastInvoke;
+      public object[] Parameters;
+      public List<HookCallbackInvoker> Postfixes;
+      public List<HookCallbackInvoker> Prefixes;
+   }
+}

+ 82 - 0
src/XUnity.RuntimeHooker.Core/TrampolineHandler.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using XUnity.RuntimeHooker.Core;
+using XUnity.RuntimeHooker.Core.Utilities;
+
+namespace XUnity.RuntimeHooker.Core
+{
+   public static class TrampolineHandler
+   {
+      private static object Null;
+
+      public static object Func( object instance, TrampolineData data )
+      {
+         var jumpedMethodInfo = data.JumpedMethodInfo;
+         var parameters = data.Parameters;
+         try
+         {
+            MemoryHelper.RestoreInstructionsAtLocation( false, jumpedMethodInfo.OriginalMethodLocation, jumpedMethodInfo.OriginalCode );
+
+            var prefixes = data.Prefixes;
+            var prefixesLen = prefixes.Count;
+            for( int i = 0 ; i < prefixesLen ; i++ )
+            {
+               if( !prefixes[ i ].PrefixInvoke( parameters, instance ) ) return null;
+            }
+
+            var fastInvoke = data.FastInvoke;
+            object result = fastInvoke != null
+               ? fastInvoke( instance, parameters )
+               : data.Method.Invoke( null, parameters );
+
+            var postfixes = data.Postfixes;
+            var postfixesLen = postfixes.Count;
+            for( int i = 0 ; i < postfixesLen ; i++ )
+            {
+               postfixes[ i ].PostfixInvoke( parameters, instance, ref result );
+            }
+
+            return result;
+         }
+         finally
+         {
+            MemoryHelper.WriteJump( false, jumpedMethodInfo.OriginalMethodLocation, jumpedMethodInfo.ReplacementMethodLocation );
+         }
+      }
+
+      public static void Action( object instance, TrampolineData data )
+      {
+         var jumpedMethodInfo = data.JumpedMethodInfo;
+         var parameters = data.Parameters;
+         try
+         {
+            MemoryHelper.RestoreInstructionsAtLocation( false, jumpedMethodInfo.OriginalMethodLocation, jumpedMethodInfo.OriginalCode );
+
+            var prefixes = data.Prefixes;
+            var prefixesLen = prefixes.Count;
+            for( int i = 0 ; i < prefixesLen ; i++ )
+            {
+               if( !prefixes[ i ].PrefixInvoke( parameters, instance ) ) return;
+            }
+
+            var fastInvoke = data.FastInvoke;
+            object result = fastInvoke != null
+               ? fastInvoke( instance, parameters )
+               : data.Method.Invoke( null, parameters );
+
+            var postfixes = data.Postfixes;
+            var postfixesLen = postfixes.Count;
+            for( int i = 0 ; i < postfixesLen ; i++ )
+            {
+               postfixes[ i ].PostfixInvoke( parameters, instance, ref Null );
+            }
+         }
+         finally
+         {
+            MemoryHelper.WriteJump( false, jumpedMethodInfo.OriginalMethodLocation, jumpedMethodInfo.ReplacementMethodLocation );
+         }
+      }
+   }
+}

+ 13 - 0
src/XUnity.RuntimeHooker.Core/Unmanaged/Kernel32.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace XUnity.RuntimeHooker.Unmanaged
+{
+   internal static class Kernel32
+   {
+      [DllImport( "kernel32.dll" )]
+      internal static extern bool VirtualProtect( IntPtr lpAddress, UIntPtr dwSize, Protection flNewProtect, out Protection lpflOldProtect );
+   }
+}

+ 34 - 0
src/XUnity.RuntimeHooker.Core/Unmanaged/Protection.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace XUnity.RuntimeHooker.Unmanaged
+{
+   /// <summary>A bit-field of flags for protections</summary>
+   [Flags]
+   internal enum Protection
+   {
+      /// <summary>No access</summary>
+      PAGE_NOACCESS = 0x01,
+      /// <summary>Read only</summary>
+      PAGE_READONLY = 0x02,
+      /// <summary>Read write</summary>
+      PAGE_READWRITE = 0x04,
+      /// <summary>Write copy</summary>
+      PAGE_WRITECOPY = 0x08,
+      /// <summary>No access</summary>
+      PAGE_EXECUTE = 0x10,
+      /// <summary>Execute read</summary>
+      PAGE_EXECUTE_READ = 0x20,
+      /// <summary>Execute read write</summary>
+      PAGE_EXECUTE_READWRITE = 0x40,
+      /// <summary>Execute write copy</summary>
+      PAGE_EXECUTE_WRITECOPY = 0x80,
+      /// <summary>guard</summary>
+      PAGE_GUARD = 0x100,
+      /// <summary>No cache</summary>
+      PAGE_NOCACHE = 0x200,
+      /// <summary>Write combine</summary>
+      PAGE_WRITECOMBINE = 0x400
+   }
+}

+ 81 - 0
src/XUnity.RuntimeHooker.Core/Utilities/ExpressionHelper.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+
+namespace XUnity.RuntimeHooker.Core.Utilities
+{
+   public static class ExpressionHelper
+   {
+      public static Func<object, object[], object> CreateFastInvoke( MethodInfo method )
+      {
+         var instanceParameterExpression = Expression.Parameter( typeof( object ), "instance" );
+         var argumentsParameterExpression = Expression.Parameter( typeof( object[] ), "args" );
+         
+         var index = 0;
+         var argumentExtractionExpressions =
+            method
+            .GetParameters()
+            .Select( parameter =>
+                Expression.Convert(
+                   Expression.ArrayIndex(
+                      argumentsParameterExpression,
+                      Expression.Constant( index++ )
+                   ),
+                   parameter.ParameterType
+                )
+            ).ToArray();
+
+         var callExpression = method.IsStatic
+            ? Expression.Call( method, argumentExtractionExpressions )
+            : Expression.Call(
+               Expression.Convert(
+                  instanceParameterExpression,
+                  method.DeclaringType
+               ),
+               method,
+               argumentExtractionExpressions
+            );
+
+         var isVoidReturn = method.ReturnType == typeof( void );
+
+         var finalExpression = isVoidReturn
+            ? (Expression)callExpression
+            : Expression.Convert( callExpression, typeof( object ) );
+
+         if( isVoidReturn )
+         {
+            var lambdaExpression = Expression.Lambda<Action<object, object[]>>(
+               finalExpression,
+               instanceParameterExpression,
+               argumentsParameterExpression
+            );
+
+            var compiledLambda = lambdaExpression.Compile();
+            return ConvertToFunc( compiledLambda );
+         }
+         else
+         {
+            var lambdaExpression = Expression.Lambda<Func<object, object[], object>>(
+               finalExpression,
+               instanceParameterExpression,
+               argumentsParameterExpression
+            );
+
+            var compiledLambda = lambdaExpression.Compile();
+            return compiledLambda;
+         }
+      }
+
+      private static Func<object, object[], object> ConvertToFunc( Action<object, object[]> action )
+      {
+         return ( instance, args ) =>
+         {
+            action( instance, args );
+            return null;
+         };
+      }
+   }
+}

+ 15 - 18
src/XUnity.AutoTranslator.Plugin.Core/Utilities/MemoryHelper.cs → src/XUnity.RuntimeHooker.Core/Utilities/MemoryHelper.cs

@@ -1,26 +1,24 @@
 using System;
 using System.Collections.Generic;
-using System.Linq;
 using System.Reflection;
 using System.Reflection.Emit;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Text;
-using Harmony.ILCopying;
-using XUnity.AutoTranslator.Plugin.Core.Constants;
+using XUnity.RuntimeHooker.Unmanaged;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Utilities
+namespace XUnity.RuntimeHooker.Core.Utilities
 {
-   internal static class MemoryHelper
+   public static class MemoryHelper
    {
       private static readonly HashSet<PlatformID> WindowsPlatformIDSet = new HashSet<PlatformID> { PlatformID.Win32NT, PlatformID.Win32S, PlatformID.Win32Windows, PlatformID.WinCE };
       private static readonly bool IsWindows = WindowsPlatformIDSet.Contains( Environment.OSVersion.Platform );
 
       private static readonly byte[] Prefix = new byte[] { 0xe9 };
-      private static readonly byte[] Instruction_Start_64bit = new byte[] { 0x48, 0xB8 };
-      private static readonly byte[] Instruction_End_64bit = new byte[] { 0xFF, 0xE0 };
-      private const byte Instruction_Start_32bit = 0x68;
-      private const byte Instruction_End_32bit = 0xc3;
+      private static readonly byte[] Instruction_1_64bit = new byte[] { 0x48, 0xB8 };
+      private static readonly byte[] Instruction_2_64bit = new byte[] { 0xFF, 0xE0 };
+      private const byte Instruction_1_32bit = 0x68;
+      private const byte Instruction_2_32bit = 0xc3;
       private const int MemoryRequiredForJumpInstruction_32bit = 6;
       private const int MemoryRequiredForJumpInstruction_64bit = 12;
 
@@ -39,7 +37,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Utilities
       }
 
 
-      public static string WriteJump( bool unprotect, long memory, long destination )
+      public static void WriteJump( bool unprotect, long memory, long destination )
       {
          if( unprotect )
          {
@@ -54,17 +52,16 @@ namespace XUnity.AutoTranslator.Plugin.Core.Utilities
                memory += 5 + offset;
             }
 
-            memory = WriteBytes( memory, Instruction_Start_64bit );
+            memory = WriteBytes( memory, Instruction_1_64bit );
             memory = WriteLong( memory, destination );
-            memory = WriteBytes( memory, Instruction_End_64bit );
+            memory = WriteBytes( memory, Instruction_2_64bit );
          }
          else
          {
-            memory = WriteByte( memory, Instruction_Start_32bit );
+            memory = WriteByte( memory, Instruction_1_32bit );
             memory = WriteInt( memory, (int)destination );
-            memory = WriteByte( memory, Instruction_End_32bit );
+            memory = WriteByte( memory, Instruction_2_32bit );
          }
-         return null;
       }
 
       public static byte[] GetInstructionsAtLocationRequiredToWriteJump( long location )
@@ -103,7 +100,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Utilities
          {
             UnprotectMemoryPage( location );
          }
-
+         
          var flag = IntPtr.Size == sizeof( long );
          if( flag )
          {
@@ -113,11 +110,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Utilities
                location += 5 + num;
             }
          }
-
+         
          foreach( var b in array )
          {
             location = WriteByte( location, b );
-         }            
+         }
       }
 
       public static unsafe bool CompareBytes( long memory, byte[] values )

+ 8 - 0
src/XUnity.RuntimeHooker.Core/XUnity.RuntimeHooker.Core.csproj

@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+   <PropertyGroup>
+      <TargetFramework>net35</TargetFramework>
+      <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+   </PropertyGroup>
+
+</Project>

+ 142 - 0
src/XUnity.RuntimeHooker.Trampolines/TrampolineInitializer.cs

@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using XUnity.RuntimeHooker.Core;
+using XUnity.RuntimeHooker.Core.Utilities;
+
+namespace XUnity.RuntimeHooker.Trampolines
+{
+   internal static class TrampolineInitializer
+   {
+      public static readonly object Sync = new object();
+      public static TrampolineData Data;
+
+      public static void SetupHook( MethodBase originalMethod )
+      {
+         // just skip if already initialized
+         if( Data != null ) return;
+
+         // determine if we have a supported return type
+         var returnType = ( originalMethod as MethodInfo )?.ReturnType;
+         if( !( returnType == typeof( void ) || !returnType.IsValueType ) )
+         {
+            throw new InvalidOperationException( "Current implementation only supports hooking method that returns reference types or void." );
+         }
+
+         // obtain parameters to method, and check they are not generic
+         var parameters = originalMethod.GetParameters();
+         if( originalMethod.IsGenericMethod || originalMethod.IsGenericMethodDefinition )
+         {
+            throw new InvalidOperationException( "Current implementation does not support generic methods." );
+         }
+
+         // check the are not ref or out parameters
+         foreach( var parameter in parameters )
+         {
+            if( parameter.ParameterType.IsByRef || parameter.IsOut )
+            {
+               throw new InvalidOperationException( "Current implementation does not support out or ref parameters." );
+            }
+         }
+
+         // determine which type to use as a trampoline
+         string type = null;
+         bool hasReturn = returnType != typeof( void );
+         bool isStatic = originalMethod.IsStatic;
+         if( hasReturn && isStatic )
+         {
+            type = "XUnity.RuntimeHooker.Trampolines.StaticTrampolineWithReturn";
+         }
+         else if( !hasReturn && isStatic )
+         {
+            type = "XUnity.RuntimeHooker.Trampolines.StaticTrampoline";
+         }
+         else if( hasReturn && !isStatic )
+         {
+            type = "XUnity.RuntimeHooker.Trampolines.InstanceTrampolineWithReturn";
+         }
+         else if( !hasReturn && !isStatic )
+         {
+            type = "XUnity.RuntimeHooker.Trampolines.InstanceTrampoline";
+         }
+
+         // determine if and how many generic arguments the trampoline type has
+         var genericArguments = new List<Type>();
+         if( !originalMethod.IsStatic )
+         {
+            genericArguments.Add( originalMethod.DeclaringType );
+         }
+         genericArguments.AddRange( parameters.Select( x => x.ParameterType ) );
+         if( genericArguments.Count > 0 )
+         {
+            // and apply those generic arguments to the type name so it can be found
+            type += "`" + genericArguments.Count;
+         }
+
+         // find the trampoline type
+         var trampolineType = typeof( TrampolineInitializer ).Assembly.GetType( type );
+         if( trampolineType == null )
+         {
+            throw new InvalidOperationException( "Current implementation only supports methods with up to 5 arguments (including the instance)." );
+         }
+
+         // genericify the trampoline type
+         if( genericArguments.Count > 0 )
+         {
+            trampolineType = trampolineType.MakeGenericType( genericArguments.ToArray() );
+         }
+
+         // obtain the method that we will jump to when the original method is called
+         var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
+         var overrideMethod = trampolineType.GetMethod( "Override", flags );
+
+         // get the location of the override method
+         long replacementMethodLocation = MemoryHelper.GetMethodStartLocation( overrideMethod );
+
+         // get the location and initial code of the original method
+         long originalMethodLocation = MemoryHelper.GetMethodStartLocation( originalMethod );
+         var originalCode = MemoryHelper.GetInstructionsAtLocationRequiredToWriteJump( originalMethodLocation );
+
+         // create an (optional) delegate that enables invoking the function MUCH faster than through reflection
+         Func<object, object[], object> fastInvoke = null;
+         if( originalMethod is MethodInfo methodInfo )
+         {
+            fastInvoke = ExpressionHelper.CreateFastInvoke( methodInfo );
+         }
+
+         // store all data required by the trampoline function
+         Data = new TrampolineData
+         {
+            Method = originalMethod,
+            FastInvoke = fastInvoke,
+            JumpedMethodInfo = new JumpedMethodInfo( originalMethodLocation, replacementMethodLocation, originalCode ),
+            Parameters = new object[ parameters.Length ],
+            Postfixes = new List<HookCallbackInvoker>(),
+            Prefixes = new List<HookCallbackInvoker>()
+         };
+
+         // create a JMP instruction at the start of the original method so we jump to the override method
+         // (create the hook)
+         MemoryHelper.WriteJump( true, originalMethodLocation, replacementMethodLocation );
+      }
+
+      public static void AddPrefix( HookMethod prefix )
+      {
+         if( Data == null ) throw new InvalidOperationException( "Please call SetupHook before adding prefixes." );
+
+         var invoker = new HookCallbackInvoker( Data.Method, prefix );
+         Data.Prefixes.Add( invoker );
+         Data.Prefixes.Sort();
+      }
+
+      public static void AddPostfix( HookMethod postfix )
+      {
+         if( Data == null ) throw new InvalidOperationException( "Please call SetupHook before adding postfixes." );
+
+         var invoker = new HookCallbackInvoker( Data.Method, postfix );
+         Data.Postfixes.Add( invoker );
+         Data.Postfixes.Sort();
+      }
+   }
+}

+ 555 - 0
src/XUnity.RuntimeHooker.Trampolines/Trampolines.cs

@@ -0,0 +1,555 @@
+using System;
+using System.Text;
+using System.Threading;
+using XUnity.RuntimeHooker.Core;
+
+namespace XUnity.RuntimeHooker.Trampolines
+{
+   #region InstanceTrampolineWithReturn
+
+   internal static class InstanceTrampolineWithReturn<T1>
+   {
+      static object Override( T1 t1 )
+      {
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            object result = TrampolineHandler.Func( t1, TrampolineInitializer.Data );
+
+            return result;
+         }
+         finally
+         {
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class InstanceTrampolineWithReturn<T1, T2>
+   {
+      static object Override( T1 t1, T2 t2 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t2;
+            object result = TrampolineHandler.Func( t1, data );
+
+            return result;
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class InstanceTrampolineWithReturn<T1, T2, T3>
+   {
+      static object Override( T1 t1, T2 t2, T3 t3 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t2;
+            data.Parameters[ 1 ] = t3;
+            object result = TrampolineHandler.Func( t1, data );
+
+            return result;
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class InstanceTrampolineWithReturn<T1, T2, T3, T4>
+   {
+      static object Override( T1 t1, T2 t2, T3 t3, T4 t4 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t2;
+            data.Parameters[ 1 ] = t3;
+            data.Parameters[ 2 ] = t4;
+            object result = TrampolineHandler.Func( t1, data );
+
+            return result;
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+            data.Parameters[ 2 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class InstanceTrampolineWithReturn<T1, T2, T3, T4, T5>
+   {
+      static object Override( T1 t1, T2 t2, T3 t3, T4 t4, T5 t5 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t2;
+            data.Parameters[ 1 ] = t3;
+            data.Parameters[ 2 ] = t4;
+            data.Parameters[ 3 ] = t5;
+            object result = TrampolineHandler.Func( t1, data );
+
+            return result;
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+            data.Parameters[ 2 ] = null;
+            data.Parameters[ 3 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   #endregion
+
+   #region InstanceTrampoline
+
+   internal static class InstanceTrampoline<T1>
+   {
+      static void Override( T1 t1 )
+      {
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            TrampolineHandler.Action( t1, TrampolineInitializer.Data );
+         }
+         finally
+         {
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class InstanceTrampoline<T1, T2>
+   {
+      static void Override( T1 t1, T2 t2 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t2;
+            TrampolineHandler.Action( t1, data );
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class InstanceTrampoline<T1, T2, T3>
+   {
+      static void Override( T1 t1, T2 t2, T3 t3 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t2;
+            data.Parameters[ 1 ] = t3;
+            TrampolineHandler.Action( t1, data );
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class InstanceTrampoline<T1, T2, T3, T4>
+   {
+      static void Override( T1 t1, T2 t2, T3 t3, T4 t4 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t2;
+            data.Parameters[ 1 ] = t3;
+            data.Parameters[ 2 ] = t4;
+            TrampolineHandler.Action( t1, data );
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+            data.Parameters[ 2 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class InstanceTrampoline<T1, T2, T3, T4, T5>
+   {
+      static void Override( T1 t1, T2 t2, T3 t3, T4 t4, T5 t5 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t2;
+            data.Parameters[ 1 ] = t3;
+            data.Parameters[ 2 ] = t4;
+            data.Parameters[ 3 ] = t5;
+            TrampolineHandler.Action( t1, data );
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+            data.Parameters[ 2 ] = null;
+            data.Parameters[ 3 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   #endregion
+
+   #region StaticTrampolineWithReturn
+
+   internal static class StaticTrampolineWithReturn
+   {
+      static object Override()
+      {
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            object result = TrampolineHandler.Func( null, TrampolineInitializer.Data );
+
+            return result;
+         }
+         finally
+         {
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class StaticTrampolineWithReturn<T1>
+   {
+      static object Override( T1 t1 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t1;
+            object result = TrampolineHandler.Func( null, data );
+
+            return result;
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class StaticTrampolineWithReturn<T1, T2>
+   {
+      static object Override( T1 t1, T2 t2 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t1;
+            data.Parameters[ 1 ] = t2;
+            object result = TrampolineHandler.Func( null, data );
+
+            return result;
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class StaticTrampolineWithReturn<T1, T2, T3>
+   {
+      static object Override( T1 t1, T2 t2, T3 t3 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t1;
+            data.Parameters[ 1 ] = t2;
+            data.Parameters[ 2 ] = t3;
+            object result = TrampolineHandler.Func( null, data );
+
+            return result;
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+            data.Parameters[ 2 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class StaticTrampolineWithReturn<T1, T2, T3, T4>
+   {
+      static object Override( T1 t1, T2 t2, T3 t3, T4 t4 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t1;
+            data.Parameters[ 1 ] = t2;
+            data.Parameters[ 2 ] = t3;
+            data.Parameters[ 3 ] = t4;
+            object result = TrampolineHandler.Func( null, data );
+
+            return result;
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+            data.Parameters[ 2 ] = null;
+            data.Parameters[ 3 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class StaticTrampolineWithReturn<T1, T2, T3, T4, T5>
+   {
+      static object Override( T1 t1, T2 t2, T3 t3, T4 t4, T5 t5 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t1;
+            data.Parameters[ 1 ] = t2;
+            data.Parameters[ 2 ] = t3;
+            data.Parameters[ 3 ] = t4;
+            data.Parameters[ 4 ] = t5;
+            object result = TrampolineHandler.Func( null, data );
+
+            return result;
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+            data.Parameters[ 2 ] = null;
+            data.Parameters[ 3 ] = null;
+            data.Parameters[ 4 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   #endregion
+
+   #region StaticTrampoline
+
+   internal static class StaticTrampoline
+   {
+      static void Override()
+      {
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            TrampolineHandler.Action( null, TrampolineInitializer.Data );
+         }
+         finally
+         {
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class StaticTrampoline<T1>
+   {
+      static void Override( T1 t1 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t1;
+            TrampolineHandler.Action( null, data );
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class StaticTrampoline<T1, T2>
+   {
+      static void Override( T1 t1, T2 t2 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t1;
+            data.Parameters[ 1 ] = t2;
+            TrampolineHandler.Action( null, data );
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class StaticTrampoline<T1, T2, T3>
+   {
+      static void Override( T1 t1, T2 t2, T3 t3 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t1;
+            data.Parameters[ 1 ] = t2;
+            data.Parameters[ 2 ] = t3;
+            TrampolineHandler.Action( null, data );
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+            data.Parameters[ 2 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class StaticTrampoline<T1, T2, T3, T4>
+   {
+      static void Override( T1 t1, T2 t2, T3 t3, T4 t4 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t1;
+            data.Parameters[ 1 ] = t2;
+            data.Parameters[ 2 ] = t3;
+            data.Parameters[ 3 ] = t4;
+            TrampolineHandler.Action( null, data );
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+            data.Parameters[ 2 ] = null;
+            data.Parameters[ 3 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   internal static class StaticTrampoline<T1, T2, T3, T4, T5>
+   {
+      static void Override( T1 t1, T2 t2, T3 t3, T4 t4, T5 t5 )
+      {
+         var data = TrampolineInitializer.Data;
+         try
+         {
+            Monitor.Enter( TrampolineInitializer.Sync );
+
+            data.Parameters[ 0 ] = t1;
+            data.Parameters[ 1 ] = t2;
+            data.Parameters[ 2 ] = t3;
+            data.Parameters[ 3 ] = t4;
+            data.Parameters[ 4 ] = t5;
+            TrampolineHandler.Action( null, data );
+         }
+         finally
+         {
+            data.Parameters[ 0 ] = null;
+            data.Parameters[ 1 ] = null;
+            data.Parameters[ 2 ] = null;
+            data.Parameters[ 3 ] = null;
+            data.Parameters[ 4 ] = null;
+
+            Monitor.Exit( TrampolineInitializer.Sync );
+         }
+      }
+   }
+
+   #endregion
+}

+ 12 - 0
src/XUnity.RuntimeHooker.Trampolines/XUnity.RuntimeHooker.Trampolines.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net35</TargetFramework>
+     <AssemblyName>b979b301af8b4ef48f224de6dcf2ddf6</AssemblyName>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\XUnity.RuntimeHooker.Core\XUnity.RuntimeHooker.Core.csproj" />
+  </ItemGroup>
+
+</Project>

+ 73 - 0
src/XUnity.RuntimeHooker/Properties/Resources.Designer.cs

@@ -0,0 +1,73 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace XUnity.RuntimeHooker.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("XUnity.RuntimeHooker.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized resource of type System.Byte[].
+        /// </summary>
+        internal static byte[] b979b301af8b4ef48f224de6dcf2ddf6 {
+            get {
+                object obj = ResourceManager.GetObject("b979b301af8b4ef48f224de6dcf2ddf6", resourceCulture);
+                return ((byte[])(obj));
+            }
+        }
+    }
+}

+ 124 - 0
src/XUnity.RuntimeHooker/Properties/Resources.resx

@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
+  <data name="b979b301af8b4ef48f224de6dcf2ddf6" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\Resources\b979b301af8b4ef48f224de6dcf2ddf6.dll;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
+</root>

+ 147 - 0
src/XUnity.RuntimeHooker/RuntimeMethodPatcher.cs

@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using XUnity.RuntimeHooker.Core;
+using XUnity.RuntimeHooker.Properties;
+
+namespace XUnity.RuntimeHooker
+{
+   public static class RuntimeMethodPatcher
+   {
+      private static readonly object Sync = new object();
+      private static readonly Dictionary<MethodBase, Type> HookedMethodToTrampolineInitializer = new Dictionary<MethodBase, Type>();
+
+      public static void Patch( MethodBase originalMethod, HookMethod prefix, HookMethod postfix )
+      {
+         // WARNING: This code is in no way guaranteed to work, as it makes some wild assumptions
+         //          about how the underlying machine code is being generated during JIT
+
+         // If the code does not work it will likely crash the application immediately with a cryptic or no error message.
+
+         // This hooking implementation works like this:
+         // 1. Create a JMP at the start of the hooked method that goes to a method of an identical signature (trampoline function)
+         // 2. When the function is entered, rewrite back the old code that was overwritten in step 1
+         // 3. Call prefixes, then call original method, then call postfixes
+         // 4. Write back in the JMP that was created in step 1
+
+         // This is all very simple. The primary issue this library solves is how to do this through a 'nice'
+         // API surface, similar to that of Harmony so that it can be used as a fallback mechanism.
+
+         // The difficult issue is how to navigate to a method of identical signature to the original one
+         // if you cannot emit any IL code (netstandard2.0 limitation) because it is difficult to
+         // generate a method with the required signature at compile time, that includes types that may
+         // be inaccessible.
+
+         // This library pre-defines a set of generic classes that each has an 'Override' method, that will
+         // be used as the trampoline function. Each version of the 'Override' method makes different assumptions
+         // about the method signature and uses generic parameters defined on the class in order to do so.
+
+         // The weakness of this approach is that any arguments (generated by the JITTER) in relation to generics
+         // will *not* be passed to the override method, as it would usually expect. (I think that with the legacy
+         // .NET JITTER in 64-bit, these are often passed as an id in the RCX register of the CPU, but this is likely
+         // different for Mono and other architectures). This means that if any code is generated in the Override
+         // method that requires this information that the application will immediately fail.
+
+         // This has an especially big impact on the JITTER used in Mono, where accessing any generic arguments
+         // in the override method will immediately cause application failure. This imposes a big limitation
+         // on this approach in that the current implementation is only able to hook methods that return reference
+         // types (or void) because it is unable to use a generic argument (TReturn) to determine the size of the variable
+         // to send back to the caller (Should it use the stack? Should it use a CPU register? It does not know!).
+
+         // Another limitation of this in Mono is that you cannot use this approach to access static fields on
+         // a per-generic class basis (as that would mean using the generic arguments). To overcome this issue
+         // a new assembly that maintains state for each hooked method is generated at runtime (by renaming an
+         // existing assembly and loading it into memory). This is essentially what this function does.
+
+         // A better approach than this would be to write the trampoline function in machine code. But that
+         // would take a whole lot more effort than this. And require knowledge about how each architecture
+         // and platform emits machine code.
+
+         // Attempting to create a StackTrace will also break this implementation.
+
+         lock( Sync )
+         {
+            var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
+
+            // check if we have already hooked the specified method
+            if( !HookedMethodToTrampolineInitializer.TryGetValue( originalMethod, out var trampolineInitializer ) )
+            {
+               // obtain data for the assembly that contains the hooking logic/trampolines
+               var assemblySize = Resources.b979b301af8b4ef48f224de6dcf2ddf6.Length;
+
+               // copy the data into a new array
+               var assemblyData = new byte[ assemblySize ];
+               Buffer.BlockCopy( Resources.b979b301af8b4ef48f224de6dcf2ddf6, 0, assemblyData, 0, assemblySize );
+
+               // rename the assembly, so it can be loaded multiple times (in Mono)
+               var newName = Guid.NewGuid().ToString( "N" );
+               FindAndReplace( "b979b301af8b4ef48f224de6dcf2ddf6", newName, assemblyData );
+               var assembly = Assembly.Load( assemblyData );
+
+               // obtain the trampoline initializer type from the dynamically named assembly
+               trampolineInitializer = assembly.GetType( "XUnity.RuntimeHooker.Trampolines.TrampolineInitializer" );
+
+               // call the setup hook method with the originalMethod to hook
+               trampolineInitializer.GetMethod( "SetupHook", flags ).Invoke( null, new object[] { originalMethod } );
+
+               // add tbe method to our hooked methods dictionary, so we do not hook it again
+               HookedMethodToTrampolineInitializer.Add( originalMethod, trampolineInitializer );
+            }
+
+            // after hooking the method, lets apply our prefixes/postfixes
+            if( prefix != null )
+            {
+               trampolineInitializer.GetMethod( "AddPrefix", flags ).Invoke( null, new object[] { prefix } );
+            }
+
+            if( postfix != null )
+            {
+               trampolineInitializer.GetMethod( "AddPostfix", flags ).Invoke( null, new object[] { postfix } );
+            }
+         }
+      }
+
+      private static void FindAndReplace( string ansiStringToSearchFor, string ansiStringToReplaceWith, byte[] data )
+      {
+         if( ansiStringToReplaceWith.Length != ansiStringToSearchFor.Length )
+            throw new ArgumentException( "The string to search for must be same size as string to replace." );
+
+         int pos = 0;
+         char c;
+         byte b;
+         int startIdx = 0;
+         int searchLen = ansiStringToReplaceWith.Length;
+
+         for( int i = 0 ; i < data.Length ; i++ )
+         {
+            b = data[ i ];
+            c = (char)b;
+
+            if( pos == searchLen )
+            {
+               for( int j = 0 ; j < searchLen ; j++ )
+               {
+                  var r = ansiStringToReplaceWith[ j ];
+                  data[ j + startIdx ] = (byte)r;
+               }
+
+               pos = 0;
+            }
+            else if( c == ansiStringToSearchFor[ pos ] )
+            {
+               if( pos == 0 )
+               {
+                  startIdx = i;
+               }
+
+               pos++;
+            }
+            else
+            {
+               pos = 0;
+            }
+         }
+      }
+   }
+}

+ 30 - 0
src/XUnity.RuntimeHooker/XUnity.RuntimeHooker.csproj

@@ -0,0 +1,30 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net35</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\XUnity.RuntimeHooker.Core\XUnity.RuntimeHooker.Core.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Update="Properties\Resources.Designer.cs">
+      <DesignTime>True</DesignTime>
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+  </ItemGroup>
+
+  <ItemGroup>
+    <EmbeddedResource Update="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+  </ItemGroup>
+
+  <Target Name="PreBuild" BeforeTargets="PreBuildEvent">
+    <Exec Command="XCOPY /Y /I &quot;$(SolutionDir)src\XUnity.RuntimeHooker.Trampolines\$(OutDir)b979b301af8b4ef48f224de6dcf2ddf6.dll&quot; &quot;$(ProjectDir)Resources\&quot;" />
+  </Target>
+
+</Project>

+ 53 - 0
test/XUnity.RuntimeHooker.Benchmark/Program.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using XUnity.RuntimeHooker.Core;
+
+namespace XUnity.RuntimeHooker.Benchmark
+{
+   class Program
+   {
+      static void Main( string[] args )
+      {
+         int a = 12;
+         int b = 23;
+
+         MyMethod( a, b );
+
+         var myMethod = typeof( Program ).GetMethod( "MyMethod" );
+         var myHook = typeof( Program ).GetMethod( "MyHook" );
+
+         int iterations = 10000;
+         var sw = Stopwatch.StartNew();
+
+         for( int i = 0 ; i < iterations ; i++ )
+         {
+            MyMethod( a, b );
+         }
+
+         Console.WriteLine( sw.ElapsedMilliseconds );
+
+         RuntimeMethodPatcher.Patch( myMethod, new HookMethod( myHook ), null );
+         MyMethod( a, b );
+
+         sw.Reset();
+         sw.Start();
+
+         for( int i = 0 ; i < iterations ; i++ )
+         {
+            MyMethod( a, b );
+         }
+
+         Console.WriteLine( sw.ElapsedMilliseconds );
+      }
+
+      [MethodImpl( MethodImplOptions.NoInlining )]
+      public static void MyMethod( int a, int b )
+      {
+      }
+
+      public static void MyHook( int b, int a )
+      {
+      }
+   }
+}

+ 12 - 0
test/XUnity.RuntimeHooker.Benchmark/XUnity.RuntimeHooker.Benchmark.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net35</TargetFramework>
+  </PropertyGroup>
+
+   <ItemGroup>
+      <ProjectReference Include="..\..\src\XUnity.RuntimeHooker\XUnity.RuntimeHooker.csproj" />
+   </ItemGroup>
+
+</Project>

+ 72 - 0
test/XUnity.RuntimeHooker.ConsoleTests/Program.cs

@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Text;
+using XUnity.RuntimeHooker.Core;
+
+namespace XUnity.RuntimeHooker.ConsoleTests
+{
+   class Program
+   {
+      static void Main( string[] args )
+      {
+         var sayHello = typeof( Program ).GetMethod( "SayHello" );
+         var sayGoodbye = typeof( Program ).GetMethod( "SayGoodbye" );
+         RuntimeMethodPatcher.Patch( sayHello, new HookMethod( sayGoodbye ), null );
+         SayHello();
+
+         var textComponent = new TextComponent();
+         textComponent.Text = "こんにちは";
+
+         var textGetter = typeof( TextComponent ).GetProperty( "Text" ).GetGetMethod();
+         var textGetterHook = typeof( Program ).GetMethod( "TextGetterHook" );
+         RuntimeMethodPatcher.Patch( textGetter, null, new HookMethod( textGetterHook ) );
+
+         Console.WriteLine( textComponent.Text );
+
+         var printValue = typeof( Program ).GetMethod( "PrintValue" );
+         var printValueHook = typeof( Program ).GetMethod( "PrintValueHook" );
+         RuntimeMethodPatcher.Patch( printValue, new HookMethod( printValueHook ), null );
+         PrintValue( 7331 );
+      }
+
+      [MethodImpl( MethodImplOptions.NoInlining )]
+      public static void SayHello()
+      {
+         Console.WriteLine( "Hello" );
+      }
+
+      public static bool SayGoodbye()
+      {
+         Console.WriteLine( "Goodbye" );
+
+         return false;
+      }
+
+      public static void TextGetterHook( ref string __result, TextComponent __instance )
+      {
+         Console.WriteLine( "Instance: " + __instance );
+
+         __result = "Hello there!";
+      }
+
+      [MethodImpl( MethodImplOptions.NoInlining )]
+      public static void PrintValue( int i )
+      {
+         Console.WriteLine( i );
+      }
+
+      public static void PrintValueHook( ref int i )
+      {
+         i = 1337;
+      }
+   }
+
+   public class TextComponent
+   {
+      public string Text { [MethodImpl( MethodImplOptions.NoInlining )]get; set; }
+   }
+}

+ 13 - 0
test/XUnity.RuntimeHooker.ConsoleTests/XUnity.RuntimeHooker.ConsoleTests.csproj

@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+   <PropertyGroup>
+      <OutputType>Exe</OutputType>
+      <TargetFramework>net35</TargetFramework>
+      <Platforms>AnyCPU;x86</Platforms>
+   </PropertyGroup>
+
+   <ItemGroup>
+     <ProjectReference Include="..\..\src\XUnity.RuntimeHooker\XUnity.RuntimeHooker.csproj" />
+   </ItemGroup>
+
+</Project>