Переглянути джерело

Version 3.0.1

 * BUG FIX - Fixed bug that could in certain situation cause IMGUI translation to drain on performance
 * BUG FIX - Never close a service point while a request is ongoing. Previously this could cause the plugin to lockup
 * BUG FIX - Only disable certificate checks if the .NET version is at or below 3.5
 * BUG FIX - Improved cleanup of object references
 * BUG FIX - Improved 'text stagger' check. Sometimes the plugin was identifying text as scrolling in, even though it was not
 * MISC - Proper test and support for .NET 4.x equivalent scripting backend for Unity
 * MISC - Timeout handling if an endpoint becomes unresponsive
 * MISC - Support post processing for normal text translations
 * MISC - Change harmony text hook priority to 'Last' instead of simply be executed 'after DTL'
 * MISC - More resilient harmony hook implementation to support potentially different versions of harmony being used
 * MISC - Updated harmony version deployed with the plugin (for IPA, ReiPatcher and UnityInjector), so it is no longer the homebrew version that was distributed with BepInEx
 * MISC - Made UI more readable by using a solid background
 * MISC - Changed max queued translations from 3500 to 4000
randoman 6 роки тому
батько
коміт
931333bdea
34 змінених файлів з 356 додано та 129 видалено
  1. 16 1
      CHANGELOG.md
  2. 4 1
      README.md
  3. BIN
      libs/0Harmony.dll
  4. BIN
      libs/0Harmony_bepinex.dll
  5. 1 1
      src/XUnity.AutoTranslator.Patcher/Patcher.cs
  6. 1 1
      src/XUnity.AutoTranslator.Plugin.BepIn/XUnity.AutoTranslator.Plugin.BepIn.csproj
  7. 70 37
      src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs
  8. 4 1
      src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs
  9. 9 0
      src/XUnity.AutoTranslator.Plugin.Core/Constants/ClrTypes.cs
  10. 0 12
      src/XUnity.AutoTranslator.Plugin.Core/Constants/KnownPlugins.cs
  11. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Constants/PluginData.cs
  12. 12 3
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ConfiguredEndpoint.cs
  13. 5 3
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/HttpEndpoint.cs
  14. 1 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/GameObjectExtensions.cs
  15. 23 5
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/HarmonyInstanceExtensions.cs
  16. 14 0
      src/XUnity.AutoTranslator.Plugin.Core/Features.cs
  17. 0 1
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/HooksSetup.cs
  18. 10 10
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/IMGUIHooks.cs
  19. 19 19
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/ImageHooks.cs
  20. 3 3
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/NGUIHooks.cs
  21. 4 5
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/TextGetterCompatHooks.cs
  22. 10 10
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/TextMeshProHooks.cs
  23. 4 6
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/UGUIHooks.cs
  24. 39 2
      src/XUnity.AutoTranslator.Plugin.Core/Shim/CustomYieldInstructionShim.cs
  25. 33 0
      src/XUnity.AutoTranslator.Plugin.Core/UI/GUIUtil.cs
  26. 15 1
      src/XUnity.AutoTranslator.Plugin.Core/UI/XuaWindow.cs
  27. 40 0
      src/XUnity.AutoTranslator.Plugin.Core/Web/Internal/ConnectionTrackingWebClient.cs
  28. 3 0
      src/XUnity.AutoTranslator.Plugin.Core/Web/XUnityWebClient.cs
  29. 9 0
      src/XUnity.AutoTranslator.Plugin.Core/Web/XUnityWebResponse.cs
  30. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/XUnity.AutoTranslator.Plugin.Core.csproj
  31. 1 1
      src/XUnity.AutoTranslator.Plugin.IPA/XUnity.AutoTranslator.Plugin.IPA.csproj
  32. 1 1
      src/XUnity.AutoTranslator.Plugin.UnityInjector/XUnity.AutoTranslator.Plugin.UnityInjector.csproj
  33. 1 1
      src/XUnity.AutoTranslator.Setup/Program.cs
  34. 2 2
      src/XUnity.AutoTranslator.Setup/XUnity.AutoTranslator.Setup.csproj

+ 16 - 1
CHANGELOG.md

@@ -1,4 +1,19 @@
-### 3.0.0
+### 3.0.1
+ * BUG FIX - Fixed bug that could in certain situation cause IMGUI translation to drain on performance
+ * BUG FIX - Never close a service point while a request is ongoing. Previously this could cause the plugin to lockup
+ * BUG FIX - Only disable certificate checks if the .NET version is at or below 3.5
+ * BUG FIX - Improved cleanup of object references
+ * BUG FIX - Improved 'text stagger' check. Sometimes the plugin was identifying text as scrolling in, even though it was not
+ * MISC - Proper test and support for .NET 4.x equivalent scripting backend for Unity
+ * MISC - Timeout handling if an endpoint becomes unresponsive
+ * MISC - Support post processing for normal text translations
+ * MISC - Change harmony text hook priority to 'Last' instead of simply be executed 'after DTL'
+ * MISC - More resilient harmony hook implementation to support potentially different versions of harmony being used
+ * MISC - Updated harmony version deployed with the plugin (for IPA, ReiPatcher and UnityInjector), so it is no longer the homebrew version that was distributed with BepInEx
+ * MISC - Made UI more readable by using a solid background
+ * MISC - Changed max queued translations from 3500 to 4000
+
+### 3.0.0
  * FEATURE - UI to control plugin more conveniently (press ALT + 0 (that's a zero))
  * FEATURE - Dynamic selection of translator during game session
  * FEATURE - Support BingTranslate API

+ 4 - 1
README.md

@@ -141,7 +141,8 @@ ForceUIResizing=True             ;Indicates whether the UI resize behavior shoul
 IgnoreTextStartingWith=\u180e;   ;Indicates that the plugin should ignore any strings starting with certain characters. This is a list seperated by ';'.
 TextGetterCompatibilityMode=False ;Indicates whether or not to enable "Text Getter Compatibility Mode". Should only be enabled if required by the game. 
 GameLogTextPaths=                ;Indicates specific paths for game objects that the game uses as "log components", where it continuously appends or prepends text to. Requires expert knowledge to setup. This is a list seperated by ';'.
-RomajiPostProcessing=RemoveAllDiacritics;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"]
+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"]
 
 [Texture]
 TextureDirectory=Translation\Texture ;Directory to dump textures to, and root of directories to load images from. Can use placeholder: {GameExeName}
@@ -237,6 +238,8 @@ To rememdy this, post processing can be applied to translations when 'romaji' is
  * `ReplaceMacronWithCircumflex`: Replaces the macron diacritic with a circumflex.
  * `RemoveApostrophes`: Some translators might decide to include apostrophes after the 'n'-character. Applying this option removes those.
 
+This type of post processing is also applied to normal translations, but instead uses the option `TranslationPostProcessing`, which can use the same values.
+
 #### 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.

BIN
libs/0Harmony.dll


BIN
libs/0Harmony_bepinex.dll


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

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

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

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.0.0</Version>
+      <Version>3.0.1</Version>
    </PropertyGroup>
 
    <ItemGroup>

+ 70 - 37
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs

@@ -121,6 +121,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private string[] _previouslyQueuedText = new string[ Settings.PreviousTextStaggerCount ];
       private int _staggerTextCursor = 0;
       private int _concurrentStaggers = 0;
+      private int _lastStaggerCheckFrame = -1;
 
       private int _frameForLastQueuedTranslation = -1;
       private int _consecutiveFramesTranslated = 0;
@@ -215,10 +216,16 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
          // TODO: Perhaps some bleeding edge check to see if this is required?
          var callback = _httpSecurity.GetCertificateValidationCheck();
-         if( callback != null )
+         if( callback != null && !Features.SupportsNet4x )
          {
+            XuaLogger.Current.Info( $"Disabling certificate checks for endpoints because a .NET 3.x runtime is used." );
+
             ServicePointManager.ServerCertificateValidationCallback += callback;
          }
+         else
+         {
+            XuaLogger.Current.Info( $"Not disabling certificate checks for endpoints because a .NET 4.x runtime is used." );
+         }
 
          // Save again because configuration may be modified by endpoints
          try
@@ -453,6 +460,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          try
          {
+            var startTime = Time.realtimeSinceStartup;
             lock( _writeToFileSync )
             {
                Directory.CreateDirectory( Path.Combine( PluginEnvironment.Current.DataPath, Settings.TranslationDirectory ).Parameterize() );
@@ -465,9 +473,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
                   LoadTranslationsInFile( fullFileName );
                }
             }
+            var endTime = Time.realtimeSinceStartup;
+            XuaLogger.Current.Info( $"Loaded text files (took {Math.Round( endTime - startTime, 2 )} seconds)" );
 
             if( Settings.EnableTextureTranslation || Settings.EnableTextureDumping )
             {
+               startTime = Time.realtimeSinceStartup;
+
                _translatedImages.Clear();
                _untranslatedImages.Clear();
                Directory.CreateDirectory( Path.Combine( PluginEnvironment.Current.DataPath, Settings.TextureDirectory ).Parameterize() );
@@ -475,6 +487,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
                {
                   RegisterImageFromFile( fullFileName );
                }
+
+               endTime = Time.realtimeSinceStartup;
+               XuaLogger.Current.Info( $"Loaded texture files (took {Math.Round( endTime - startTime, 2 )} seconds)" );
             }
          }
          catch( Exception e )
@@ -777,44 +792,50 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       private void CheckStaggerText( string untranslatedText )
       {
-         bool wasProblematic = false;
-
-         for( int i = 0 ; i < _previouslyQueuedText.Length ; i++ )
+         var currentFrame = Time.frameCount;
+         if( currentFrame != _lastStaggerCheckFrame )
          {
-            var previouslyQueuedText = _previouslyQueuedText[ i ];
+            _lastStaggerCheckFrame = currentFrame;
+
+            bool wasProblematic = false;
 
-            if( previouslyQueuedText != null )
+            for( int i = 0 ; i < _previouslyQueuedText.Length ; i++ )
             {
-               if( untranslatedText.RemindsOf( previouslyQueuedText ) )
+               var previouslyQueuedText = _previouslyQueuedText[ i ];
+
+               if( previouslyQueuedText != null )
                {
-                  wasProblematic = true;
-                  break;
-               }
+                  if( untranslatedText.RemindsOf( previouslyQueuedText ) )
+                  {
+                     wasProblematic = true;
+                     break;
+                  }
 
+               }
             }
-         }
 
-         if( wasProblematic )
-         {
-            _concurrentStaggers++;
-            if( _concurrentStaggers > Settings.MaximumStaggers )
+            if( wasProblematic )
             {
-               _unstartedJobs.Clear();
-               _completedJobs.Clear();
-               _ongoingJobs.Clear();
+               _concurrentStaggers++;
+               if( _concurrentStaggers > Settings.MaximumStaggers )
+               {
+                  _unstartedJobs.Clear();
+                  _completedJobs.Clear();
+                  _ongoingJobs.Clear();
 
-               Settings.IsShutdown = true;
-               Settings.IsShutdownFatal = true;
-               XuaLogger.Current.Error( $"SPAM DETECTED: Text that is 'scrolling in' is being translated. Disable that feature. Shutting down plugin." );
+                  Settings.IsShutdown = true;
+                  Settings.IsShutdownFatal = true;
+                  XuaLogger.Current.Error( $"SPAM DETECTED: Text that is 'scrolling in' is being translated. Disable that feature. Shutting down plugin." );
+               }
+            }
+            else
+            {
+               _concurrentStaggers = 0;
             }
-         }
-         else
-         {
-            _concurrentStaggers = 0;
-         }
 
-         _previouslyQueuedText[ _staggerTextCursor % _previouslyQueuedText.Length ] = untranslatedText;
-         _staggerTextCursor++;
+            _previouslyQueuedText[ _staggerTextCursor % _previouslyQueuedText.Length ] = untranslatedText;
+            _staggerTextCursor++;
+         }
       }
 
       private void CheckThresholds()
@@ -1596,6 +1617,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
             info?.Reset( originalText );
             var isSpammer = ui.IsSpammingComponent();
+            if( isSpammer && !IsBelowMaxLength( text ) ) return null; // avoid templating long strings every frame for IMGUI, important!
+
             var textKey = new TranslationKey( ui, text, isSpammer, context != null );
 
             // if we already have translation loaded in our _translatios dictionary, simply load it and set text
@@ -1869,8 +1892,6 @@ namespace XUnity.AutoTranslator.Plugin.Core
             yield return new WaitForSeconds( delay );
             var afterText = ui.GetText();
 
-            //Logger.Current.Debug( "WAITING: " + ui.GetType().Name + ": " + afterText );
-
             if( beforeText == afterText )
             {
                onTextStabilized( afterText );
@@ -1983,12 +2004,6 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          try
          {
-            // perform this check every 100 frames!
-            if( Time.frameCount % 100 == 0 )
-            {
-               ConnectionTrackingWebClient.CheckServicePoints();
-            }
-
             if( Features.SupportsClipboard )
             {
                CopyToClipboard();
@@ -2010,6 +2025,12 @@ namespace XUnity.AutoTranslator.Plugin.Core
                }
             }
 
+            // perform this check every 100 frames!
+            if( Time.frameCount % 100 == 0 && _ongoingJobs.Count == 0 )
+            {
+               ConnectionTrackingWebClient.CheckServicePoints();
+            }
+
             if( Input.anyKey )
             {
                var isAltPressed = Input.GetKey( KeyCode.LeftAlt ) || Input.GetKey( KeyCode.RightAlt );
@@ -2042,6 +2063,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
                {
                   RebootPlugin();
                }
+               //else if( isAltPressed && Input.GetKeyDown( KeyCode.B ) )
+               //{
+               //   ConnectionTrackingWebClient.CloseServicePoints();
+               //}
                else if( isAltPressed && ( Input.GetKeyDown( KeyCode.Alpha0 ) || Input.GetKeyDown( KeyCode.Keypad0 ) ) )
                {
                   if( _window != null )
@@ -2217,6 +2242,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
                   {
                      translatedText = RomanizationHelper.PostProcess( translatedText, Settings.RomajiPostProcessing );
                   }
+                  else if( Settings.TranslationPostProcessing != TextPostProcessing.None )
+                  {
+                     translatedText = RomanizationHelper.PostProcess( translatedText, Settings.TranslationPostProcessing );
+                  }
 
                   if( Settings.ForceSplitTextAfterCharacters > 0 )
                   {
@@ -2291,7 +2320,11 @@ namespace XUnity.AutoTranslator.Plugin.Core
             {
                translatedText = RomanizationHelper.PostProcess( translatedText, Settings.RomajiPostProcessing );
             }
-            
+            else if( Settings.TranslationPostProcessing != TextPostProcessing.None )
+            {
+               translatedText = RomanizationHelper.PostProcess( translatedText, Settings.TranslationPostProcessing );
+            }
+
             if( Settings.ForceSplitTextAfterCharacters > 0 )
             {
                translatedText = translatedText.SplitToLines( Settings.ForceSplitTextAfterCharacters, '\n', ' ', ' ' );

+ 4 - 1
src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs

@@ -24,7 +24,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static readonly int MaxErrors = 5;
       public static readonly float ClipboardDebounceTime = 1f;
       public static readonly int MaxTranslationsBeforeShutdown = 8000;
-      public static readonly int MaxUnstartedJobs = 3500;
+      public static readonly int MaxUnstartedJobs = 4000;
       public static readonly float IncreaseBatchOperationsEvery = 30;
       public static readonly int MaximumStaggers = 6;
       public static readonly int PreviousTextStaggerCount = 3;
@@ -32,6 +32,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static readonly int MaximumConsecutiveSecondsTranslated = 60;
       public static bool UsesWhitespaceBetweenWords = false;
       public static string ApplicationName;
+      public static float Timeout = 50.0f;
 
 
       public static bool IsShutdown = false;
@@ -80,6 +81,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static HashSet<string> GameLogTextPaths;
       public static bool TextGetterCompatibilityMode;
       public static TextPostProcessing RomajiPostProcessing;
+      public static TextPostProcessing TranslationPostProcessing;
 
       public static string TextureDirectory;
       public static bool EnableTextureTranslation;
@@ -151,6 +153,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
          GameLogTextPaths.RemoveWhere( x => !x.StartsWith( "/" ) ); // clean up to ensure no 'empty' entries
          WhitespaceRemovalStrategy = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "WhitespaceRemovalStrategy", WhitespaceHandlingStrategy.TrimPerNewline );
          RomajiPostProcessing = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "RomajiPostProcessing", TextPostProcessing.ReplaceMacronWithCircumflex | TextPostProcessing.RemoveApostrophes );
+         TranslationPostProcessing = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "TranslationPostProcessing", TextPostProcessing.ReplaceMacronWithCircumflex );
 
          TextureDirectory = PluginEnvironment.Current.Preferences.GetOrDefault( "Texture", "TextureDirectory", @"Translation\Texture" );
          EnableTextureTranslation = PluginEnvironment.Current.Preferences.GetOrDefault( "Texture", "EnableTextureTranslation", false );

+ 9 - 0
src/XUnity.AutoTranslator.Plugin.Core/Constants/ClrTypes.cs

@@ -36,6 +36,7 @@ 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 GraphicRaycaster = FindType( "UnityEngine.UI.GraphicRaycaster" );
 
       // Something...
       public static readonly Type Typewriter = FindType( "Typewriter" );
@@ -51,6 +52,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
       // Live2D
       public static readonly Type CubismRenderer = FindType( "Live2D.Cubism.Rendering.CubismRenderer" );
 
+      // Harmony
+      public static readonly Type HarmonyInstance = FindType( "Harmony.HarmonyInstance" );
+      public static readonly Type HarmonyMethod = FindType( "Harmony.HarmonyMethod" );
+
+      // Mono / .NET
+      public static readonly Type MethodBase = FindType( "System.Reflection.MethodBase" );
+      public static readonly Type Task = FindType( "System.Threading.Tasks.Task" );
+
       private static Type FindType( string name )
       {
          return AppDomain.CurrentDomain.GetAssemblies()

+ 0 - 12
src/XUnity.AutoTranslator.Plugin.Core/Constants/KnownPlugins.cs

@@ -1,12 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace XUnity.AutoTranslator.Plugin.Core.Constants
-{
-   internal static class KnownPlugins
-   {
-      public const string DynamicTranslationLoader = "com.bepis.bepinex.dynamictranslationloader";
-   }
-}

+ 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.0";
+      public const string Version = "3.0.1";
    }
 }

+ 12 - 3
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ConfiguredEndpoint.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
+using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
@@ -50,19 +51,27 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
 
       public IEnumerator Translate( string[] untranslatedTexts, string from, string to, Action<string[]> success, Action<string, Exception> failure )
       {
+         var startTime = Time.realtimeSinceStartup;
          var context = new TranslationContext( untranslatedTexts, from, to, success, failure );
          _ongoingTranslations++;
 
-         bool ok = false;
-         IEnumerator iterator = null;
          try
          {
-            iterator = Endpoint.Translate( context );
+            bool ok = false;
+            var iterator = Endpoint.Translate( context );
             if( iterator != null )
             {
                TryMe: try
                {
                   ok = iterator.MoveNext();
+
+                  // check for timeout
+                  var now = Time.realtimeSinceStartup;
+                  if( now - startTime > Settings.Timeout )
+                  {
+                     ok = false;
+                     context.FailWithoutThrowing( $"Timeout occurred during translation (took more than {Settings.Timeout} seconds)", null );
+                  }
                }
                catch( TranslationContextException )
                {

+ 5 - 3
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/HttpEndpoint.cs

@@ -79,20 +79,22 @@ namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
          // prepare request
          OnCreateRequest( httpContext );
          if( httpContext.Request == null ) httpContext.Fail( "No request object was provided by the translator." );
-         
+
          // execute request
          var client = new XUnityWebClient();
          var response = client.Send( httpContext.Request );
-         
+
          // wait for completion
          var iterator = response.GetSupportedEnumerator();
          while( iterator.MoveNext() ) yield return iterator.Current;
 
+         if( response.IsTimedOut ) httpContext.Fail( "Error occurred while retrieving translation. Timeout." );
+
          httpContext.Response = response;
          OnInspectResponse( httpContext );
 
          // failure
-         if( response.Error != null ) httpContext.Fail( "Error occurred while retrieving translation.", response.Error ); 
+         if( response.Error != null ) httpContext.Fail( "Error occurred while retrieving translation.", response.Error );
 
          // failure
          if( response.Data == null ) httpContext.Fail( "Error occurred while retrieving translation. Nothing was returned." );

+ 1 - 0
src/XUnity.AutoTranslator.Plugin.Core/Extensions/GameObjectExtensions.cs

@@ -46,6 +46,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          while( --i >= 0 )
          {
             path.Append( "/" ).Append( _objects[ i ].name );
+            _objects[ i ] = null;
          }
 
 

+ 23 - 5
src/XUnity.AutoTranslator.Plugin.Core/Extensions/HarmonyInstanceExtensions.cs

@@ -1,13 +1,17 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Reflection;
 using System.Text;
 using Harmony;
+using XUnity.AutoTranslator.Plugin.Core.Constants;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 {
    internal static class HarmonyInstanceExtensions
    {
+      public static readonly MethodInfo PatchMethod = ClrTypes.HarmonyInstance.GetMethod( "Patch", new Type[] { ClrTypes.MethodBase, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod } );
+
       public static void PatchAll( this HarmonyInstance instance, IEnumerable<Type> types )
       {
          foreach( var type in types )
@@ -20,12 +24,26 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
       {
          try
          {
-            var parentMethodInfos = type.GetHarmonyMethods();
-            if( parentMethodInfos != null && parentMethodInfos.Count() > 0 )
+            var prepare = type.GetMethod( "Prepare", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public );
+            if( prepare == null || (bool)prepare.Invoke( null, new object[] { instance } ) )
             {
-               var info = HarmonyMethod.Merge( parentMethodInfos );
-               var processor = new PatchProcessor( instance, type, info );
-               processor.Patch();
+               var original = (MethodBase)type.GetMethod( "TargetMethod", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public ).Invoke( null, new object[] { instance } );
+               if( original != null )
+               {
+                  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;
+
+                  PatchMethod.Invoke( instance, new object[] { original, harmonyPrefix, harmonyPostfix, harmonyTranspiler } );
+               }
+               else
+               {
+                  XuaLogger.Current.Warn( $"Could not enable hook '{type.Name}'. Likely due differences between different versions of the engine or text framework." );
+               }
             }
          }
          catch( Exception e )

+ 14 - 0
src/XUnity.AutoTranslator.Plugin.Core/Features.cs

@@ -22,6 +22,11 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// </summary>
       public static bool SupportsSceneManager { get; } = false;
 
+      /// <summary>
+      /// Gets a bool indicating if this game is running in a .NET 4.x runtime.
+      /// </summary>
+      public static bool SupportsNet4x { get; } = false;
+
       static Features()
       {
          try
@@ -50,6 +55,15 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
 
          }
+
+         try
+         {
+            SupportsNet4x = ClrTypes.Task != null;
+         }
+         catch( Exception )
+         {
+
+         }
       }
    }
 }

+ 0 - 1
src/XUnity.AutoTranslator.Plugin.Core/Hooks/HooksSetup.cs

@@ -139,7 +139,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          {
             XuaLogger.Current.Error( e, "An error occurred while setting up hooks for Utage." );
          }
-
       }
 
       public static bool SetupHook( string eventName, Func<object, string, string> callback )

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

@@ -27,7 +27,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI
       };
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class GUI_BeginGroup_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -49,7 +49,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class GUI_Box_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -72,7 +72,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI
 
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class GUI_DoRepeatButton_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -94,7 +94,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class GUI_DoLabel_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -116,7 +116,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class GUI_DoButton_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -138,7 +138,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class GUI_DoModalWindow_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -160,7 +160,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class GUI_DoWindow_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -182,7 +182,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class GUI_DoButtonGrid_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -207,7 +207,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class GUI_DoTextField_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -229,7 +229,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class GUI_DoToggle_Hook
    {
       static bool Prepare( HarmonyInstance instance )

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

@@ -54,7 +54,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.SpriteRenderer, "sprite" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.SpriteRenderer, "sprite" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -73,7 +73,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.CubismRenderer, "MainTexture" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.CubismRenderer, "MainTexture" )?.GetSetMethod();
       }
 
       public static void Prefix( object __instance, Texture2D value )
@@ -111,7 +111,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( typeof( Material ), "mainTexture" ).GetSetMethod();
+         return AccessTools.Property( typeof( Material ), "mainTexture" )?.GetSetMethod();
       }
 
       public static void Prefix( object __instance, Texture value )
@@ -155,7 +155,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( typeof( Image ), "sprite" ).GetSetMethod();
+         return AccessTools.Property( typeof( Image ), "sprite" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -174,7 +174,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( typeof( Image ), "overrideSprite" ).GetSetMethod();
+         return AccessTools.Property( typeof( Image ), "overrideSprite" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -193,7 +193,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( typeof( Image ), "material" ).GetSetMethod();
+         return AccessTools.Property( typeof( Image ), "material" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -212,7 +212,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( typeof( RawImage ), "texture" ).GetSetMethod();
+         return AccessTools.Property( typeof( RawImage ), "texture" )?.GetSetMethod();
       }
 
       public static void Prefix( object __instance, Texture value )
@@ -253,7 +253,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UIAtlas, "spriteMaterial" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UIAtlas, "spriteMaterial" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -291,7 +291,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UISprite, "material" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UISprite, "material" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -310,7 +310,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UISprite, "atlas" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UISprite, "atlas" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -329,7 +329,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UITexture, "mainTexture" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UITexture, "mainTexture" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -348,7 +348,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UITexture, "material" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UITexture, "material" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -386,7 +386,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UI2DSprite, "sprite2D" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UI2DSprite, "sprite2D" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -405,7 +405,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UI2DSprite, "material" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UI2DSprite, "material" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -424,7 +424,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UIPanel, "clipTexture" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UIPanel, "clipTexture" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -444,7 +444,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UIFont, "material" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UIFont, "material" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -463,7 +463,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UIFont, "dynamicFont" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UIFont, "dynamicFont" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -482,7 +482,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UILabel, "bitmapFont" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UILabel, "bitmapFont" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -501,7 +501,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.UILabel, "trueTypeFont" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.UILabel, "trueTypeFont" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )

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

@@ -20,7 +20,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
       };
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class UILabel_text_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -30,7 +30,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( Constants.ClrTypes.UILabel, "text" ).GetSetMethod();
+         return AccessTools.Property( Constants.ClrTypes.UILabel, "text" )?.GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -43,7 +43,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class UILabel_OnEnable_Hook
    {
       static bool Prepare( HarmonyInstance instance )

+ 4 - 5
src/XUnity.AutoTranslator.Plugin.Core/Hooks/TextGetterCompatHooks.cs

@@ -14,7 +14,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextGetterCompat
       };
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony]
    internal static class Text_text_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -24,8 +24,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextGetterCompat
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         var text = AccessTools.Property( ClrTypes.Text, "text" );
-         return text.GetGetMethod();
+         return AccessTools.Property( ClrTypes.Text, "text" )?.GetGetMethod();
       }
 
       static void Postfix( object __instance, ref string __result )
@@ -34,7 +33,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextGetterCompat
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony]
    internal static class TMP_Text_text_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -44,7 +43,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextGetterCompat
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.TMP_Text, "text" ).GetGetMethod();
+         return AccessTools.Property( ClrTypes.TMP_Text, "text" )?.GetGetMethod();
       }
 
       static void Postfix( object __instance, ref string __result )

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

@@ -25,7 +25,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
       };
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class TeshMeshProUGUI_OnEnable_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -48,7 +48,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class TeshMeshPro_OnEnable_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -71,7 +71,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class TMP_Text_text_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -81,7 +81,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( ClrTypes.TMP_Text, "text" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.TMP_Text, "text" )?.GetSetMethod();
       }
 
       static void Postfix( object __instance )
@@ -94,7 +94,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class TMP_Text_SetText_Hook1
    {
       static bool Prepare( HarmonyInstance instance )
@@ -117,7 +117,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class TMP_Text_SetText_Hook2
    {
       static bool Prepare( HarmonyInstance instance )
@@ -140,7 +140,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class TMP_Text_SetText_Hook3
    {
       static bool Prepare( HarmonyInstance instance )
@@ -163,7 +163,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class TMP_Text_SetCharArray_Hook1
    {
       static bool Prepare( HarmonyInstance instance )
@@ -186,7 +186,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class TMP_Text_SetCharArray_Hook2
    {
       static bool Prepare( HarmonyInstance instance )
@@ -209,7 +209,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class TMP_Text_SetCharArray_Hook3
    {
       static bool Prepare( HarmonyInstance instance )

+ 4 - 6
src/XUnity.AutoTranslator.Plugin.Core/Hooks/UGUIHooks.cs

@@ -18,7 +18,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
       };
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class Text_text_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -28,8 +28,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         var text = AccessTools.Property( ClrTypes.Text, "text" );
-         return text.GetSetMethod();
+         return AccessTools.Property( ClrTypes.Text, "text" )?.GetSetMethod();
       }
 
       static void Postfix( object __instance )
@@ -42,7 +41,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
       }
    }
 
-   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   [Harmony, HarmonyPriority( Priority.Last )]
    internal static class Text_OnEnable_Hook
    {
       static bool Prepare( HarmonyInstance instance )
@@ -52,8 +51,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         var OnEnable = AccessTools.Method( ClrTypes.Text, "OnEnable" );
-         return OnEnable;
+         return AccessTools.Method( ClrTypes.Text, "OnEnable" );
       }
 
       static void Postfix( object __instance )

+ 39 - 2
src/XUnity.AutoTranslator.Plugin.Core/Shim/CustomYieldInstructionShim.cs

@@ -12,6 +12,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Shim
    /// </summary>
    public abstract class CustomYieldInstructionShim : IEnumerator
    {
+      private float? _startTime;
+
       /// <summary>
       /// Default constructor.
       /// </summary>
@@ -19,13 +21,38 @@ namespace XUnity.AutoTranslator.Plugin.Core.Shim
       {
       }
 
+      internal bool DetermineKeepWaiting()
+      {
+         if( !keepWaiting || IsTimedOut ) return false;
+         
+         if( InGameTimeout.HasValue )
+         {
+            if( !_startTime.HasValue )
+            {
+               _startTime = Time.realtimeSinceStartup;
+            }
+
+            var startTime = _startTime.Value;
+            var time = Time.realtimeSinceStartup - startTime;
+
+            if( time > InGameTimeout )
+            {
+               IsTimedOut = true;
+
+               return false;
+            }
+         }
+
+         return true;
+      }
+
       /// <summary>
       /// Checks if the yield instruction needs to keep waiting.
       /// </summary>
       /// <returns></returns>
       public bool MoveNext()
       {
-         return keepWaiting;
+         return DetermineKeepWaiting();
       }
 
       /// <summary>
@@ -53,6 +80,16 @@ namespace XUnity.AutoTranslator.Plugin.Core.Shim
       /// </summary>
       public abstract bool keepWaiting { get; }
 
+      /// <summary>
+      /// Gets or sets a timeout in in-game seconds.
+      /// </summary>
+      public float? InGameTimeout { get; set; }
+
+      /// <summary>
+      /// Gets or sets a bool indicating if the CustomYieldOperation timed out.
+      /// </summary>
+      public bool IsTimedOut { get; set; }
+
       /// <summary>
       /// Gets an enumerator that can be iterated through
       /// in a co-routine and will work in even ancient versions
@@ -67,7 +104,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Shim
          }
          else
          {
-            while( keepWaiting )
+            while( DetermineKeepWaiting() )
             {
                yield return new WaitForSeconds( 0.2f );
             }

+ 33 - 0
src/XUnity.AutoTranslator.Plugin.Core/UI/GUIUtil.cs

@@ -51,6 +51,39 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
          padding = Empty
       };
 
+      public static GUIStyle WindowBackgroundStyle = new GUIStyle
+      {
+         normal = new GUIStyleState
+         {
+            background = CreateBackgroundTexture()
+         }
+      };
+
       public static Rect R( int x, int y, int width, int height ) => new Rect( x, y, width, height );
+
+      private static Texture2D CreateBackgroundTexture()
+      {
+         var windowBackground = new Texture2D( 1, 1, TextureFormat.ARGB32, false );
+         windowBackground.SetPixel( 0, 0, new Color( 0.6f, 0.6f, 0.6f, 1 ) );
+         windowBackground.Apply();
+         GameObject.DontDestroyOnLoad( windowBackground );
+         return windowBackground;
+      }
+
+      public static GUIStyle GetWindowBackgroundStyle()
+      {
+         if( !WindowBackgroundStyle.normal.background )
+         {
+            WindowBackgroundStyle = new GUIStyle
+            {
+               normal = new GUIStyleState
+               {
+                  background = CreateBackgroundTexture()
+               }
+            };
+         }
+
+         return WindowBackgroundStyle;
+      }
    }
 }

+ 15 - 1
src/XUnity.AutoTranslator.Plugin.Core/UI/XuaWindow.cs

@@ -3,6 +3,7 @@ using System.Linq;
 using System.Text;
 using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Endpoints;
+using XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI;
 
 namespace XUnity.AutoTranslator.Plugin.Core.UI
 {
@@ -18,12 +19,23 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
 
       private DropdownGUI<TranslatorDropdownOptionViewModel, ConfiguredEndpoint> _endpointDropdown;
 
+      private bool _isShown;
       private List<ToggleViewModel> _toggles;
       private List<TranslatorDropdownOptionViewModel> _endpointOptions;
       private List<ButtonViewModel> _commandButtons;
       private List<LabelViewModel> _labels;
 
-      public bool IsShown { get; set; }
+      public bool IsShown
+      {
+         get
+         {
+            return _isShown;
+         }
+         set
+         {
+            _isShown = value;
+         }
+      }
 
       public XuaWindow(
          List<ToggleViewModel> toggles,
@@ -39,6 +51,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.UI
 
       public void OnGUI()
       {
+         GUI.Box( _windowRect, GUIContent.none, GUIUtil.GetWindowBackgroundStyle() );
+
          _windowRect = GUI.Window( 5464332, _windowRect, CreateWindowUI, "---- XUnity.AutoTranslator UI ----" );
       }
 

+ 40 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/Internal/ConnectionTrackingWebClient.cs

@@ -394,6 +394,46 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web.Internal
          }
       }
 
+      internal static void CloseServicePoints()
+      {
+         List<KeyValuePair<string, ServicePointState>> idleEntries = null;
+
+         lock( ActiveConnections )
+         {
+            var timestamp = DateTime.UtcNow;
+            foreach( var kvp in ActiveConnections )
+            {
+               if( idleEntries == null )
+               {
+                  idleEntries = new List<KeyValuePair<string, ServicePointState>>();
+               }
+
+               idleEntries.Add( kvp );
+            }
+
+            if( idleEntries != null )
+            {
+               foreach( var idleEntry in idleEntries )
+               {
+                  ActiveConnections.Remove( idleEntry.Key );
+                  XuaLogger.Current.Debug( $"Closing connections to endpoint '{idleEntry.Key}' due to force shutdown." );
+               }
+            }
+         }
+
+         if( idleEntries != null )
+         {
+            ThreadPool.QueueUserWorkItem( delegate ( object state )
+            {
+               // never do a job like this on the game loop thread
+               foreach( var kvp in idleEntries )
+               {
+                  kvp.Value.ServicePoint.CloseConnectionGroup( ConnectionGroupName );
+               }
+            } );
+         }
+      }
+
       private class ServicePointState
       {
          public ServicePointState( ServicePoint servicePoint )

+ 3 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/XUnityWebClient.cs

@@ -9,6 +9,7 @@ using System.Reflection;
 using System.Text;
 using Harmony;
 using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Endpoints.Http;
 using XUnity.AutoTranslator.Plugin.Core.Web.Internal;
 
@@ -101,6 +102,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
             {
                Headers = _requestHeaders;
             }
+            httpRequest.ReadWriteTimeout = (int)( Settings.Timeout * 1000 ) - 10000;
+            httpRequest.Timeout = (int)( Settings.Timeout * 1000 ) - 5000;
          }
       }
 

+ 9 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/XUnityWebResponse.cs

@@ -2,6 +2,7 @@
 using System.Collections;
 using System.Net;
 using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Shim;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Web
@@ -11,6 +12,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
    /// </summary>
    public class XUnityWebResponse : CustomYieldInstructionShim
    {
+      /// <summary>
+      /// Default construcutor.
+      /// </summary>
+      public XUnityWebResponse()
+      {
+         InGameTimeout = Settings.Timeout;
+      }
+
       internal void SetCompleted( HttpStatusCode code, string data, WebHeaderCollection headers, CookieCollection newCookies, Exception error )
       {
          IsCompleted = true;

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/XUnity.AutoTranslator.Plugin.Core.csproj

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.0.0</Version>
+      <Version>3.0.1</Version>
    </PropertyGroup>
 
    <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

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

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.0.0</Version>
+      <Version>3.0.1</Version>
    </PropertyGroup>
 
    <ItemGroup>

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

@@ -2,7 +2,7 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>3.0.0</Version>
+      <Version>3.0.1</Version>
    </PropertyGroup>
 
    <ItemGroup>

+ 1 - 1
src/XUnity.AutoTranslator.Setup/Program.cs

@@ -56,7 +56,7 @@ namespace XUnity.AutoTranslator.Setup
          foreach( var launcher in launchers )
          {
             var managedDir = Path.Combine( gamePath, launcher.Data.Name, "Managed" );
-            AddFile( Path.Combine( managedDir, "0Harmony.dll" ), Resources._0Harmony );
+            AddFile( Path.Combine( managedDir, "0Harmony.dll" ), Resources._0Harmony, true );
             AddFile( Path.Combine( managedDir, "ExIni.dll" ), Resources.ExIni );
             AddFile( Path.Combine( managedDir, "ReiPatcher.exe" ), Resources.ReiPatcher );
             AddFile( Path.Combine( managedDir, "XUnity.AutoTranslator.Plugin.Core.dll" ), Resources.XUnity_AutoTranslator_Plugin_Core, true );

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

@@ -4,7 +4,7 @@
       <OutputType>Exe</OutputType>
       <TargetFramework>net40</TargetFramework>
       <AssemblyName>SetupReiPatcherAndAutoTranslator</AssemblyName>
-      <Version>3.0.0</Version>
+      <Version>3.0.1</Version>
       <ApplicationIcon>icon.ico</ApplicationIcon>
       <Win32Resource />
    </PropertyGroup>
@@ -48,7 +48,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;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\ReiPatcher\&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\ReiPatcher\$(TargetName)$(TargetExt)' -DestinationPath '$(SolutionDir)dist\ReiPatcher\XUnity.AutoTranslator-ReiPatcher-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
+      <Exec Command="if $(ConfigurationName) == Release (&#xD;&#xA;   XCOPY /Y /I &quot;$(TargetDir)$(TargetName)$(TargetExt)&quot; &quot;$(SolutionDir)dist\ReiPatcher\&quot;&#xD;&#xA;   powershell -command &quot;Start-Sleep -s 1&quot;&#xD;&#xA;   powershell Compress-Archive -Path '$(SolutionDir)dist\ReiPatcher\$(TargetName)$(TargetExt)' -DestinationPath '$(SolutionDir)dist\ReiPatcher\XUnity.AutoTranslator-ReiPatcher-@(VersionNumber).zip' -Force)&#xD;&#xA;)" />
    </Target>
 
 </Project>