Browse Source

Fixed public API

randoman 6 years ago
parent
commit
f44009d6e7
100 changed files with 2620 additions and 1689 deletions
  1. 10 1
      CHANGELOG.md
  2. 9 1
      XUnity.AutoTranslator.sln
  3. 26 0
      src/XUnity.AutoTranslator.Plugin.Core/ApplicationInformation.cs
  4. 261 70
      src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs
  5. 19 0
      src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationState.cs
  6. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Batching/TranslationBatch.cs
  7. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Batching/TranslationLineTracker.cs
  8. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Configuration/Config.cs
  9. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Configuration/DefaultConfiguration.cs
  10. 0 2
      src/XUnity.AutoTranslator.Plugin.Core/Configuration/IConfiguration.cs
  11. 14 48
      src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs
  12. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/ConsoleLogger.cs
  13. 1 17
      src/XUnity.AutoTranslator.Plugin.Core/Constants/KnownEndpointNames.cs
  14. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Constants/KnownEvents.cs
  15. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Constants/KnownPlugins.cs
  16. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Debugging/DebugConsole.cs
  17. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Debugging/Kernel32.cs
  18. 43 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ConfiguredEndpoint.cs
  19. 17 9
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BaiduTranslateEndpoint.cs
  20. 19 13
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BingTranslateEndpoint.cs
  21. 16 9
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BingTranslateLegitimateEndpoint.cs
  22. 49 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/CustomHttpEndpoint.cs
  23. 26 22
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/GoogleTranslateEndpoint.cs
  24. 16 14
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/GoogleTranslateLegitimateEndpoint.cs
  25. 131 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/HttpEndpoint.cs
  26. 15 9
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/YandexTranslateEndpoint.cs
  27. 39 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ITranslateEndpoint.cs
  28. 65 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/KnownEndpoints.cs
  29. 59 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ProcessLineProtocol/LecPowerTranslateEndpoint.cs
  30. 168 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ProcessLineProtocol/ProcessLineProtocolEndpoint.cs
  31. 12 3
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/ExciteTranslateEndpoint.cs
  32. 21 21
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/WatsonTranslateEndpoint.cs
  33. 115 0
      src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/WwwEndpoint.cs
  34. 0 162
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/ComponentExtensions.cs
  35. 20 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/EnumerableExtensions.cs
  36. 24 1
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/GameObjectExtensions.cs
  37. 2 2
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/HarmonyInstanceExtensions.cs
  38. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/IniKeyExtensions.cs
  39. 2 2
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/StringBuilderExtensions.cs
  40. 2 141
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/StringExtensions.cs
  41. 132 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/TextComponentExtensions.cs
  42. 59 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/TextureComponentExtensions.cs
  43. 21 1
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/TextureExtensions.cs
  44. 18 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/TranslationEndpointExtensions.cs
  45. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/TypeExtensions.cs
  46. 48 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/UILabelExtensions.cs
  47. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Fonts/FontCache.cs
  48. 3 50
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/HooksSetup.cs
  49. 22 21
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/IMGUIHooks.cs
  50. 25 174
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/ImageHooks.cs
  51. 5 5
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/NGUIHooks.cs
  52. 9 8
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/TextGetterCompatHooks.cs
  53. 19 19
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/TextMeshProHooks.cs
  54. 5 6
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/UGUIHooks.cs
  55. 7 7
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/UtageHooks.cs
  56. 0 33
      src/XUnity.AutoTranslator.Plugin.Core/IKnownEndpoint.cs
  57. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/ImageTranslationInfo.cs
  58. 0 42
      src/XUnity.AutoTranslator.Plugin.Core/KnownEndpoints.cs
  59. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/MonoHttp/Helpers.cs
  60. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/MonoHttp/HtmlEncoder.cs
  61. 73 0
      src/XUnity.AutoTranslator.Plugin.Core/Parsing/GameLogTextParser.cs
  62. 9 0
      src/XUnity.AutoTranslator.Plugin.Core/Parsing/ITextParser.cs
  63. 7 4
      src/XUnity.AutoTranslator.Plugin.Core/Parsing/ParserResult.cs
  64. 9 2
      src/XUnity.AutoTranslator.Plugin.Core/Parsing/RichTextParser.cs
  65. 6 4
      src/XUnity.AutoTranslator.Plugin.Core/Parsing/UnityTextParsers.cs
  66. 0 2
      src/XUnity.AutoTranslator.Plugin.Core/Shim/CustomYieldInstructionShim.cs
  67. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/TemplatedString.cs
  68. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/TextTranslationInfo.cs
  69. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/TextureHashGenerationStrategy.cs
  70. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/TextureReloadContext.cs
  71. 3 2
      src/XUnity.AutoTranslator.Plugin.Core/TextureTranslationInfo.cs
  72. 21 0
      src/XUnity.AutoTranslator.Plugin.Core/TranslationContext.cs
  73. 1 25
      src/XUnity.AutoTranslator.Plugin.Core/TranslationJob.cs
  74. 9 0
      src/XUnity.AutoTranslator.Plugin.Core/TranslationJobState.cs
  75. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/TranslationKey.cs
  76. 21 0
      src/XUnity.AutoTranslator.Plugin.Core/UI/ButtonViewModel.cs
  77. 93 0
      src/XUnity.AutoTranslator.Plugin.Core/UI/DropdownGUI.cs
  78. 61 0
      src/XUnity.AutoTranslator.Plugin.Core/UI/DropdownOptionViewModel.cs
  79. 56 0
      src/XUnity.AutoTranslator.Plugin.Core/UI/GUIUtil.cs
  80. 17 0
      src/XUnity.AutoTranslator.Plugin.Core/UI/LabelViewModel.cs
  81. 35 0
      src/XUnity.AutoTranslator.Plugin.Core/UI/ToggleViewModel.cs
  82. 129 0
      src/XUnity.AutoTranslator.Plugin.Core/UI/XuaWindow.cs
  83. 20 0
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/DirectoryHelper.cs
  84. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/HashHelper.cs
  85. 2 2
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/IndentedTextWriter.cs
  86. 122 0
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/LanguageHelper.cs
  87. 3 84
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/ObjectReferenceMapper.cs
  88. 3 2
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/SceneManagerHelper.cs
  89. 3 3
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/TextGetterCompatModeHelper.cs
  90. 127 112
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/TextHelper.cs
  91. 2 2
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/TextureHelper.cs
  92. 2 2
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/UtageHelper.cs
  93. 129 40
      src/XUnity.AutoTranslator.Plugin.Core/Web/ConnectionTrackingWebClient.cs
  94. 0 39
      src/XUnity.AutoTranslator.Plugin.Core/Web/DefaultEndpoint.cs
  95. 0 53
      src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateHackEndpoint.cs
  96. 0 205
      src/XUnity.AutoTranslator.Plugin.Core/Web/KnownHttpEndpoint.cs
  97. 0 144
      src/XUnity.AutoTranslator.Plugin.Core/Web/KnownWwwEndpoint.cs
  98. 14 4
      src/XUnity.AutoTranslator.Plugin.Core/Web/ServiceEndpointConfiguration.cs
  99. 47 23
      src/XUnity.AutoTranslator.Plugin.Core/Web/UnityWebClient.cs
  100. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/WhitespaceHandlingStrategy.cs

+ 10 - 1
CHANGELOG.md

@@ -1,4 +1,13 @@
-### 2.18.0
+### 2.19.0
+ * FEATURE - UI to control plugin more conveniently (press ALT + X)
+ * FEATURE - Dynamic selection of translator during game session
+ * FEATURE - Support BingTranslate API
+ * FEATURE - Support LEC Offline Power Translator 15
+ * MISC - {GameExeName} variable can now be used in configuration of directories and files
+ * MISC - Changed the way the 'Custom' endpoint works. See README for more info
+ * MISC - Added new configuration 'GameLogTextPaths' to enable special handling of text components that text is being appended to continuously (requires export knowledge to setup)
+
+### 2.18.0
  * FEATURE - Text Getter Compatibility Mode. Fools the game into thinking that it is not actually translated
  * FEATURE - Textures - Live2D component support
  * FEATURE - Textures - SpriteRenderer component support

+ 9 - 1
XUnity.AutoTranslator.sln

@@ -19,12 +19,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.AutoTranslator.Setup
 		{0A2A6B66-91D4-4A4E-AC77-80C6DD748FCD} = {0A2A6B66-91D4-4A4E-AC77-80C6DD748FCD}
 	EndProjectSection
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XUnity.AutoTranslator.Plugin.UnityInjector", "src\XUnity.AutoTranslator.Plugin.UnityInjector\XUnity.AutoTranslator.Plugin.UnityInjector.csproj", "{12F1D16B-B8E1-4A9D-B65A-044650F15440}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.AutoTranslator.Plugin.UnityInjector", "src\XUnity.AutoTranslator.Plugin.UnityInjector\XUnity.AutoTranslator.Plugin.UnityInjector.csproj", "{12F1D16B-B8E1-4A9D-B65A-044650F15440}"
+EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{ACADAE2C-1642-428A-84D4-CC53E24F1348}"
 	ProjectSection(SolutionItems) = preProject
 		.editorconfig = .editorconfig
 	EndProjectSection
 EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.AutoTranslator.Plugin.Lec", "src\XUnity.AutoTranslator.Plugin.Lec\XUnity.AutoTranslator.Plugin.Lec.csproj", "{961123AE-1F4F-4340-B39D-6C6F41B3C237}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -55,6 +58,10 @@ Global
 		{12F1D16B-B8E1-4A9D-B65A-044650F15440}.Debug|Any CPU.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
+		{961123AE-1F4F-4340-B39D-6C6F41B3C237}.Debug|Any CPU.ActiveCfg = Debug|x86
+		{961123AE-1F4F-4340-B39D-6C6F41B3C237}.Debug|Any CPU.Build.0 = Debug|x86
+		{961123AE-1F4F-4340-B39D-6C6F41B3C237}.Release|Any CPU.ActiveCfg = Release|x86
+		{961123AE-1F4F-4340-B39D-6C6F41B3C237}.Release|Any CPU.Build.0 = Release|x86
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -66,6 +73,7 @@ Global
 		{0A2A6B66-91D4-4A4E-AC77-80C6DD748FCD} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
 		{86BF1F46-44C1-4301-8314-6EC32F74575F} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
 		{12F1D16B-B8E1-4A9D-B65A-044650F15440} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
+		{961123AE-1F4F-4340-B39D-6C6F41B3C237} = {0F9B38FC-4E57-4B83-AF0B-0993B8470823}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {EE803FED-4447-4D19-B3D6-88C56E8DFCCA}

+ 26 - 0
src/XUnity.AutoTranslator.Plugin.Core/ApplicationInformation.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace XUnity.AutoTranslator.Plugin.Core
+{
+   internal static class ApplicationInformation
+   {
+      [DllImport( "kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = false )]
+      private static extern int GetModuleFileName( HandleRef hModule, StringBuilder buffer, int length );
+
+      private static HandleRef Null = new HandleRef( null, IntPtr.Zero );
+
+      public static string StartupPath
+      {
+         get
+         {
+            StringBuilder stringBuilder = new StringBuilder( 260 );
+            GetModuleFileName( Null, stringBuilder, stringBuilder.Capacity );
+            return stringBuilder.ToString();
+         }
+      }
+   }
+}

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

@@ -20,7 +20,6 @@ using XUnity.AutoTranslator.Plugin.Core.Web;
 using XUnity.AutoTranslator.Plugin.Core.Hooks;
 using XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro;
 using XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI;
-using XUnity.AutoTranslator.Plugin.Core.IMGUI;
 using XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI;
 using UnityEngine.SceneManagement;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
@@ -29,6 +28,8 @@ using XUnity.AutoTranslator.Plugin.Core.Batching;
 using Harmony;
 using XUnity.AutoTranslator.Plugin.Core.Parsing;
 using System.Diagnostics;
+using XUnity.AutoTranslator.Plugin.Core.UI;
+using XUnity.AutoTranslator.Plugin.Core.Endpoints;
 
 namespace XUnity.AutoTranslator.Plugin.Core
 {
@@ -41,6 +42,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// </summary>
       public static AutoTranslationPlugin Current;
 
+
+      private XuaWindow _window;
+
       /// <summary>
       /// These are the currently running translation jobs (being translated by an http request).
       /// </summary>
@@ -98,7 +102,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private Component _advEngine;
       private float? _nextAdvUpdate;
 
-      private IKnownEndpoint _endpoint;
+      private ServiceEndpointConfiguration _servicePoints;
+      private List<ConfiguredEndpoint> _configuredEndpoints;
+      private ConfiguredEndpoint _endpoint;
 
       private int[] _currentTranslationsQueuedPerSecondRollingWindow = new int[ Settings.TranslationQueueWatchWindow ];
       private float? _timeExceededThreshold;
@@ -146,7 +152,6 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
             Logger.Current.Error( e, "An error occurred during configuration. Shutting plugin down." );
 
-            _endpoint = null;
             Settings.IsShutdown = true;
             Settings.IsShutdownFatal = true;
 
@@ -159,16 +164,55 @@ namespace XUnity.AutoTranslator.Plugin.Core
          HooksSetup.InstallImageHooks();
          HooksSetup.InstallTextGetterCompatHooks();
 
+         _servicePoints = new ServiceEndpointConfiguration();
+         try
+         {
+            _configuredEndpoints = KnownEndpoints.CreateEndpoints( gameObject, _servicePoints )
+               .OrderBy( x => x.Endpoint.FriendlyName )
+               .ToList();
+         }
+         catch( Exception e )
+         {
+            Logger.Current.Error( e, "An error occurred while constructing endpoints. Shutting plugin down." );
+
+            Settings.IsShutdown = true;
+            Settings.IsShutdownFatal = true;
+
+            return;
+         }
+
          try
          {
-            _endpoint = KnownEndpoints.FindEndpoint( Settings.ServiceEndpoint );
+            var primaryEndpoint = _configuredEndpoints.FirstOrDefault( x => x.Endpoint.Id == Settings.ServiceEndpoint );
+
+            if( primaryEndpoint == null ) throw new Exception( "The primary endpoint was not properly configured." );
+            if( primaryEndpoint.Error != null ) throw new Exception( "The primary endpoint was not properly configured.", primaryEndpoint.Error );
+
+            _endpoint = primaryEndpoint;
          }
          catch( Exception e )
          {
             Logger.Current.Error( e, "An unexpected error occurred during initialization of endpoint." );
          }
 
-         if( !TextHelper.IsFromLanguageSupported( Settings.FromLanguage ) )
+         // TODO: Perhaps some bleeding edge check to see if this is required?
+         var callback = _servicePoints.GetCertificateValidationCheck();
+         if( callback != null )
+         {
+            ServicePointManager.ServerCertificateValidationCallback += callback;
+         }
+
+         // Save again because configuration may be modified by endpoints
+         try
+         {
+            Config.Current.SaveConfig();
+         }
+         catch( Exception e )
+         {
+            Logger.Current.Error( e, "An error occurred during while saving configuration." );
+         }
+
+         if( !LanguageHelper.IsFromLanguageSupported( Settings.FromLanguage ) )
          {
             Logger.Current.Error( $"The plugin has been configured to use the 'FromLanguage={Settings.FromLanguage}'. This language is not supported. Shutting plugin down." );
 
@@ -177,7 +221,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             Settings.IsShutdownFatal = true;
          }
 
-         _symbolCheck = TextHelper.GetSymbolCheck( Settings.FromLanguage );
+         _symbolCheck = LanguageHelper.GetSymbolCheck( Settings.FromLanguage );
 
          if( !string.IsNullOrEmpty( Settings.OverrideFont ) )
          {
@@ -207,6 +251,34 @@ namespace XUnity.AutoTranslator.Plugin.Core
          LoadTranslations();
          LoadStaticTranslations();
 
+         _window = new XuaWindow(
+            new List<ToggleViewModel>
+            {
+               new ToggleViewModel(
+                  " Translated",
+                  "<b>TRANSLATED</b>\nThe plugin currently displays translated texts. Disabling this does not mean the plugin will no longer perform translations, just that they will not be displayed.",
+                  "<b>NOT TRANSLATED</b>\nThe plugin currently displays untranslated texts.",
+                  ToggleTranslation, () => _isInTranslatedMode )
+            },
+            _configuredEndpoints.Select( x =>
+               new TranslatorDropdownOptionViewModel( () => x == _endpoint, x, OnEndpointSelected ) ).ToList(),
+            new List<ButtonViewModel>
+            {
+               new ButtonViewModel( "Reboot", "<b>REBOOT PLUGIN</b>\nReboots the plugin if it has been shutdown. This only works if the plugin was shut down due to consequtive errors towards the translation endpoint.", RebootPlugin, () => Settings.IsShutdown && !Settings.IsShutdownFatal ),
+               new ButtonViewModel( "Reload", "<b>RELOAD TRANSLATION</b>\nReloads all translation text files and texture files from disk.", ReloadTranslations, null ),
+               new ButtonViewModel( "Hook", "<b>MANUAL HOOK</b>\nTraverses the unity object tree for looking for anything that can be translated. Performs a translation if something is found.", ManualHook, null )
+            },
+            new List<LabelViewModel>
+            {
+               new LabelViewModel( "Version: ", () => PluginData.Version ),
+               new LabelViewModel( "Status: ", () => Settings.IsShutdown ? "Shutdown" : "Running" ),
+               new LabelViewModel( "Served translations: ", () => $"{Settings.TranslationCount} / {Settings.MaxTranslationsBeforeShutdown}" ),
+               new LabelViewModel( "Queued translations: ", () => $"{(_unstartedJobs.Count + _ongoingJobs.Count)} / {Settings.MaxUnstartedJobs}"  ),
+               new LabelViewModel( "Error'ed translations: ", () => $"{_consecutiveErrors} / {Settings.MaxErrors}"  ),
+            } );
+
+         UnityTextParsers.Initialize( text => IsTranslatable( text ) && IsBelowMaxLength( text ) );
+
          // start a thread that will periodically removed unused references
          var t1 = new Thread( MaintenanceLoop );
          t1.IsBackground = true;
@@ -218,15 +290,25 @@ namespace XUnity.AutoTranslator.Plugin.Core
          t2.Start();
       }
 
+      private void OnEndpointSelected( ConfiguredEndpoint endpoint )
+      {
+         _endpoint = endpoint;
+         if( Settings.IsShutdown && !Settings.IsShutdownFatal )
+         {
+            RebootPlugin();
+            ManualHook();
+         }
+      }
+
       private IEnumerable<string> GetTranslationFiles()
       {
-         return Directory.GetFiles( Path.Combine( Config.Current.DataPath, Settings.TranslationDirectory ), $"*.txt", SearchOption.AllDirectories )
+         return Directory.GetFiles( Path.Combine( Config.Current.DataPath, Settings.TranslationDirectory ).Parameterize(), $"*.txt", SearchOption.AllDirectories )
             .Select( x => x.Replace( "/", "\\" ) );
       }
 
       private IEnumerable<string> GetTextureFiles()
       {
-         return Directory.GetFiles( Path.Combine( Config.Current.DataPath, Settings.TextureDirectory ), $"*.png", SearchOption.AllDirectories )
+         return Directory.GetFiles( Path.Combine( Config.Current.DataPath, Settings.TextureDirectory ).Parameterize(), $"*.png", SearchOption.AllDirectories )
             .Select( x => x.Replace( "/", "\\" ) );
       }
 
@@ -236,7 +318,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
             try
             {
-               ObjectExtensions.Cull();
+               ObjectReferenceMapper.Cull();
             }
             catch( Exception e )
             {
@@ -284,7 +366,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      public void EnableSceneLoadScan()
+      private void EnableSceneLoadScan()
       {
          Logger.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.SupportsScenes )
@@ -298,13 +380,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      public void EnableSceneLoadScanInternal()
+      private void EnableSceneLoadScanInternal()
       {
          // do this in a different class to avoid having an anonymous method with references to the "Scene" class
          SceneManagerLoader.EnableSceneLoadScanInternal( this );
       }
 
-      public void OnLevelWasLoadedFromSceneManager( int id )
+      internal void OnLevelWasLoadedFromSceneManager( int id )
       {
          try
          {
@@ -343,10 +425,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
             lock( _writeToFileSync )
             {
-               Directory.CreateDirectory( Path.Combine( Config.Current.DataPath, Settings.TranslationDirectory ) );
-               Directory.CreateDirectory( Path.GetDirectoryName( Path.Combine( Config.Current.DataPath, Settings.OutputFile ) ) );
+               Directory.CreateDirectory( Path.Combine( Config.Current.DataPath, Settings.TranslationDirectory ).Parameterize() );
+               Directory.CreateDirectory( Path.GetDirectoryName( Settings.AutoTranslationsFilePath ) );
 
-               var mainTranslationFile = Settings.AutoTranslationsFilePath.Replace( "/", "\\" );
+               var mainTranslationFile = Settings.AutoTranslationsFilePath;
                LoadTranslationsInFile( mainTranslationFile );
                foreach( var fullFileName in GetTranslationFiles().Reverse().Except( new[] { mainTranslationFile } ) )
                {
@@ -358,7 +440,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             {
                _translatedImages.Clear();
                _untranslatedImages.Clear();
-               Directory.CreateDirectory( Path.Combine( Config.Current.DataPath, Settings.TextureDirectory ) );
+               Directory.CreateDirectory( Path.Combine( Config.Current.DataPath, Settings.TextureDirectory ).Parameterize() );
                foreach( var fullFileName in GetTextureFiles() )
                {
                   RegisterImageFromFile( fullFileName );
@@ -439,7 +521,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private void RegisterImageFromData( string textureName, string key, byte[] data )
       {
          var name = textureName.SanitizeForFileSystem();
-         var root = Path.Combine( Config.Current.DataPath, Settings.TextureDirectory );
+         var root = Path.Combine( Config.Current.DataPath, Settings.TextureDirectory ).Parameterize();
          var originalHash = HashHelper.Compute( data );
 
          // allow hash and key to be the same; only store one of them then!
@@ -654,7 +736,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          _frameForLastQueuedTranslation = currentFrame;
       }
 
-      public void PeriodicResetFrameCheck()
+      private void PeriodicResetFrameCheck()
       {
          var currentSecond = (int)Time.time;
          if( currentSecond % 100 == 0 )
@@ -897,12 +979,12 @@ namespace XUnity.AutoTranslator.Plugin.Core
          return result;
       }
 
-      public bool TryGetReverseTranslation( string value, out string key )
+      internal bool TryGetReverseTranslation( string value, out string key )
       {
          return _reverseTranslations.TryGetValue( value, out key );
       }
 
-      public string Hook_TextChanged_WithResult( object ui, string text )
+      internal string Hook_TextChanged_WithResult( object ui, string text )
       {
          if( !ui.IsKnownTextType() ) return null;
 
@@ -913,7 +995,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          return null;
       }
 
-      public string ExternalHook_TextChanged_WithResult( object ui, string text )
+      internal string ExternalHook_TextChanged_WithResult( object ui, string text )
       {
          if( !ui.IsKnownTextType() ) return null;
 
@@ -924,7 +1006,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          return null;
       }
 
-      public void Hook_TextChanged( object ui, bool onEnable )
+      internal void Hook_TextChanged( object ui, bool onEnable )
       {
          if( _textHooksEnabled && !_temporarilyDisabled )
          {
@@ -937,7 +1019,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      public void Hook_ImageChangedOnComponent( object source, Texture2D texture, bool isPrefixHooked, bool onEnable )
+      internal void Hook_ImageChangedOnComponent( object source, Texture2D texture, bool isPrefixHooked, bool onEnable )
       {
          if( !_imageHooksEnabled ) return;
          if( !source.IsKnownImageType() ) return;
@@ -950,7 +1032,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      public void Hook_ImageChanged( Texture2D texture, bool isPrefixHooked )
+      internal void Hook_ImageChanged( Texture2D texture, bool isPrefixHooked )
       {
          if( !_imageHooksEnabled ) return;
          if( texture == null ) return;
@@ -968,7 +1050,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      public void Hook_HandleComponent( object ui )
+      internal void Hook_HandleComponent( object ui )
       {
          if( _hasOverrideFont )
          {
@@ -1076,17 +1158,22 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private bool IsTranslatable( string str )
       {
          return _symbolCheck( str )
-            && str.Length <= Settings.MaxCharactersPerTranslation
+            //&& str.Length <= Settings.MaxCharactersPerTranslation
             && !_reverseTranslations.ContainsKey( str )
             && !Settings.IgnoreTextStartingWith.Any( x => str.StartsWithStrict( x ) );
       }
 
-      private bool IsShortText( string str )
+      private bool IsBelowMaxLength( string str )
+      {
+         return str.Length <= Settings.MaxCharactersPerTranslation;
+      }
+
+      private bool IsBelowMaxLengthStrict( string str )
       {
          return str.Length <= ( Settings.MaxCharactersPerTranslation / 2 );
       }
 
-      public bool ShouldTranslateImageComponent( object ui )
+      private bool ShouldTranslateImageComponent( object ui )
       {
          var component = ui as Component;
          if( component != null )
@@ -1109,7 +1196,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          return true;
       }
 
-      public bool ShouldTranslateTextComponent( object ui, bool ignoreComponentState )
+      private bool ShouldTranslateTextComponent( object ui, bool ignoreComponentState )
       {
          var component = ui as Component;
          if( component != null )
@@ -1166,7 +1253,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          return null;
       }
 
-      public static bool IsCurrentlySetting( TextTranslationInfo info )
+      private static bool IsCurrentlySetting( TextTranslationInfo info )
       {
          if( info == null ) return false;
 
@@ -1435,6 +1522,22 @@ namespace XUnity.AutoTranslator.Plugin.Core
                   return translation;
                }
             }
+            else
+            {
+               if( UnityTextParsers.GameLogTextParser.CanApply( ui ) )
+               {
+                  var result = UnityTextParsers.GameLogTextParser.Parse( text );
+                  if( result.Succeeded )
+                  {
+                     translation = TranslateOrQueueWebJobImmediateByParserResult( ui, result, false );
+                     if( translation != null )
+                     {
+                        SetTranslatedText( ui, translation, info );
+                        return translation;
+                     }
+                  }
+               }
+            }
          }
 
          return null;
@@ -1482,13 +1585,25 @@ namespace XUnity.AutoTranslator.Plugin.Core
             }
             else
             {
-               if( context == null && ui.SupportsRichText() )
+               if( context == null )
                {
-                  var parser = UnityTextParsers.GetTextParserByGameEngine();
-                  if( parser != null )
+                  if( UnityTextParsers.GameLogTextParser.CanApply( ui ) )
                   {
-                     var result = parser.Parse( text );
-                     if( result.HasRichSyntax )
+                     var result = UnityTextParsers.GameLogTextParser.Parse( text );
+                     if( result.Succeeded )
+                     {
+                        translation = TranslateOrQueueWebJobImmediateByParserResult( ui, result, false );
+                        if( translation != null )
+                        {
+                           SetTranslatedText( ui, translation, info );
+                           return translation;
+                        }
+                     }
+                  }
+                  else if( UnityTextParsers.RichTextParser.CanApply( ui ) && IsBelowMaxLength( text ) )
+                  {
+                     var result = UnityTextParsers.RichTextParser.Parse( text );
+                     if( result.Succeeded )
                      {
                         var isWhitelisted = ui.IsWhitelistedForImmediateRichTextTranslation();
 
@@ -1558,13 +1673,26 @@ namespace XUnity.AutoTranslator.Plugin.Core
                                  }
                                  else
                                  {
-                                    if( context == null && ui.SupportsRichText() )
+                                    if( context == null )
                                     {
-                                       var parser = UnityTextParsers.GetTextParserByGameEngine();
-                                       if( parser != null )
+                                       if( UnityTextParsers.GameLogTextParser.CanApply( ui ) )
+                                       {
+                                          var result = UnityTextParsers.GameLogTextParser.Parse( stabilizedText );
+                                          if( result.Succeeded )
+                                          {
+                                             var translatedText = TranslateOrQueueWebJobImmediateByParserResult( ui, result, true );
+                                             if( translatedText != null )
+                                             {
+                                                // stabilized, no need to untemplate
+                                                SetTranslatedText( ui, translatedText, info );
+                                             }
+                                             return;
+                                          }
+                                       }
+                                       else if( UnityTextParsers.RichTextParser.CanApply( ui ) && IsBelowMaxLength( stabilizedText ) )
                                        {
-                                          var result = parser.Parse( stabilizedText );
-                                          if( result.HasRichSyntax )
+                                          var result = UnityTextParsers.RichTextParser.Parse( stabilizedText );
+                                          if( result.Succeeded )
                                           {
                                              var translatedText = TranslateOrQueueWebJobImmediateByParserResult( ui, result, true );
                                              if( translatedText != null )
@@ -1580,10 +1708,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
                                     // Lets try not to spam a service that might not be there...
                                     if( _endpoint != null )
                                     {
-                                       if( !Settings.IsShutdown )
+                                       if( IsBelowMaxLength( stabilizedText ) )
                                        {
-                                          var job = GetOrCreateTranslationJobFor( ui, stabilizedTextKey, context );
-                                          job.Components.Add( ui );
+                                          if( !Settings.IsShutdown )
+                                          {
+                                             var job = GetOrCreateTranslationJobFor( ui, stabilizedTextKey, context );
+                                             job.Components.Add( ui );
+                                          }
                                        }
                                     }
                                     else
@@ -1600,7 +1731,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                      _ongoingOperations.Remove( ui );
                   }
                }
-               else if( !isSpammer || ( isSpammer && IsShortText( text ) ) )
+               else if( !isSpammer || ( isSpammer && IsBelowMaxLengthStrict( text ) ) )
                {
                   if( context != null )
                   {
@@ -1661,7 +1792,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
             var key = kvp.Key;
             var value = kvp.Value.TrimIfConfigured();
-            if( !string.IsNullOrEmpty( value ) && IsTranslatable( value ) )
+            if( !string.IsNullOrEmpty( value ) && IsTranslatable( value ) && IsBelowMaxLength( value ) )
             {
                string partTranslation;
                if( TryGetTranslation( value, out partTranslation ) )
@@ -1697,7 +1828,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// the text has stopped changing. This is important for 'story'
       /// mode text, which 'scrolls' into place slowly.
       /// </summary>
-      public IEnumerator WaitForTextStablization( object ui, float delay, int maxTries, int currentTries, Action<string> onTextStabilized, Action onMaxTriesExceeded )
+      private IEnumerator WaitForTextStablization( object ui, float delay, int maxTries, int currentTries, Action<string> onTextStabilized, Action onMaxTriesExceeded )
       {
          yield return 0; // wait a single frame to allow any external plugins to complete their hooking logic
 
@@ -1733,7 +1864,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// for global text, where the component cannot tell us if the text
       /// has changed itself.
       /// </summary>
-      public IEnumerator WaitForTextStablization( TranslationKey textKey, float delay, Action onTextStabilized, Action onFailed = null )
+      private IEnumerator WaitForTextStablization( TranslationKey textKey, float delay, Action onTextStabilized, Action onFailed = null )
       {
          var text = textKey.GetDictionaryLookupKey();
 
@@ -1773,13 +1904,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      public IEnumerator DelayForSeconds( float delay, Action onContinue )
+      private IEnumerator DelayForSeconds( float delay, Action onContinue )
       {
          yield return new WaitForSeconds( delay );
          onContinue();
       }
 
-      public void Awake()
+      void Awake()
       {
          if( !_initialized )
          {
@@ -1797,7 +1928,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      public void Start()
+      void Start()
       {
          try
          {
@@ -1809,13 +1940,14 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      public void Update()
+      void Update()
       {
          try
          {
-            if( _endpoint != null )
+            // perform this check every 100 frames!
+            if( Time.frameCount % 100 == 0 )
             {
-               _endpoint.OnUpdate();
+               ConnectionTrackingWebClient.CheckServicePoints();
             }
 
             if( Features.SupportsClipboard )
@@ -1871,6 +2003,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
                {
                   RebootPlugin();
                }
+               else if( isAltPressed && Input.GetKeyDown( KeyCode.X ) )
+               {
+                  _window.IsShown = !_window.IsShown;
+               }
             }
          }
          catch( Exception e )
@@ -1879,6 +2015,20 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
+      void OnGUI()
+      {
+         try
+         {
+            DisableAutoTranslator();
+
+            if( _window.IsShown ) _window.OnGUI();
+         }
+         finally
+         {
+            EnableAutoTranslator();
+         }
+      }
+
       private void RebootPlugin()
       {
          if( Settings.IsShutdown )
@@ -1904,7 +2054,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          if( _endpoint == null ) return;
 
-         if( Settings.EnableBatching && _endpoint.SupportsLineSplitting && !_batchLogicHasFailed && _unstartedJobs.Count > 1 && _availableBatchOperations > 0 )
+         if( Settings.EnableBatching && _endpoint.Endpoint.SupportsLineSplitting() && !_batchLogicHasFailed && _unstartedJobs.Count > 1 && _availableBatchOperations > 0 )
          {
             while( _unstartedJobs.Count > 0 && _availableBatchOperations > 0 )
             {
@@ -1929,8 +2079,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
                {
                   _availableBatchOperations--;
 
-                  StartCoroutine( _endpoint.Translate( batch.GetFullTranslationKey(), Settings.FromLanguage, Settings.Language, translatedText => OnBatchTranslationCompleted( batch, translatedText ),
-                  () => OnTranslationFailed( batch ) ) );
+                  var untranslatedText = batch.GetFullTranslationKey();
+                  Logger.Current.Debug( "Starting translation for: " + untranslatedText );
+                  StartCoroutine( _endpoint.Translate( untranslatedText, Settings.FromLanguage, Settings.Language, translatedText => OnBatchTranslationCompleted( batch, translatedText ),
+                  ( msg, e ) => OnTranslationFailed( batch, msg, e ) ) );
                }
             }
          }
@@ -1949,8 +2101,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
                _ongoingJobs[ key ] = job;
 
-               StartCoroutine( _endpoint.Translate( job.Key.GetDictionaryLookupKey(), Settings.FromLanguage, Settings.Language, translatedText => OnSingleTranslationCompleted( job, translatedText ),
-               () => OnTranslationFailed( job ) ) );
+               var untranslatedText = job.Key.GetDictionaryLookupKey();
+               Logger.Current.Debug( "Starting translation for: " + untranslatedText );
+               StartCoroutine( _endpoint.Translate( untranslatedText, Settings.FromLanguage, Settings.Language, translatedText => OnSingleTranslationCompleted( job, translatedText ),
+               ( msg, e ) => OnTranslationFailed( job, msg, e ) ) );
             }
          }
 
@@ -1962,9 +2116,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
          _kickedOff.Clear();
       }
 
-      public void OnBatchTranslationCompleted( TranslationBatch batch, string translatedTextBatch )
+      private void OnBatchTranslationCompleted( TranslationBatch batch, string translatedTextBatch )
       {
          _consecutiveErrors = 0;
+         Logger.Current.Debug( $"Translation for '{batch.GetFullTranslationKey()}' succeded. Result: {translatedTextBatch}" );
 
          var succeeded = batch.MatchWithTranslations( translatedTextBatch );
          if( succeeded )
@@ -2029,6 +2184,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private void OnSingleTranslationCompleted( TranslationJob job, string translatedText )
       {
          Settings.TranslationCount++;
+         Logger.Current.Debug( $"Translation for '{job.Key.GetDictionaryLookupKey()}' succeded. Result: {translatedText}" );
 
          _consecutiveErrors = 0;
 
@@ -2063,8 +2219,17 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      private void OnTranslationFailed( TranslationJob job )
+      private void OnTranslationFailed( TranslationJob job, string error, Exception e )
       {
+         if( e == null )
+         {
+            Logger.Current.Error( error );
+         }
+         else
+         {
+            Logger.Current.Error( e, error );
+         }
+
          Settings.TranslationCount++; // counts as a translation
          _consecutiveErrors++;
 
@@ -2085,8 +2250,17 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      private void OnTranslationFailed( TranslationBatch batch )
+      private void OnTranslationFailed( TranslationBatch batch, string error, Exception e )
       {
+         if( e == null )
+         {
+            Logger.Current.Error( error );
+         }
+         else
+         {
+            Logger.Current.Error( e, error );
+         }
+
          Settings.TranslationCount++; // counts as a translation
          _consecutiveErrors++;
 
@@ -2153,7 +2327,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
                         if( !string.IsNullOrEmpty( translatedText ) )
                         {
-                           if( !_translations.ContainsKey( context.Result.OriginalText ) )
+                           if( result.PersistCombinedResult && !_translations.ContainsKey( context.Result.OriginalText ) )
                            {
                               AddTranslation( context.Result.OriginalText, translatedText );
                               QueueNewTranslationForDisk( context.Result.OriginalText, translatedText );
@@ -2206,7 +2380,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          LoadTranslations();
 
          var context = new TextureReloadContext();
-         foreach( var kvp in ObjectExtensions.GetAllRegisteredObjects() )
+         foreach( var kvp in ObjectReferenceMapper.GetAllRegisteredObjects() )
          {
             var ui = kvp.Key;
             try
@@ -2235,7 +2409,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             catch( Exception )
             {
                // not super pretty, no...
-               ObjectExtensions.Remove( ui );
+               ObjectReferenceMapper.Remove( ui );
             }
          }
       }
@@ -2278,7 +2452,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
             _overrideFont = !_overrideFont;
 
-            var objects = ObjectExtensions.GetAllRegisteredObjects();
+            var objects = ObjectReferenceMapper.GetAllRegisteredObjects();
             Logger.Current.Info( $"Toggling fonts of {objects.Count} objects." );
 
             if( _overrideFont )
@@ -2300,7 +2474,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                      catch( Exception )
                      {
                         // not super pretty, no...
-                        ObjectExtensions.Remove( ui );
+                        ObjectReferenceMapper.Remove( ui );
                      }
                   }
                }
@@ -2322,7 +2496,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                   catch( Exception )
                   {
                      // not super pretty, no...
-                     ObjectExtensions.Remove( ui );
+                     ObjectReferenceMapper.Remove( ui );
                   }
                }
             }
@@ -2332,7 +2506,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private void ToggleTranslation()
       {
          _isInTranslatedMode = !_isInTranslatedMode;
-         var objects = ObjectExtensions.GetAllRegisteredObjects();
+         var objects = ObjectReferenceMapper.GetAllRegisteredObjects();
 
          Logger.Current.Info( $"Toggling translations of {objects.Count} objects." );
 
@@ -2364,7 +2538,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                catch( Exception )
                {
                   // not super pretty, no...
-                  ObjectExtensions.Remove( ui );
+                  ObjectReferenceMapper.Remove( ui );
                }
             }
          }
@@ -2396,7 +2570,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                catch( Exception )
                {
                   // not super pretty, no...
-                  ObjectExtensions.Remove( ui );
+                  ObjectReferenceMapper.Remove( ui );
                }
             }
          }
@@ -2561,6 +2735,23 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          _temporarilyDisabled = false;
       }
+
+      void OnApplicationQuit()
+      {
+         if( _configuredEndpoints == null ) return;
+
+         foreach( var ce in _configuredEndpoints )
+         {
+            try
+            {
+               if( ce.Endpoint is IDisposable disposable ) disposable.Dispose();
+            }
+            catch( Exception e )
+            {
+               Logger.Current.Error( e, "An error occurred while disposing endpoint." );
+            }
+         }
+      }
    }
 
    internal static class SceneManagerLoader

+ 19 - 0
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationState.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+
+namespace XUnity.AutoTranslator.Plugin.Core
+{
+   public static class AutoTranslationState
+   {
+      public static int TranslationCount
+      {
+         get
+         {
+            return Settings.TranslationCount;
+         }
+      }
+   }
+}

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/Batching/TranslationBatch.cs

@@ -3,7 +3,7 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Batching
 {
-   public class TranslationBatch
+   internal class TranslationBatch
    {
       public TranslationBatch()
       {

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/Batching/TranslationLineTracker.cs

@@ -2,7 +2,7 @@
 
 namespace XUnity.AutoTranslator.Plugin.Core.Batching
 {
-   public class TranslationLineTracker
+   internal class TranslationLineTracker
    {
       public TranslationLineTracker( TranslationJob job )
       {

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

@@ -6,7 +6,7 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Configuration
 {
-   public static class Config
+   internal static class Config
    {
       public static IConfiguration Current;
    }

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

@@ -7,7 +7,7 @@ using ExIni;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Configuration
 {
-   public class DefaultConfiguration : IConfiguration
+   internal class DefaultConfiguration : IConfiguration
    {
       private IniFile _file;
       private string _configPath;

+ 0 - 2
src/XUnity.AutoTranslator.Plugin.Core/Configuration/IConfiguration.cs

@@ -13,7 +13,5 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       IniFile Preferences { get; }
 
       void SaveConfig();
-
-      IniFile ReloadConfig();
    }
 }

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

@@ -11,7 +11,7 @@ using XUnity.AutoTranslator.Plugin.Core.Utilities;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Configuration
 {
-   public static class Settings
+   internal static class Settings
    {
       // cannot be changed
       public static readonly int MaxMaxCharactersPerTranslation = 500;
@@ -23,12 +23,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static readonly int MaxTranslationsBeforeShutdown = 8000;
       public static readonly int MaxUnstartedJobs = 3500;
       public static readonly float IncreaseBatchOperationsEvery = 30;
-      public static readonly bool EnableObjectTracking = true;
       public static readonly int MaximumStaggers = 6;
       public static readonly int PreviousTextStaggerCount = 3;
       public static readonly int MaximumConsecutiveFramesTranslated = 90;
       public static readonly int MaximumConsecutiveSecondsTranslated = 60;
       public static bool UsesWhitespaceBetweenWords = false;
+      public static string ApplicationName;
 
 
       public static bool IsShutdown = false;
@@ -63,20 +63,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static bool IgnoreWhitespaceInDialogue;
       public static bool IgnoreWhitespaceInNGUI;
       public static int MinDialogueChars;
-      public static string BaiduAppId;
-      public static string BaiduAppSecret;
-      public static string YandexAPIKey;
-      public static string WatsonAPIUrl;
-      public static string WatsonAPIUsername;
-      public static string WatsonAPIPassword;
-      public static string BingOcpApimSubscriptionKey;
       public static int ForceSplitTextAfterCharacters;
       public static bool EnableMigrations;
       public static string MigrationsTag;
       public static bool EnableBatching;
       public static bool TrimAllText;
       public static bool EnableUIResizing;
-      public static string GoogleAPIKey;
       public static bool UseStaticTranslations;
       public static string OverrideFont;
       public static string UserAgent;
@@ -84,6 +76,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static float? ResizeUILineSpacingScale;
       public static bool ForceUIResizing;
       public static string[] IgnoreTextStartingWith;
+      public static HashSet<string> GameLogTextPaths;
       public static bool TextGetterCompatibilityMode;
 
       public static string TextureDirectory;
@@ -103,18 +96,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       {
          try
          {
-            // clear configuration from old versions...
-            var section = Config.Current.Preferences[ "AutoTranslator" ];
-            foreach( var key in section.Keys.ToList() )
-            {
-               section.DeleteKey( key.Key );
-            }
-
-            Config.Current.Preferences.DeleteSection( "AutoTranslator" );
-            Config.Current.Preferences[ "Service" ].DeleteKey( "EnableSSL" );
+            ApplicationName = Path.GetFileNameWithoutExtension( ApplicationInformation.StartupPath );
+         }
+         catch( Exception e )
+         {
+            Logger.Current.Error( e, "An error occurred while getting application name." );
          }
-         catch { }
-
 
 
          ServiceEndpoint = Config.Current.Preferences[ "Service" ][ "Endpoint" ].GetOrDefault( KnownEndpointNames.GoogleTranslate, true );
@@ -150,6 +137,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
          IgnoreTextStartingWith = Config.Current.Preferences[ "Behaviour" ][ "IgnoreTextStartingWith" ].GetOrDefault( "\\u180e;", true )
             ?.Split( new[] { ';' }, StringSplitOptions.RemoveEmptyEntries ).Select( x => x.UnescapeJson() ).ToArray() ?? new string[ 0 ];
          TextGetterCompatibilityMode = Config.Current.Preferences[ "Behaviour" ][ "TextGetterCompatibilityMode" ].GetOrDefault( false );
+         GameLogTextPaths = Config.Current.Preferences[ "Behaviour" ][ "GameLogTextPaths" ].GetOrDefault( "", true )
+            ?.Split( new[] { ';' }, StringSplitOptions.RemoveEmptyEntries ).ToHashSet() ?? new HashSet<string>();
+         GameLogTextPaths.RemoveWhere( x => !x.StartsWith( "/" ) ); // clean up to ensure no 'empty' entries
 
          TextureDirectory = Config.Current.Preferences[ "Texture" ][ "TextureDirectory" ].GetOrDefault( @"Translation\Texture" );
          EnableTextureTranslation = Config.Current.Preferences[ "Texture" ][ "EnableTextureTranslation" ].GetOrDefault( false );
@@ -181,19 +171,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
 
          UserAgent = Config.Current.Preferences[ "Http" ][ "UserAgent" ].GetOrDefault( string.Empty );
 
-         GoogleAPIKey = Config.Current.Preferences[ "GoogleLegitimate" ][ "GoogleAPIKey" ].GetOrDefault( "" );
-
-         BingOcpApimSubscriptionKey = Config.Current.Preferences[ "BingLegitimate" ][ "OcpApimSubscriptionKey" ].GetOrDefault( "" );
-
-         BaiduAppId = Config.Current.Preferences[ "Baidu" ][ "BaiduAppId" ].GetOrDefault( "" );
-         BaiduAppSecret = Config.Current.Preferences[ "Baidu" ][ "BaiduAppSecret" ].GetOrDefault( "" );
-
-         YandexAPIKey = Config.Current.Preferences[ "Yandex" ][ "YandexAPIKey" ].GetOrDefault( "" );
-
-         WatsonAPIUrl = Config.Current.Preferences[ "Watson" ][ "WatsonAPIUrl" ].GetOrDefault( "" );
-         WatsonAPIUsername = Config.Current.Preferences[ "Watson" ][ "WatsonAPIUsername" ].GetOrDefault( "" );
-         WatsonAPIPassword = Config.Current.Preferences[ "Watson" ][ "WatsonAPIPassword" ].GetOrDefault( "" );
-
          EnablePrintHierarchy = Config.Current.Preferences[ "Debug" ][ "EnablePrintHierarchy" ].GetOrDefault( false );
          EnableConsole = Config.Current.Preferences[ "Debug" ][ "EnableConsole" ].GetOrDefault( false );
          EnableDebugLogs = Config.Current.Preferences[ "Debug" ][ "EnableLog" ].GetOrDefault( false );
@@ -201,8 +178,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
          EnableMigrations = Config.Current.Preferences[ "Migrations" ][ "Enable" ].GetOrDefault( true );
          MigrationsTag = Config.Current.Preferences[ "Migrations" ][ "Tag" ].GetOrDefault( string.Empty );
 
-         AutoTranslationsFilePath = Path.Combine( Config.Current.DataPath, OutputFile.Replace( "{lang}", Language ) );
-         UsesWhitespaceBetweenWords = TextHelper.RequiresWhitespaceUponLineMerging( FromLanguage );
+         AutoTranslationsFilePath = Path.Combine( Config.Current.DataPath, OutputFile.Replace( "{lang}", Language ) ).Replace( "/", "\\" ).Parameterize();
+         UsesWhitespaceBetweenWords = LanguageHelper.RequiresWhitespaceUponLineMerging( FromLanguage );
 
          if( EnableMigrations )
          {
@@ -214,20 +191,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
 
          Config.Current.SaveConfig();
       }
-
+      
       private static void Migrate()
       {
-         var currentTag = MigrationsTag;
-         var newTag = PluginData.Version;
-
-         // migrate from unknown version to known version. Reset to google translate
-         if( string.IsNullOrEmpty( currentTag ) )
-         {
-            if( ServiceEndpoint == KnownEndpointNames.GoogleTranslateHack )
-            {
-               ServiceEndpoint = Config.Current.Preferences[ "Service" ][ "Endpoint" ].Value = KnownEndpointNames.GoogleTranslate;
-            }
-         }
       }
 
       public static string GetUserAgent( string defaultUserAgent )

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/ConsoleLogger.cs

@@ -2,7 +2,7 @@
 
 namespace XUnity.AutoTranslator.Plugin.Core
 {
-   public class ConsoleLogger : Logger
+   internal class ConsoleLogger : Logger
    {
       protected override void Log( LogLevel level, string message )
       {

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

@@ -5,24 +5,8 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Constants
 {
-   public static class KnownEndpointNames
+   internal static class KnownEndpointNames
    {
       public const string GoogleTranslate = "GoogleTranslate";
-
-      public const string GoogleTranslateHack = "GoogleTranslateHack";
-
-      public const string GoogleTranslateLegitimate = "GoogleTranslateLegitimate";
-
-      public const string BaiduTranslate = "BaiduTranslate";
-
-      public const string YandexTranslate = "YandexTranslate";
-
-      public const string WatsonTranslate = "WatsonTranslate";
-
-      public const string ExciteTranslate = "ExciteTranslate";
-
-      public const string BingTranslate = "BingTranslate";
-
-      public const string BingTranslateLegitimate = "BingTranslateLegitimate";
    }
 }

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

@@ -5,7 +5,7 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Constants
 {
-   public static class KnownEvents
+   internal static class KnownEvents
    {
       public static string OnUnableToTranslateUGUI = "OnUnableToTranslateUGUI";
       public static string OnUnableToTranslateTextMeshPro = "OnUnableToTranslateTextMeshPro";

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

@@ -5,7 +5,7 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Constants
 {
-   public static class KnownPlugins
+   internal static class KnownPlugins
    {
       public const string DynamicTranslationLoader = "com.bepis.bepinex.dynamictranslationloader";
    }

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/Debugging/DebugConsole.cs

@@ -6,7 +6,7 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Debugging
 {
-   public static class DebugConsole
+   internal static class DebugConsole
    {
       private static IntPtr _consoleOut;
 

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/Debugging/Kernel32.cs

@@ -6,7 +6,7 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Debugging
 {
-   public static class Kernel32
+   internal static class Kernel32
    {
       [DllImport( "kernel32.dll", SetLastError = true )]
       public static extern bool AllocConsole();

+ 43 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ConfiguredEndpoint.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Collections;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
+{
+   internal class ConfiguredEndpoint
+   {
+      private int _ongoingTranslations;
+
+      public ConfiguredEndpoint( ITranslateEndpoint endpoint, Exception error )
+      {
+         Endpoint = endpoint;
+         Error = error;
+         _ongoingTranslations = 0;
+      }
+
+      public ITranslateEndpoint Endpoint { get; }
+
+      public Exception Error { get; }
+
+      public bool IsBusy => _ongoingTranslations >= Endpoint.MaxConcurrency;
+
+      public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure )
+      {
+         _ongoingTranslations++;
+         try
+         {
+            var iterator = Endpoint.Translate( untranslatedText, from, to, success, failure );
+            if( iterator != null )
+            {
+               while( iterator.MoveNext() )
+               {
+                  yield return iterator.Current;
+               }
+            }
+         }
+         finally
+         {
+            _ongoingTranslations--;
+         }
+      }
+   }
+}

+ 17 - 9
src/XUnity.AutoTranslator.Plugin.Core/Web/BaiduTranslateEndpoint.cs → src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BaiduTranslateEndpoint.cs

@@ -9,10 +9,12 @@ using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Utilities;
+using XUnity.AutoTranslator.Plugin.Core.Web;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Web
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 {
-   public class BaiduTranslateEndpoint : KnownHttpEndpoint
+   internal class BaiduTranslateEndpoint : HttpEndpoint
    {
       private static readonly string HttpServicePointTemplateUrl = "http://api.fanyi.baidu.com/api/trans/vip/translate?q={0}&from={1}&to={2}&appid={3}&salt={4}&sign={5}";
       private static readonly MD5 HashMD5 = MD5.Create();
@@ -20,17 +22,23 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       private string _appId;
       private string _appSecret;
 
-      public BaiduTranslateEndpoint( string baiduAppId, string baiduAppSecret )
+      public BaiduTranslateEndpoint()
       {
-         if( string.IsNullOrEmpty( baiduAppId ) ) throw new ArgumentException( "The BaiduTranslate endpoint requires an App Id which has not been provided.", nameof( baiduAppId ) );
-         if( string.IsNullOrEmpty( baiduAppSecret ) ) throw new ArgumentException( "The BaiduTranslate endpoint requires an App Secret which has not been provided.", nameof( baiduAppSecret ) );
 
-         _appId = baiduAppId;
-         _appSecret = baiduAppSecret;
+      }
+
+      public override string Id => "BaiduTranslate";
 
-         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "api.fanyi.baidu.com" );
+      public override string FriendlyName => "Baidu Translator";
+
+      public override void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints )
+      {
+         _appId = configuration.Preferences[ "Baidu" ][ "BaiduAppId" ].GetOrDefault( "" );
+         _appSecret = configuration.Preferences[ "Baidu" ][ "BaiduAppSecret" ].GetOrDefault( "" );
+         if( string.IsNullOrEmpty( _appId ) ) throw new ArgumentException( "The BaiduTranslate endpoint requires an App Id which has not been provided." );
+         if( string.IsNullOrEmpty( _appSecret ) ) throw new ArgumentException( "The BaiduTranslate endpoint requires an App Secret which has not been provided." );
 
-         SetupServicePoints( "http://api.fanyi.baidu.com" );
+         servicePoints.EnableHttps( "api.fanyi.baidu.com" );
       }
 
       private static string CreateMD5( string input )

+ 19 - 13
src/XUnity.AutoTranslator.Plugin.Core/Web/BingTranslateEndpoint.cs → src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BingTranslateEndpoint.cs

@@ -12,10 +12,12 @@ using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Utilities;
+using XUnity.AutoTranslator.Plugin.Core.Web;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Web
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 {
-   public class BingTranslateEndpoint : KnownHttpEndpoint
+   internal class BingTranslateEndpoint : HttpEndpoint
    {
       private static readonly string HttpsServicePointTemplateUrl = "https://www.bing.com/ttranslate?&category=&IG={0}&IID={1}.{2}";
       private static readonly string HttpsServicePointTemplateUrlWithoutIG = "https://www.bing.com/ttranslate?&category=";
@@ -48,14 +50,18 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       public BingTranslateEndpoint()
       {
          _cookieContainer = new CookieContainer();
+      }
+
+      public override string Id => "BingTranslate";
+
+      public override string FriendlyName => "Bing Translator";
 
+      public override void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints )
+      {
          // Configure service points / service point manager
-         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "www.bing.com" );
-         SetupServicePoints( "https://www.bing.com" );
+         servicePoints.EnableHttps( "www.bing.com" );
       }
 
-      public override bool SupportsLineSplitting => false;
-
       public override void ApplyHeaders( WebHeaderCollection headers )
       {
          headers[ HttpRequestHeader.UserAgent ] = Settings.GetUserAgent( DefaultUserAgent );
@@ -86,9 +92,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          }
       }
 
-      public override IEnumerator OnBeforeTranslate( int translationCount )
+      public override IEnumerator OnBeforeTranslate()
       {
-         if( !_hasSetup || translationCount % _resetAfter == 0 )
+         if( !_hasSetup || AutoTranslationState.TranslationCount % _resetAfter == 0 )
          {
             _resetAfter = RandomNumbers.Next( 75, 125 );
             _hasSetup = true;
@@ -105,19 +111,19 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       public IEnumerator SetupIGAndIID()
       {
          string error = null;
-         DownloadResult downloadResult = null;
+         UnityWebResponse downloadResult = null;
 
          _cookieContainer = new CookieContainer();
          _translationCount = 0;
 
-         var client = GetClient();
+         var client = Client;
          try
          {
             ApplyHeaders( client.Headers );
             client.Headers.Remove( HttpRequestHeader.Referer );
             client.Headers.Remove( "Origin" );
             client.Headers.Remove( HttpRequestHeader.ContentType );
-            downloadResult = client.GetDownloadResult( new Uri( HttpsTranslateUserSite ) );
+            downloadResult = client.DownloadStringByUnityInstruction( new Uri( HttpsTranslateUserSite ) );
          }
          catch( Exception e )
          {
@@ -232,12 +238,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          }
       }
 
-      public override void WriteCookies( HttpWebResponse response )
+      public override void StoreCookiesFromResponse( HttpWebResponse response )
       {
          _cookieContainer.Add( response.Cookies );
       }
 
-      public override CookieContainer ReadCookies()
+      public override CookieContainer GetCookiesForNewRequest()
       {
          return _cookieContainer;
       }

+ 16 - 9
src/XUnity.AutoTranslator.Plugin.Core/Web/BingTranslateLegitimateEndpoint.cs → src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/BingTranslateLegitimateEndpoint.cs

@@ -12,10 +12,12 @@ using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Utilities;
+using XUnity.AutoTranslator.Plugin.Core.Web;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Web
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 {
-   public class BingTranslateLegitimateEndpoint : KnownHttpEndpoint
+   internal class BingTranslateLegitimateEndpoint : HttpEndpoint
    {
       private static readonly string HttpsServicePointTemplateUrl = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&from={0}&to={1}";
       private static readonly string RequestTemplate = "[{{\"Text\":\"{0}\"}}]";
@@ -29,19 +31,24 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       private string _key;
 
-      public BingTranslateLegitimateEndpoint( string key )
+      public BingTranslateLegitimateEndpoint()
       {
-         if( string.IsNullOrEmpty( key ) ) throw new ArgumentException( "The BingTranslateLegitimate endpoint requires an API key which has not been provided.", nameof( key ) );
 
-         _key = key;
+      }
+
+      public override string Id => "BingTranslateLegitimate";
+
+      public override string FriendlyName => "Bing Translator (Authenticated)";
+
+      public override void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints )
+      {
+         _key = Config.Current.Preferences[ "BingLegitimate" ][ "OcpApimSubscriptionKey" ].GetOrDefault( "" );
+         if( string.IsNullOrEmpty( _key ) ) throw new Exception( "The BingTranslateLegitimate endpoint requires an API key which has not been provided." );
 
          // Configure service points / service point manager
-         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "api.cognitive.microsofttranslator.com" );
-         SetupServicePoints( "https://api.cognitive.microsofttranslator.com" );
+         servicePoints.EnableHttps( "api.cognitive.microsofttranslator.com" );
       }
 
-      public override bool SupportsLineSplitting => false;
-
       public override void ApplyHeaders( WebHeaderCollection headers )
       {
          if( Accept != null )

+ 49 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/CustomHttpEndpoint.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Constants;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Web;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
+{
+   internal class CustomHttpEndpoint : HttpEndpoint
+   {
+      private static readonly string ServicePointTemplateUrl = "{0}?from={1}&to={2}&text={3}";
+      private string _endpoint;
+      private string _friendlyName;
+
+      public CustomHttpEndpoint()
+      {
+         _friendlyName = "Custom";
+      }
+
+      public override string Id => "Custom";
+
+      public override string FriendlyName => "Custom";
+
+      public override void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints )
+      {
+         _endpoint = configuration.Preferences[ "Custom" ][ "Url" ].GetOrDefault( "" );
+         if( string.IsNullOrEmpty( _endpoint ) ) throw new ArgumentException( "The custom endpoint requires a url which has not been provided." );
+
+         var uri = new Uri( _endpoint );
+         servicePoints.EnableHttps( uri.Host );
+
+         _friendlyName += " (" + uri.Host + ")";
+      }
+
+      public override bool TryExtractTranslated( string result, out string translated )
+      {
+         translated = result;
+         return true;
+      }
+
+      public override string GetServiceUrl( string untranslatedText, string from, string to )
+      {
+         return string.Format( ServicePointTemplateUrl, _endpoint, from, to, WWW.EscapeURL( untranslatedText ) );
+      }
+   }
+}

+ 26 - 22
src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateEndpoint.cs → src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/GoogleTranslateEndpoint.cs

@@ -12,10 +12,12 @@ using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Utilities;
+using XUnity.AutoTranslator.Plugin.Core.Web;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Web
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 {
-   public class GoogleTranslateEndpoint : KnownHttpEndpoint
+   internal class GoogleTranslateEndpoint : HttpEndpoint
    {
       //protected static readonly ConstructorInfo WwwConstructor = Constants.Types.WWW?.GetConstructor( new[] { typeof( string ), typeof( byte[] ), typeof( Dictionary<string, string> ) } );
       private static readonly string HttpsServicePointTemplateUrl = "https://translate.googleapis.com/translate_a/single?client=t&dt=t&sl={0}&tl={1}&ie=UTF-8&oe=UTF-8&tk={2}&q={3}";
@@ -44,13 +46,16 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       public GoogleTranslateEndpoint()
       {
          _cookieContainer = new CookieContainer();
-
-         // Configure service points / service point manager
-         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "translate.google.com", "translate.googleapis.com" );
-         SetupServicePoints( "https://translate.googleapis.com", "https://translate.google.com" );
       }
 
-      public override bool SupportsLineSplitting => true;
+      public override string Id => KnownEndpointNames.GoogleTranslate;
+
+      public override string FriendlyName => "Google! Translate";
+
+      public override void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints )
+      {
+         servicePoints.EnableHttps( "translate.google.com", "translate.googleapis.com" );
+      }
 
       public override void ApplyHeaders( WebHeaderCollection headers )
       {
@@ -74,7 +79,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          }
       }
 
-      public override IEnumerator OnBeforeTranslate( int translationCount )
+      public override IEnumerator OnBeforeTranslate()
       {
          //if( !_hasSetupCustomUserAgent )
          //{
@@ -88,7 +93,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          //   }
          //}
 
-         if( !_hasSetup || translationCount % 100 == 0 )
+         if( !_hasSetup || AutoTranslationState.TranslationCount % 100 == 0 )
          {
             _hasSetup = true;
 
@@ -161,42 +166,41 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       public IEnumerator SetupTKK()
       {
          string error = null;
-         DownloadResult downloadResult = null;
+         UnityWebResponse response = null;
 
          _cookieContainer = new CookieContainer();
 
-         var client = GetClient();
          try
          {
-            ApplyHeaders( client.Headers );
-            client.Headers.Remove( HttpRequestHeader.Referer );
-            downloadResult = client.GetDownloadResult( new Uri( HttpsTranslateUserSite ) );
+            ApplyHeaders( Client.Headers );
+            Client.Headers.Remove( HttpRequestHeader.Referer );
+            response = Client.DownloadStringByUnityInstruction( new Uri( HttpsTranslateUserSite ) );
          }
          catch( Exception e )
          {
             error = e.ToString();
          }
 
-         if( downloadResult != null )
+         if( response != null )
          {
             if( Features.SupportsCustomYieldInstruction )
             {
-               yield return downloadResult;
+               yield return response;
             }
             else
             {
-               while( !downloadResult.IsCompleted )
+               while( !response.IsCompleted )
                {
                   yield return new WaitForSeconds( 0.2f );
                }
             }
 
-            error = downloadResult.Error;
-            if( downloadResult.Succeeded && downloadResult.Result != null )
+            error = response.Error;
+            if( response.Succeeded && response.Result != null )
             {
                try
                {
-                  var html = downloadResult.Result;
+                  var html = response.Result;
 
                   bool found = false;
                   string[] lookups = new[] { "tkk:'", "TKK='" };
@@ -336,7 +340,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          return string.Format( HttpsServicePointTemplateUrl, from, to, Tk( untranslatedText ), WWW.EscapeURL( untranslatedText ) );
       }
 
-      public override void WriteCookies( HttpWebResponse response )
+      public override void StoreCookiesFromResponse( HttpWebResponse response )
       {
          CookieCollection cookies = response.Cookies;
          foreach( Cookie cookie in cookies )
@@ -348,7 +352,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          _cookieContainer.Add( cookies );
       }
 
-      public override CookieContainer ReadCookies()
+      public override CookieContainer GetCookiesForNewRequest()
       {
          return _cookieContainer;
       }

+ 16 - 14
src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateLegitimateEndpoint.cs → src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/GoogleTranslateLegitimateEndpoint.cs

@@ -12,28 +12,35 @@ using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Utilities;
+using XUnity.AutoTranslator.Plugin.Core.Web;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Web
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 {
-   public class GoogleTranslateLegitimateEndpoint : KnownHttpEndpoint
+   internal class GoogleTranslateLegitimateEndpoint : HttpEndpoint
    {
       private static readonly string HttpsServicePointTemplateUrl = "https://translation.googleapis.com/language/translate/v2?key={0}";
 
       private string _key;
 
-      public GoogleTranslateLegitimateEndpoint( string key )
+      public GoogleTranslateLegitimateEndpoint()
       {
-         if( string.IsNullOrEmpty( key ) ) throw new ArgumentException( "The GoogleTranslateLegitimate endpoint requires an API key which has not been provided.", nameof( key ) );
 
-         _key = key;
+      }
+
+      public override string Id => "GoogleTranslateLegitimate";
+
+      public override string FriendlyName => "Google! Translate (Authenticated)";
+
+      public override void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints )
+      {
+         _key = Config.Current.Preferences[ "GoogleLegitimate" ][ "GoogleAPIKey" ].GetOrDefault( "" );
+         if( string.IsNullOrEmpty( _key ) ) throw new Exception( "The GoogleTranslateLegitimate endpoint requires an API key which has not been provided." );
 
          // Configure service points / service point manager
-         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "translation.googleapis.com" );
-         SetupServicePoints( "https://translation.googleapis.com" );
+         servicePoints.EnableHttps( "translation.googleapis.com" );
       }
 
-      public override bool SupportsLineSplitting => true;
-
       public override void ApplyHeaders( WebHeaderCollection headers )
       {
       }
@@ -83,10 +90,5 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          b.Append( "}" );
          return b.ToString();
       }
-
-      public override bool ShouldGetSecondChanceAfterFailure()
-      {
-         return false;
-      }
    }
 }

+ 131 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/HttpEndpoint.cs

@@ -0,0 +1,131 @@
+using System;
+using System.Collections;
+using System.Net;
+using System.Threading;
+using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Shim;
+using XUnity.AutoTranslator.Plugin.Core.Web;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
+{
+   public abstract class HttpEndpoint : ITranslateEndpoint
+   {
+      public HttpEndpoint()
+      {
+         Client = new UnityWebClient( this );
+      }
+
+      public abstract string Id { get; }
+
+      public abstract string FriendlyName { get; }
+
+      public UnityWebClient Client { get; }
+
+      public abstract void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints );
+
+      public abstract string GetServiceUrl( string untranslatedText, string from, string to );
+
+      public abstract bool TryExtractTranslated( string result, out string translated );
+
+      public int MaxConcurrency => 1;
+
+      public virtual string GetRequestObject( string untranslatedText, string from, string to ) => null;
+
+      public virtual CookieContainer GetCookiesForNewRequest() => null;
+
+      public virtual IEnumerator OnBeforeTranslate() => null;
+
+      public virtual void StoreCookiesFromResponse( HttpWebResponse response )
+      {
+
+      }
+
+      public virtual void ApplyHeaders( WebHeaderCollection headers )
+      {
+
+      }
+
+      public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure )
+      {
+         // allow implementer of HttpEndpoint to do anything before starting translation
+         var setup = OnBeforeTranslate();
+         if( setup != null )
+         {
+            while( setup.MoveNext() )
+            {
+               yield return setup.Current;
+            }
+         }
+
+         UnityWebResponse response = null;
+         try
+         {
+            // prepare request
+            var url = GetServiceUrl( untranslatedText, from, to );
+            var request = GetRequestObject( untranslatedText, from, to );
+            ApplyHeaders( Client.Headers );
+
+            // execute request
+            if( request != null )
+            {
+               response = Client.UploadStringByUnityInstruction( new Uri( url ), request );
+            }
+            else
+            {
+               response = Client.DownloadStringByUnityInstruction( new Uri( url ) );
+            }
+         }
+         catch( Exception e )
+         {
+            failure( "Error occurred while setting up translation request.", e );
+            yield break;
+         }
+
+         if( response == null )
+         {
+            failure( "Unexpected error occurred while retrieving translation.", null );
+            yield break;
+         }
+
+         // wait for completion
+         if( Features.SupportsCustomYieldInstruction )
+         {
+            yield return response;
+         }
+         else
+         {
+            while( !response.IsCompleted )
+            {
+               yield return new WaitForSeconds( 0.2f );
+            }
+         }
+
+         // failure
+         if( !response.Succeeded || response.Result == null )
+         {
+            failure( "Error occurred while retrieving translation." + Environment.NewLine + response.Error, null );
+            yield break;
+         }
+
+
+         try
+         {
+            // attempt to extract translation from data
+            if( TryExtractTranslated( response.Result, out var translatedText ) )
+            {
+               translatedText = translatedText ?? string.Empty;
+               success( translatedText );
+            }
+            else
+            {
+               failure( "Error occurred while extracting translation.", null );
+            }
+         }
+         catch( Exception e )
+         {
+            failure( "Error occurred while retrieving translation.", e );
+         }
+      }
+   }
+}

+ 15 - 9
src/XUnity.AutoTranslator.Plugin.Core/Web/YandexTranslateEndpoint.cs → src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Http/YandexTranslateEndpoint.cs

@@ -8,24 +8,31 @@ using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Utilities;
+using XUnity.AutoTranslator.Plugin.Core.Web;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Web
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Http
 {
-   public class YandexTranslateEndpoint : KnownHttpEndpoint
+   internal class YandexTranslateEndpoint : HttpEndpoint
    {
       private static readonly string HttpsServicePointTemplateUrl = "https://translate.yandex.net/api/v1.5/tr.json/translate?key={3}&text={2}&lang={0}-{1}&format=plain";
 
       private string _key;
 
-      public YandexTranslateEndpoint( string key )
-      {
-         if( string.IsNullOrEmpty( key ) ) throw new ArgumentException( "The YandexTranslate endpoint requires an API key which has not been provided.", nameof( key ) );
+      public override string Id => "YandexTranslate";
+
+      public override string FriendlyName => "Yandex Translate";
 
-         _key = key;
+      public YandexTranslateEndpoint()
+      {
+      }
 
-         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "translate.yandex.net" );
+      public override void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints )
+      {
+         _key = Config.Current.Preferences[ "Yandex" ][ "YandexAPIKey" ].GetOrDefault( "" );
+         if( string.IsNullOrEmpty( _key ) ) throw new Exception( "The YandexTranslate endpoint requires an API key which has not been provided." );
 
-         SetupServicePoints( "https://translate.yandex.net" );
+         servicePoints.EnableHttps( "translate.yandex.net" );
       }
 
       public override void ApplyHeaders( WebHeaderCollection headers )
@@ -39,7 +46,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       {
          try
          {
-
             var obj = JSON.Parse( result );
             var lineBuilder = new StringBuilder( result.Length );
 

+ 39 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ITranslateEndpoint.cs

@@ -0,0 +1,39 @@
+using System;
+using System.Collections;
+using System.IO;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Web;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
+{
+   public interface ITranslateEndpoint
+   {
+      /// <summary>
+      /// Gets the id of the IKnownEndpoint that is used as a configuration parameter.
+      /// </summary>
+      string Id { get; }
+
+      /// <summary>
+      /// Gets a friendly name that can be displayed to the user representing the plugin.
+      /// </summary>
+      string FriendlyName { get; }
+
+      /// <summary>
+      /// Gets the maximum concurrency for the endpoint. This specifies how many times "Translate"
+      /// can be called before it returns.
+      /// </summary>
+      int MaxConcurrency { get; }
+
+      /// <summary>
+      /// Called during initialization. Use this to initialize plugin or throw exception if impossible.
+      /// </summary>
+      void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints );
+
+      /// <summary>
+      /// Attempt to translated the provided untranslated text. Will be used in a "coroutine", so it can be implemented
+      /// in an async fashion.
+      /// </summary>
+      IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure );
+   }
+}

+ 65 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/KnownEndpoints.cs

@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Constants;
+using XUnity.AutoTranslator.Plugin.Core.Web;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints
+{
+   internal static class KnownEndpoints
+   {
+      public static List<ConfiguredEndpoint> CreateEndpoints( GameObject go, ServiceEndpointConfiguration servicePoints )
+      {
+         var endpoints = new List<ConfiguredEndpoint>();
+
+         // could dynamically load types from other assemblies...
+         var types = typeof( KnownEndpoints ).Assembly
+            .GetTypes()
+            .Where( x => typeof( ITranslateEndpoint ).IsAssignableFrom( x ) && !x.IsAbstract && !x.IsInterface )
+            .ToList();
+
+         foreach( var type in types )
+         {
+            AddEndpoint( go, servicePoints, endpoints, type );
+         }
+
+         return endpoints;
+      }
+
+      private static void AddEndpoint( GameObject go, ServiceEndpointConfiguration servicePoints, List<ConfiguredEndpoint> endpoints, Type type )
+      {
+         ITranslateEndpoint endpoint;
+         try
+         {
+            if( typeof( MonoBehaviour ).IsAssignableFrom( type ) )
+            {
+               // allow implementing plugins to hook into Unity lifecycle
+               endpoint = (ITranslateEndpoint)go.AddComponent( type );
+            }
+            else
+            {
+               // or... just use any old object
+               endpoint = (ITranslateEndpoint)Activator.CreateInstance( type );
+            }
+         }
+         catch( Exception e )
+         {
+            Logger.Current.Error( e, "Could not instantiate class: " + type.Name );
+            return;
+         }
+
+         try
+         {
+            endpoint.Initialize( Config.Current, servicePoints );
+            endpoints.Add( new ConfiguredEndpoint( endpoint, null ) );
+         }
+         catch( Exception e )
+         {
+            endpoints.Add( new ConfiguredEndpoint( endpoint, e ) );
+         }
+      }
+   }
+}

+ 59 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ProcessLineProtocol/LecPowerTranslateEndpoint.cs

@@ -0,0 +1,59 @@
+using System;
+using System.IO;
+using System.Text;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Web;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ProcessLineProtocol
+{
+   internal class LecPowerTranslateEndpoint : ProcessLineProtocolEndpoint
+   {
+      public override string Id => "LecPowerTranslator15";
+
+      public override string FriendlyName => "LEC Power Translator 15";
+
+      public override void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints )
+      {
+         var to = configuration.Preferences[ "General" ][ "Language" ].Value;
+         var from = configuration.Preferences[ "General" ][ "FromLanguage" ].Value;
+         var pathToLec = configuration.Preferences[ "LecPowerTranslator15" ][ "Path" ].GetOrDefault( "" );
+         if( string.IsNullOrEmpty( pathToLec ) ) throw new Exception( "The LecPowerTranslator15 requires the path to the installation folder." );
+         if( !from.Equals( "ja", StringComparison.OrdinalIgnoreCase ) ) throw new Exception( "Only japanese to english is supported." );
+         if( !to.Equals( "en", StringComparison.OrdinalIgnoreCase ) ) throw new Exception( "Only japanese to english is supported." );
+
+         var path1 = configuration.DataPath;
+         var exePath1 = Path.Combine( path1, "XUnity.AutoTranslator.Plugin.Lec.exe" );
+         var file1Exists = File.Exists( exePath1 );
+         var path2 = new FileInfo( typeof( LecPowerTranslateEndpoint ).Assembly.Location ).Directory.FullName;
+         var exePath2 = Path.Combine( path2, "XUnity.AutoTranslator.Plugin.Lec.exe" );
+         var file2Exists = File.Exists( exePath2 );
+         if( !file1Exists && !file2Exists )
+         {
+            if( path1 != path2 )
+            {
+               throw new Exception( $"Could not find any executable at '{exePath1}' or at '{exePath2}'" );
+            }
+            else
+            {
+               throw new Exception( $"Could not find any executable at '{exePath1}'" );
+            }
+         }
+
+         _arguments = Convert.ToBase64String( Encoding.UTF8.GetBytes( pathToLec ) );
+
+         if( file1Exists )
+         {
+            _exePath = exePath1;
+         }
+         else if( file2Exists )
+         {
+            _exePath = exePath2;
+         }
+         else
+         {
+            throw new Exception( "Unexpected error occurred." );
+         }
+      }
+   }
+}

+ 168 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/ProcessLineProtocol/ProcessLineProtocolEndpoint.cs

@@ -0,0 +1,168 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Shim;
+using XUnity.AutoTranslator.Plugin.Core.Web;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.ProcessLineProtocol
+{
+
+   public abstract class ProcessLineProtocolEndpoint : ITranslateEndpoint, IDisposable
+   {
+      private bool _disposed = false;
+      private Process _process;
+      protected string _exePath;
+      protected string _arguments;
+
+      public abstract string Id { get; }
+
+      public abstract string FriendlyName { get; }
+
+      public abstract void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints );
+
+      public int MaxConcurrency => 1;
+
+      protected void Process_OutputDataReceived( object sender, DataReceivedEventArgs e )
+      {
+      }
+
+      public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure )
+      {
+         var _result = new StreamReaderResult();
+         try
+         {
+            try
+            {
+               ThreadPool.QueueUserWorkItem( state =>
+               {
+                  if( _process == null )
+                  {
+                     _process = new Process();
+                     _process.StartInfo.FileName = _exePath;
+                     _process.StartInfo.Arguments = _arguments;
+                     _process.EnableRaisingEvents = false;
+                     _process.StartInfo.UseShellExecute = false;
+                     _process.StartInfo.RedirectStandardInput = true;
+                     _process.StartInfo.RedirectStandardOutput = true;
+                     _process.StartInfo.RedirectStandardError = true;
+                     _process.OutputDataReceived += Process_OutputDataReceived;
+                     _process.Start();
+
+                     // wait a second...
+                     _process.WaitForExit( 2500 );
+                  }
+
+                  if( _process.HasExited )
+                  {
+                     _result.SetCompleted( null, "The translation process exited. Likely due to invalid path to installation." );
+                     return;
+                  }
+
+                  var payload = Convert.ToBase64String( Encoding.UTF8.GetBytes( untranslatedText ) );
+                  _process.StandardInput.WriteLine( payload );
+
+                  var returnedPayload = _process.StandardOutput.ReadLine();
+                  var returnedLine = Encoding.UTF8.GetString( Convert.FromBase64String( returnedPayload ) );
+
+                  _result.SetCompleted( returnedLine, string.IsNullOrEmpty( returnedLine ) ? "Nothing was returned." : null );
+               } );
+            }
+            catch( Exception e )
+            {
+               failure( "Error occurred while retrieving translation.", e );
+               yield break;
+            }
+
+            // yield-wait for completion
+            if( Features.SupportsCustomYieldInstruction )
+            {
+               yield return _result;
+            }
+            else
+            {
+               while( !_result.IsCompleted )
+               {
+                  yield return new WaitForSeconds( 0.2f );
+               }
+            }
+
+            try
+            {
+               if( _result.Succeeded && _result.Result != null )
+               {
+                  success( _result.Result );
+               }
+               else
+               {
+                  failure( "Error occurred while retrieving translation." + Environment.NewLine + _result.Error, null );
+               }
+            }
+            catch( Exception e )
+            {
+               failure( "Error occurred while retrieving translation.", e );
+            }
+         }
+         finally
+         {
+            _result = null;
+         }
+      }
+
+      public void OnUpdate()
+      {
+      }
+
+      public class StreamReaderResult : CustomYieldInstructionShim
+      {
+         public void SetCompleted( string result, string error )
+         {
+            IsCompleted = true;
+            Result = result;
+            Error = error;
+         }
+
+         public override bool keepWaiting => !IsCompleted;
+
+         public string Result { get; set; }
+
+         public string Error { get; set; }
+
+         public bool IsCompleted { get; private set; } = false;
+
+         public bool Succeeded => Error == null;
+      }
+
+      #region IDisposable Support
+
+      protected virtual void Dispose( bool disposing )
+      {
+         if( !_disposed )
+         {
+            if( disposing )
+            {
+               if(_process != null )
+               {
+                  _process.Kill();
+                  _process.Dispose();
+               }
+            }
+
+            _disposed = true;
+         }
+      }
+
+      // This code added to correctly implement the disposable pattern.
+      public void Dispose()
+      {
+         Dispose( true );
+      }
+
+      #endregion
+   }
+}

+ 12 - 3
src/XUnity.AutoTranslator.Plugin.Core/Web/ExciteTranslateEndpoint.cs → src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/ExciteTranslateEndpoint.cs

@@ -8,16 +8,25 @@ using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Web;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Web
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Www
 {
-   public class ExciteTranslateEndpoint : KnownWwwEndpoint
+   internal class ExciteTranslateEndpoint : WwwEndpoint
    {
       private static readonly string HttpsServicePointTemplateUrl = "https://www.excite.co.jp/world/?wb_lp={0}{1}&before={2}";
 
       public ExciteTranslateEndpoint()
       {
-         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "www.excite.co.jp", "excite.co.jp" );
+      }
+
+      public override string Id => "ExciteTranslate";
+
+      public override string FriendlyName => "Excite Translator";
+
+      public override void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints )
+      {
+         servicePoints.EnableHttps( "www.excite.co.jp", "excite.co.jp" );
       }
 
       public override void ApplyHeaders( Dictionary<string, string> headers )

+ 21 - 21
src/XUnity.AutoTranslator.Plugin.Core/Web/WatsonTranslateEndpoint.cs → src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/WatsonTranslateEndpoint.cs

@@ -8,28 +8,36 @@ using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Utilities;
+using XUnity.AutoTranslator.Plugin.Core.Web;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Web
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Www
 {
-   public class WatsonTranslateEndpoint : KnownWwwEndpoint
+   internal class WatsonTranslateEndpoint : WwwEndpoint
    {
-      private static readonly string HttpsServicePointTemplateUrl = Settings.WatsonAPIUrl.TrimEnd( '/' ) + "/v2/translate?model_id={0}-{1}&text={2}";
-
+      private string _template;
       private string _url;
       private string _username;
       private string _password;
 
-      public WatsonTranslateEndpoint( string url, string username, string password )
+      public WatsonTranslateEndpoint()
       {
-         if( string.IsNullOrEmpty( url ) ) throw new ArgumentException( "The WatsonTranslate endpoint requires a url which has not been provided.", nameof( url ) );
-         if( string.IsNullOrEmpty( username ) ) throw new ArgumentException( "The WatsonTranslate endpoint requires a username which has not been provided.", nameof( username ) );
-         if( string.IsNullOrEmpty( password ) ) throw new ArgumentException( "The WatsonTranslate endpoint requires a password which has not been provided.", nameof( password ) );
+      }
+
+      public override string Id => "WatsonTranslate";
 
-         _url = url;
-         _username = username;
-         _password = password;
+      public override string FriendlyName => "Watson Language Translator";
 
-         //ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( new Uri( _url ).Host );
+      public override void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints )
+      {
+         _url = Config.Current.Preferences[ "Watson" ][ "WatsonAPIUrl" ].GetOrDefault( "" );
+         _username = Config.Current.Preferences[ "Watson" ][ "WatsonAPIUsername" ].GetOrDefault( "" );
+         _password = Config.Current.Preferences[ "Watson" ][ "WatsonAPIPassword" ].GetOrDefault( "" );
+         if( string.IsNullOrEmpty( _url ) ) throw new Exception( "The WatsonTranslate endpoint requires a url which has not been provided." );
+         if( string.IsNullOrEmpty( _username ) ) throw new Exception( "The WatsonTranslate endpoint requires a username which has not been provided." );
+         if( string.IsNullOrEmpty( _password ) ) throw new Exception( "The WatsonTranslate endpoint requires a password which has not been provided." );
+
+         _template = _url.TrimEnd( '/' ) + "/v2/translate?model_id={0}-{1}&text={2}";
       }
 
       public override void ApplyHeaders( Dictionary<string, string> headers )
@@ -40,14 +48,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          headers[ "Authorization" ] = "Basic " + System.Convert.ToBase64String( System.Text.Encoding.ASCII.GetBytes( _username + ":" + _password ) );
       }
 
-      //public override void ApplyHeaders( WebHeaderCollection headers )
-      //{
-      //   headers[ HttpRequestHeader.UserAgent ] = "curl/7.55.1";
-      //   headers[ HttpRequestHeader.Accept ] = "application/json";
-      //   headers[ HttpRequestHeader.AcceptCharset ] = "UTF-8";
-      //   headers[ HttpRequestHeader.Authorization ] = "Basic " + System.Convert.ToBase64String( System.Text.Encoding.ASCII.GetBytes( _username + ":" + _password ) );
-      //}
-
       public override bool TryExtractTranslated( string result, out string translated )
       {
          try
@@ -78,7 +78,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       public override string GetServiceUrl( string untranslatedText, string from, string to )
       {
-         return string.Format( HttpsServicePointTemplateUrl, from, to, WWW.EscapeURL( untranslatedText ) );
+         return string.Format( _template, from, to, WWW.EscapeURL( untranslatedText ) );
       }
    }
 }

+ 115 - 0
src/XUnity.AutoTranslator.Plugin.Core/Endpoints/Www/WwwEndpoint.cs

@@ -0,0 +1,115 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Net;
+using System.Reflection;
+using Harmony;
+using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Web;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Endpoints.Www
+{
+   public abstract class WwwEndpoint : ITranslateEndpoint
+   {
+      protected static readonly ConstructorInfo WwwConstructor = Constants.ClrTypes.WWW.GetConstructor( new[] { typeof( string ), typeof( byte[] ), typeof( Dictionary<string, string> ) } );
+
+      public abstract string Id { get; }
+
+      public abstract string FriendlyName { get; }
+
+      public abstract void Initialize( IConfiguration configuration, ServiceEndpointConfiguration servicePoints );
+
+      public abstract string GetServiceUrl( string untranslatedText, string from, string to );
+
+      public abstract void ApplyHeaders( Dictionary<string, string> headers );
+
+      public abstract bool TryExtractTranslated( string result, out string translated );
+
+      public virtual IEnumerator OnBeforeTranslate() => null;
+
+      public int MaxConcurrency => 1;
+
+      public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action<string, Exception> failure )
+      {
+         // allow implementer of HttpEndpoint to do anything before starting translation
+         var setup = OnBeforeTranslate();
+         if( setup != null )
+         {
+            while( setup.MoveNext() )
+            {
+               yield return setup.Current;
+            }
+         }
+
+         object www = null;
+         try
+         {
+            // prepare request
+            var headers = new Dictionary<string, string>();
+            ApplyHeaders( headers );
+            var url = GetServiceUrl( untranslatedText, from, to );
+
+            // execute request
+            www = WwwConstructor.Invoke( new object[] { url, null, headers } );
+         }
+         catch( Exception e )
+         {
+            failure( "Error occurred while setting up translation request.", e );
+            yield break;
+         }
+
+         if( www == null )
+         {
+            failure( "Unexpected error occurred while retrieving translation.", null );
+            yield break;
+         }
+
+         // wait for completion
+         yield return www;
+
+         try
+         {
+            // extract error
+            string error = null;
+            try
+            {
+               error = (string)AccessTools.Property( Constants.ClrTypes.WWW, "error" ).GetValue( www, null );
+            }
+            catch( Exception e )
+            {
+               error = e.ToString();
+            }
+
+            if( error != null )
+            {
+               failure( "Error occurred while retrieving translation." + Environment.NewLine + error, null );
+               yield break;
+            }
+
+            // extract text
+            var text = (string)AccessTools.Property( Constants.ClrTypes.WWW, "text" ).GetValue( www, null );
+            if( text == null )
+            {
+               failure( "Error occurred while extracting text from response.", null );
+               yield break;
+            }
+
+            // attempt to extract translation from data
+            if( TryExtractTranslated( text, out var translatedText ) )
+            {
+               translatedText = translatedText ?? string.Empty;
+               success( translatedText );
+            }
+            else
+            {
+               failure( "Error occurred while extracting translation.", null );
+            }
+         }
+         catch( Exception e )
+         {
+            failure( "Error occurred while retrieving translation.", e );
+         }
+      }
+   }
+}

+ 0 - 162
src/XUnity.AutoTranslator.Plugin.Core/Extensions/ComponentExtensions.cs

@@ -1,162 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using Harmony;
-using UnityEngine;
-using UnityEngine.UI;
-
-namespace XUnity.AutoTranslator.Plugin.Core.Extensions
-{
-   public static class ComponentExtensions
-   {
-      private static readonly string TextPropertyName = "text";
-      private static readonly string TexturePropertyName = "texture";
-      private static readonly string MainTexturePropertyName = "mainTexture";
-      private static readonly string CapitalMainTexturePropertyName = "MainTexture";
-      private static readonly string MarkAsChangedMethodName = "MarkAsChanged";
-
-      public static string GetText( this object ui )
-      {
-         if( ui == null ) return null;
-
-         string text = null;
-         var type = ui.GetType();
-
-         if( ui is Text )
-         {
-            text = ( (Text)ui ).text;
-         }
-         else if( ui is GUIContent )
-         {
-            text = ( (GUIContent)ui ).text;
-         }
-         else
-         {
-            // fallback to reflective approach
-            text = (string)ui.GetType()?.GetProperty( TextPropertyName )?.GetValue( ui, null );
-         }
-
-         return text ?? string.Empty;
-      }
-
-      public static void SetText( this object ui, string text )
-      {
-         if( ui == null ) return;
-
-         var type = ui.GetType();
-
-         if( ui is Text )
-         {
-            ( (Text)ui ).text = text;
-         }
-         else if( ui is GUIContent )
-         {
-            ( (GUIContent)ui ).text = text;
-         }
-         else
-         {
-            // fallback to reflective approach
-            type.GetProperty( TextPropertyName )?.GetSetMethod()?.Invoke( ui, new[] { text } );
-         }
-      }
-
-      public static string GetTextureName( this Texture texture )
-      {
-         if( !string.IsNullOrEmpty( texture.name ) ) return texture.name;
-         return "Unnamed";
-      }
-
-      public static Texture2D GetTexture( this object ui )
-      {
-         if( ui == null ) return null;
-
-         if( ui is Image image )
-         {
-            return image.mainTexture as Texture2D;
-         }
-         else if( ui is RawImage rawImage )
-         {
-            return rawImage.mainTexture as Texture2D;
-         }
-         else if( ui is SpriteRenderer spriteRenderer )
-         {
-            return spriteRenderer.sprite?.texture;
-         }
-         else
-         {
-            // lets attempt some reflection for several known types
-            var type = ui.GetType();
-            var texture = type.GetProperty( MainTexturePropertyName )?.GetValue( ui, null )
-               ?? type.GetProperty( TexturePropertyName )?.GetValue( ui, null )
-               ?? type.GetProperty( CapitalMainTexturePropertyName )?.GetValue( ui, null );
-
-            return texture as Texture2D;
-         }
-      }
-
-      public static void SetAllDirtyEx( this object ui )
-      {
-         if( ui == null ) return;
-
-         if( ui is Graphic graphic )
-         {
-            graphic.SetAllDirty();
-         }
-         else
-         {
-            // lets attempt some reflection for several known types
-            var type = ui.GetType();
-
-            AccessTools.Method( type, MarkAsChangedMethodName )?.Invoke( ui, null );
-         }
-      }
-   }
-
-   public static class UILabelExtensions
-   {
-      private static readonly string SpacingYPropertyName = "spacingY";
-      private static readonly string FloatSpacingYPropertyName = "floatSpacingY";
-      private static readonly string UseFloatSpacingPropertyName = "useFloatSpacing";
-
-      public static object GetSpacingY( this object uiLabel )
-      {
-         var type = uiLabel.GetType();
-         var useFloatSpacing = (bool)type.GetProperty( UseFloatSpacingPropertyName )?.GetGetMethod()?.Invoke( uiLabel, null );
-         if( useFloatSpacing )
-         {
-            return type.GetProperty( FloatSpacingYPropertyName )?.GetGetMethod()?.Invoke( uiLabel, null );
-         }
-         else
-         {
-            return type.GetProperty( SpacingYPropertyName )?.GetGetMethod()?.Invoke( uiLabel, null );
-         }
-      }
-
-      public static void SetSpacingY( this object uiLabel, object spacing )
-      {
-         var type = uiLabel.GetType();
-         if( spacing is float )
-         {
-            type.GetProperty( FloatSpacingYPropertyName )?.GetSetMethod()?.Invoke( uiLabel, new[] { spacing } );
-         }
-         else
-         {
-            type.GetProperty( SpacingYPropertyName )?.GetSetMethod()?.Invoke( uiLabel, new[] { spacing } );
-         }
-      }
-
-      public static object Multiply( this object numeric, float scale )
-      {
-         if( numeric is float )
-         {
-            return (float)numeric * scale;
-         }
-         else
-         {
-            return (int)( (int)numeric * scale );
-         }
-      }
-   }
-}

+ 20 - 0
src/XUnity.AutoTranslator.Plugin.Core/Extensions/EnumerableExtensions.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Extensions
+{
+   internal static class EnumerableExtensions
+   {
+      public static HashSet<T> ToHashSet<T>( this IEnumerable<T> ts )
+      {
+         var hashSet = new HashSet<T>();
+         foreach( var t in ts )
+         {
+            hashSet.Add( t );
+         }
+         return hashSet;
+      }
+   }
+}

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

@@ -6,8 +6,9 @@ using UnityEngine;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 {
-   public static class GameObjectExtensions
+   internal static class GameObjectExtensions
    {
+      private static GameObject[] _objects = new GameObject[ 128 ];
       private static readonly string DummyName = "Dummy";
       private static readonly string XuaIgnore = "XUAIGNORE";
 
@@ -31,6 +32,28 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          return null;
       }
 
+      public static string GetPath( this GameObject obj )
+      {
+         int i = 0;
+         _objects[ i++ ] = obj;
+         while( obj.transform.parent != null )
+         {
+            obj = obj.transform.parent.gameObject;
+            _objects[ i++ ] = obj;
+         }
+
+         StringBuilder path = new StringBuilder();
+         while( --i >= 0 )
+         {
+            path.Append( "/" ).Append( _objects[ i ].name );
+         }
+
+
+         var result = path.ToString();
+
+         return result;
+      }
+
       public static bool HasIgnoredName( this GameObject go )
       {
          return go.name.EndsWith( DummyName ) || go.name.Contains( XuaIgnore ) || go.transform?.parent?.name.EndsWith( DummyName ) == true;

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

@@ -6,7 +6,7 @@ using Harmony;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 {
-   public static class HarmonyInstanceExtensions
+   internal static class HarmonyInstanceExtensions
    {
       public static void PatchAll( this HarmonyInstance instance, IEnumerable<Type> types )
       {
@@ -30,7 +30,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          }
          catch( Exception e )
          {
-            Logger.Current.Warn( e, "An error occurred while patching a property/method on a class." );
+            Logger.Current.Warn( e, "An error occurred while patching a property/method on a class. Failing class: " + type.Name );
          }
       }
    }

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/Configuration/IniKeyExtensions.cs → src/XUnity.AutoTranslator.Plugin.Core/Extensions/IniKeyExtensions.cs

@@ -6,7 +6,7 @@ using System.Text;
 using ExIni;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Configuration
+namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 {
    public static class IniKeyExtensions
    {

+ 2 - 2
src/XUnity.AutoTranslator.Plugin.Core/Extensions/StringBuilderExtensions.cs

@@ -3,14 +3,14 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 {
-   public static class StringBuilderExtensions
+   internal static class StringBuilderExtensions
    {
       public static bool EndsWithWhitespaceOrNewline( this StringBuilder builder )
       {
          if( builder.Length == 0 ) return true;
 
          var lastChar = builder[ builder.Length - 1 ];
-         return Char.IsWhiteSpace( lastChar ) || lastChar == '\n' || lastChar == '\r';
+         return char.IsWhiteSpace( lastChar ) || lastChar == '\n';
       }
    }
 }

+ 2 - 141
src/XUnity.AutoTranslator.Plugin.Core/Extensions/StringExtensions.cs

@@ -8,7 +8,7 @@ using XUnity.AutoTranslator.Plugin.Core.Configuration;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 {
-   public static class StringExtensions
+   internal static class StringExtensions
    {
       private static readonly HashSet<char> Numbers = new HashSet<char>
       {
@@ -63,8 +63,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
       private static readonly char[] NewlinesCharacters = new char[] { '\r', '\n' };
       private static readonly char[] WhitespacesAndNewlines = new char[] { '\r', '\n', ' ', ' ' };
 
-
-
       public static string SanitizeForFileSystem( this string path )
       {
          var builder = new StringBuilder( path.Length );
@@ -257,18 +255,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          return builder.ToString();
       }
 
-      public static bool ContainsNumbers( this string text )
-      {
-         foreach( var c in text )
-         {
-            if( Numbers.Contains( c ) )
-            {
-               return true;
-            }
-         }
-         return false;
-      }
-
       public static bool StartsWithStrict( this string str, string prefix )
       {
          var len = Math.Min( str.Length, prefix.Length );
@@ -282,131 +268,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          return true;
       }
 
-      public static string UnescapeJson( this string str )
-      {
-         if( str == null ) return null;
-
-         var builder = new StringBuilder( str );
-
-         bool escapeNext = false;
-         for( int i = 0 ; i < builder.Length ; i++ )
-         {
-            var c = builder[ i ];
-            if( escapeNext )
-            {
-               bool found = true;
-               char escapeWith = default( char );
-               switch( c )
-               {
-                  case 'b':
-                     escapeWith = '\b';
-                     break;
-                  case 'f':
-                     escapeWith = '\f';
-                     break;
-                  case 'n':
-                     escapeWith = '\n';
-                     break;
-                  case 'r':
-                     escapeWith = '\r';
-                     break;
-                  case 't':
-                     escapeWith = '\t';
-                     break;
-                  case '"':
-                     escapeWith = '\"';
-                     break;
-                  case '\\':
-                     escapeWith = '\\';
-                     break;
-                  case 'u':
-                     escapeWith = 'u';
-                     break;
-                  default:
-                     found = false;
-                     break;
-               }
-
-               // remove previous char and go one back
-               if( found )
-               {
-                  if( escapeWith == 'u' )
-                  {
-                     // unicode crap, lets handle the next 4 characters manually
-                     int code = int.Parse( new string( new char[] { builder[ i + 1 ], builder[ i + 2 ], builder[ i + 3 ], builder[ i + 4 ] } ), NumberStyles.HexNumber );
-                     var replacingChar = (char)code;
-                     builder.Remove( --i, 6 );
-                     builder.Insert( i, replacingChar );
-                  }
-                  else
-                  {
-                     // found proper escaping
-                     builder.Remove( --i, 2 );
-                     builder.Insert( i, escapeWith );
-                  }
-               }
-               else
-               {
-                  // dont do anything
-               }
-
-               escapeNext = false;
-            }
-            else if( c == '\\' )
-            {
-               escapeNext = true;
-            }
-         }
-
-         return builder.ToString();
-      }
-
-      public static string EscapeJson( this string str )
-      {
-         if( str == null || str.Length == 0 )
-         {
-            return "";
-         }
-
-         char c;
-         int len = str.Length;
-         StringBuilder sb = new StringBuilder( len + 4 );
-         for( int i = 0 ; i < len ; i += 1 )
-         {
-            c = str[ i ];
-            switch( c )
-            {
-               case '\\':
-               case '"':
-                  sb.Append( '\\' );
-                  sb.Append( c );
-                  break;
-               case '/':
-                  sb.Append( '\\' );
-                  sb.Append( c );
-                  break;
-               case '\b':
-                  sb.Append( "\\b" );
-                  break;
-               case '\t':
-                  sb.Append( "\\t" );
-                  break;
-               case '\n':
-                  sb.Append( "\\n" );
-                  break;
-               case '\f':
-                  sb.Append( "\\f" );
-                  break;
-               case '\r':
-                  sb.Append( "\\r" );
-                  break;
-               default:
-                  sb.Append( c );
-                  break;
-            }
-         }
-         return sb.ToString();
-      }
       public static string GetBetween( this string strSource, string strStart, string strEnd )
       {
          const int kNotFound = -1;
@@ -421,7 +282,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
                return strSource.Substring( startIdx, endIdx - startIdx );
             }
          }
-         return String.Empty;
+         return string.Empty;
       }
 
       public static bool RemindsOf( this string that, string other )

+ 132 - 0
src/XUnity.AutoTranslator.Plugin.Core/Extensions/TextComponentExtensions.cs

@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+using UnityEngine.UI;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Constants;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Extensions
+{
+   internal static class TextComponentExtensions
+   {
+      private static readonly string RichTextPropertyName = "richText";
+      private static readonly string TextPropertyName = "text";
+
+      public static bool IsKnownTextType( this object ui )
+      {
+         if( ui == null ) return false;
+
+         var type = ui.GetType();
+
+         return ( Settings.EnableUGUI && ui is Text )
+            || ( 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 ) );
+      }
+
+      public static bool SupportsRichText( this object ui )
+      {
+         if( ui == null ) return false;
+
+         var type = ui.GetType();
+
+         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 ) );
+      }
+
+      public static bool SupportsStabilization( this object ui )
+      {
+         if( ui == null ) return false;
+
+         // shortcircuit for spammy component, to avoid reflective calls
+         if( ui is GUIContent ) return false;
+
+         var type = ui.GetType();
+
+         return ui is Text
+            || ( ClrTypes.UILabel != null && ClrTypes.UILabel.IsAssignableFrom( type ) )
+            || ( ClrTypes.TMP_Text != null && ClrTypes.TMP_Text.IsAssignableFrom( type ) );
+      }
+
+      public static bool SupportsLineParser( this object ui )
+      {
+         return Settings.GameLogTextPaths.Count > 0 && ui is Component comp && Settings.GameLogTextPaths.Contains( comp.gameObject.GetPath() );
+      }
+
+      public static bool IsSpammingComponent( this object ui )
+      {
+         if( ui == null ) return false;
+
+         return ui is UnityEngine.GUIContent;
+      }
+
+      public static bool IsWhitelistedForImmediateRichTextTranslation( this object ui )
+      {
+         if( ui == null ) return false;
+
+         var type = ui.GetType();
+
+         return ClrTypes.AdvCommand != null && ClrTypes.AdvCommand.IsAssignableFrom( type );
+      }
+
+      public static bool IsNGUI( this object ui )
+      {
+         if( ui == null ) return false;
+
+         var type = ui.GetType();
+
+         return ClrTypes.UILabel != null && ClrTypes.UILabel.IsAssignableFrom( type );
+      }
+
+      public static string GetText( this object ui )
+      {
+         if( ui == null ) return null;
+
+         string text = null;
+         var type = ui.GetType();
+
+         if( ui is Text )
+         {
+            text = ( (Text)ui ).text;
+         }
+         else if( ui is GUIContent )
+         {
+            text = ( (GUIContent)ui ).text;
+         }
+         else
+         {
+            // fallback to reflective approach
+            text = (string)ui.GetType()?.GetProperty( TextPropertyName )?.GetValue( ui, null );
+         }
+
+         return text ?? string.Empty;
+      }
+
+      public static void SetText( this object ui, string text )
+      {
+         if( ui == null ) return;
+
+         var type = ui.GetType();
+
+         if( ui is Text )
+         {
+            ( (Text)ui ).text = text;
+         }
+         else if( ui is GUIContent )
+         {
+            ( (GUIContent)ui ).text = text;
+         }
+         else
+         {
+            // fallback to reflective approach
+            type.GetProperty( TextPropertyName )?.GetSetMethod()?.Invoke( ui, new[] { text } );
+         }
+      }
+   }
+}

+ 59 - 0
src/XUnity.AutoTranslator.Plugin.Core/Extensions/TextureComponentExtensions.cs

@@ -0,0 +1,59 @@
+using Harmony;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Extensions
+{
+   internal static class TextureComponentExtensions
+   {
+      private static readonly string TexturePropertyName = "texture";
+      private static readonly string MainTexturePropertyName = "mainTexture";
+      private static readonly string CapitalMainTexturePropertyName = "MainTexture";
+      private static readonly string MarkAsChangedMethodName = "MarkAsChanged";
+
+      public static Texture2D GetTexture( this object ui )
+      {
+         if( ui == null ) return null;
+
+         if( ui is Image image )
+         {
+            return image.mainTexture as Texture2D;
+         }
+         else if( ui is RawImage rawImage )
+         {
+            return rawImage.mainTexture as Texture2D;
+         }
+         else if( ui is SpriteRenderer spriteRenderer )
+         {
+            return spriteRenderer.sprite?.texture;
+         }
+         else
+         {
+            // lets attempt some reflection for several known types
+            var type = ui.GetType();
+            var texture = type.GetProperty( MainTexturePropertyName )?.GetValue( ui, null )
+               ?? type.GetProperty( TexturePropertyName )?.GetValue( ui, null )
+               ?? type.GetProperty( CapitalMainTexturePropertyName )?.GetValue( ui, null );
+
+            return texture as Texture2D;
+         }
+      }
+
+      public static void SetAllDirtyEx( this object ui )
+      {
+         if( ui == null ) return;
+
+         if( ui is Graphic graphic )
+         {
+            graphic.SetAllDirty();
+         }
+         else
+         {
+            // lets attempt some reflection for several known types
+            var type = ui.GetType();
+
+            AccessTools.Method( type, MarkAsChangedMethodName )?.Invoke( ui, null );
+         }
+      }
+   }
+}

+ 21 - 1
src/XUnity.AutoTranslator.Plugin.Core/Extensions/TextureExtensions.cs

@@ -1,15 +1,17 @@
 using System;
 using System.Collections.Generic;
+using System.Drawing;
 using System.Linq;
 using System.Reflection;
 using System.Text;
 using Harmony;
 using UnityEngine;
+using UnityEngine.UI;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 {
-   public static class TextureExtensions
+   internal static class TextureExtensions
    {
       private static readonly MethodInfo LoadImage = AccessTools.Method( ClrTypes.ImageConversion, "LoadImage", new[] { typeof( Texture2D ), typeof( byte[] ), typeof( bool ) } );
       private static readonly MethodInfo EncodeToPNG = AccessTools.Method( ClrTypes.ImageConversion, "EncodeToPNG", new[] { typeof( Texture2D ) } );
@@ -19,6 +21,24 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
       //   return texture.GetRawTextureData().Length == 0;
       //}
 
+      public static bool IsKnownImageType( this object ui )
+      {
+         var type = ui.GetType();
+
+         return ( ui is Material || ui is UnityEngine.UI.Image || ui is RawImage || ui is SpriteRenderer )
+            || ( ClrTypes.CubismRenderer != null && ClrTypes.CubismRenderer.IsAssignableFrom( type ) )
+            || ( ClrTypes.UIWidget != null && type != ClrTypes.UILabel && ClrTypes.UIWidget.IsAssignableFrom( type ) )
+            || ( ClrTypes.UIAtlas != null && ClrTypes.UIAtlas.IsAssignableFrom( type ) )
+            || ( ClrTypes.UITexture != null && ClrTypes.UITexture.IsAssignableFrom( type ) )
+            || ( ClrTypes.UIPanel != null && ClrTypes.UIPanel.IsAssignableFrom( type ) );
+      }
+
+      public static string GetTextureName( this Texture texture )
+      {
+         if( !string.IsNullOrEmpty( texture.name ) ) return texture.name;
+         return "Unnamed";
+      }
+
       public static void LoadImageEx( this Texture2D texture, byte[] data, bool markNonReadable )
       {
          if( LoadImage != null )

+ 18 - 0
src/XUnity.AutoTranslator.Plugin.Core/Extensions/TranslationEndpointExtensions.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using XUnity.AutoTranslator.Plugin.Core.Endpoints;
+using XUnity.AutoTranslator.Plugin.Core.Endpoints.Http;
+using XUnity.AutoTranslator.Plugin.Core.Web;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Extensions
+{
+   internal static class TranslationEndpointExtensions
+   {
+      public static bool SupportsLineSplitting( this ITranslateEndpoint endpoint )
+      {
+         return endpoint is GoogleTranslateEndpoint || endpoint is GoogleTranslateLegitimateEndpoint;
+      }
+   }
+}

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

@@ -5,7 +5,7 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 {
-   public static class TypeExtensions
+   internal static class TypeExtensions
    {
       public static Type UnwrapNullable( this Type type )
       {

+ 48 - 0
src/XUnity.AutoTranslator.Plugin.Core/Extensions/UILabelExtensions.cs

@@ -0,0 +1,48 @@
+namespace XUnity.AutoTranslator.Plugin.Core.Extensions
+{
+   internal static class UILabelExtensions
+   {
+      private static readonly string SpacingYPropertyName = "spacingY";
+      private static readonly string FloatSpacingYPropertyName = "floatSpacingY";
+      private static readonly string UseFloatSpacingPropertyName = "useFloatSpacing";
+
+      public static object GetSpacingY( this object uiLabel )
+      {
+         var type = uiLabel.GetType();
+         var useFloatSpacing = (bool)type.GetProperty( UseFloatSpacingPropertyName )?.GetGetMethod()?.Invoke( uiLabel, null );
+         if( useFloatSpacing )
+         {
+            return type.GetProperty( FloatSpacingYPropertyName )?.GetGetMethod()?.Invoke( uiLabel, null );
+         }
+         else
+         {
+            return type.GetProperty( SpacingYPropertyName )?.GetGetMethod()?.Invoke( uiLabel, null );
+         }
+      }
+
+      public static void SetSpacingY( this object uiLabel, object spacing )
+      {
+         var type = uiLabel.GetType();
+         if( spacing is float )
+         {
+            type.GetProperty( FloatSpacingYPropertyName )?.GetSetMethod()?.Invoke( uiLabel, new[] { spacing } );
+         }
+         else
+         {
+            type.GetProperty( SpacingYPropertyName )?.GetSetMethod()?.Invoke( uiLabel, new[] { spacing } );
+         }
+      }
+
+      public static object Multiply( this object numeric, float scale )
+      {
+         if( numeric is float )
+         {
+            return (float)numeric * scale;
+         }
+         else
+         {
+            return (int)( (int)numeric * scale );
+         }
+      }
+   }
+}

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/Fonts/FontCache.cs

@@ -7,7 +7,7 @@ using XUnity.AutoTranslator.Plugin.Core.Configuration;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Fonts
 {
-   public static class FontCache
+   internal static class FontCache
    {
       private static readonly Dictionary<int, Font> CachedFonts = new Dictionary<int, Font>();
 

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

@@ -9,15 +9,16 @@ using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI;
 using XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI;
+using XUnity.AutoTranslator.Plugin.Core.Hooks.TextGetterCompat;
 using XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro;
 using XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI;
-using XUnity.AutoTranslator.Plugin.Core.IMGUI;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 {
 
-   public static class HooksSetup
+   internal static class HooksSetup
    {
       private static HarmonyInstance _harmony;
 
@@ -74,47 +75,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          {
             Logger.Current.Error( e, "An error occurred while setting up image hooks." );
          }
-         
-         //var knownTypes = new HashSet<Type> { typeof( Texture ), typeof( Texture2D ), typeof( Sprite ), typeof( Material ) };
-         //foreach( var assembly in AppDomain.CurrentDomain.GetAssemblies() )
-         //{
-         //   try
-         //   {
-         //      var types = assembly.GetTypes();
-         //      foreach( var type in types )
-         //      {
-         //         try
-         //         {
-         //            var properties = type.GetProperties( BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public );
-         //            foreach( var property in properties )
-         //            {
-         //               if( property.CanWrite && knownTypes.Contains( property.PropertyType ) )
-         //               {
-         //                  try
-         //                  {
-         //                     var original = property.GetSetMethod();
-         //                     var prefix = typeof( GenericPrefix_Hook ).GetMethod( "Prefix" );
-         //                     _harmony.Patch( original, new HarmonyMethod( prefix ), null, null );
-         //                     Logger.Current.Warn( "Patched: " + type.Name + "." + property.Name );
-         //                  }
-         //                  catch( Exception e )
-         //                  {
-         //                     Logger.Current.Error( "Failed patching: " + type.Name + "." + property.Name );
-         //                  }
-         //               }
-         //            }
-         //         }
-         //         catch( Exception e )
-         //         {
-         //            Logger.Current.Error( e, "Failed getting properties of type: " + type.Name );
-         //         }
-         //      }
-         //   }
-         //   catch( Exception )
-         //   {
-         //      Logger.Current.Error( "Failed getting types of assembly: " + assembly.FullName );
-         //   }
-         //}
       }
 
       public static void InstallTextHooks()
@@ -161,13 +121,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
             if( Settings.EnableIMGUI )
             {
                _harmony.PatchAll( IMGUIHooks.All );
-
-               // This wont work in "newer" unity versions!
-               try
-               {
-                  _harmony.PatchType( typeof( DoButtonGridHook ) );
-               }
-               catch { }
             }
          }
          catch( Exception e )

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

@@ -7,27 +7,28 @@ using Harmony;
 using UnityEngine;
 using static UnityEngine.GUI;
 
-namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
+namespace XUnity.AutoTranslator.Plugin.Core.Hooks.IMGUI
 {
-   public static class IMGUIHooks
+   internal static class IMGUIHooks
    {
       public static bool HooksOverriden = false;
 
       public static readonly Type[] All = new[] {
-         typeof( BeginGroupHook ),
-         typeof( BoxHook ),
-         typeof( DoRepeatButtonHook ),
-         typeof( DoLabelHook ),
-         typeof( DoButtonHook ),
-         typeof( DoModalWindowHook ),
-         typeof( DoWindowHook ),
-         typeof( DoTextFieldHook ),
-         typeof( DoToggleHook ),
+         typeof( GUI_BeginGroup_Hook ),
+         typeof( GUI_Box_Hook ),
+         typeof( GUI_DoRepeatButton_Hook ),
+         typeof( GUI_DoLabel_Hook ),
+         typeof( GUI_DoButton_Hook ),
+         typeof( GUI_DoModalWindow_Hook ),
+         typeof( GUI_DoWindow_Hook ),
+         typeof( GUI_DoTextField_Hook ),
+         typeof( GUI_DoButtonGrid_Hook ),
+         typeof( GUI_DoToggle_Hook ),
       };
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class BeginGroupHook
+   internal static class GUI_BeginGroup_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -49,7 +50,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class BoxHook
+   internal static class GUI_Box_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -72,7 +73,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class DoRepeatButtonHook
+   internal static class GUI_DoRepeatButton_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -94,7 +95,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class DoLabelHook
+   internal static class GUI_DoLabel_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -116,7 +117,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class DoButtonHook
+   internal static class GUI_DoButton_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -138,7 +139,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class DoModalWindowHook
+   internal static class GUI_DoModalWindow_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -160,7 +161,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class DoWindowHook
+   internal static class GUI_DoWindow_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -182,7 +183,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class DoButtonGridHook
+   internal static class GUI_DoButtonGrid_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -207,7 +208,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class DoTextFieldHook
+   internal static class GUI_DoTextField_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -229,7 +230,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class DoToggleHook
+   internal static class GUI_DoToggle_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {

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

@@ -12,7 +12,7 @@ using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 {
-   public static class ImageHooks
+   internal static class ImageHooks
    {
       public static readonly Type[] All = new[] {
          typeof( MaskableGraphic_OnEnable_Hook ),
@@ -41,26 +41,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          typeof( UITexture_material_Hook ),
          typeof( UIPanel_clipTexture_Hook ),
          typeof( UIRect_OnInit_Hook ),
-         //typeof( UIFont_dynamicFont_Hook ),
-         //typeof( UIFont_material_Hook ),
-         //typeof( UILabel_bitmapFont_Hook ),
-         //typeof( UILabel_trueTypeFont_Hook ),
       };
    }
 
-   //public static class GenericPrefix_Hook
-   //{
-   //   public static void Prefix( object __instance, object value )
-   //   {
-   //      if( value is Texture2D texture2d )
-   //      {
-   //         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance, texture2d, true );
-   //      }
-   //   }
-   //}
-
    [Harmony]
-   public static class SpriteRenderer_sprite_Hook
+   internal static class SpriteRenderer_sprite_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -78,142 +63,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
       }
    }
 
-   //[Harmony]
-   //public static class SpriteRenderer_get_sprite_Hook
-   //{
-   //   static bool Prepare( HarmonyInstance instance )
-   //   {
-   //      return ClrTypes.SpriteRenderer != null;
-   //   }
-
-   //   static MethodBase TargetMethod( HarmonyInstance instance )
-   //   {
-   //      return AccessTools.Property( ClrTypes.SpriteRenderer, "sprite" ).GetGetMethod();
-   //   }
-
-   //   public static void Postfix( object __instance )
-   //   {
-   //      Logger.Current.Error( new StackTrace().ToString() );
-   //      AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance, null, false, false );
-   //   }
-   //}
-
-   //[Harmony]
-   //public static class SpriteRenderer_get_size_Hook
-   //{
-   //   static bool Prepare( HarmonyInstance instance )
-   //   {
-   //      return ClrTypes.SpriteRenderer != null;
-   //   }
-
-   //   static MethodBase TargetMethod( HarmonyInstance instance )
-   //   {
-   //      return AccessTools.Property( ClrTypes.SpriteRenderer, "size" ).GetGetMethod();
-   //   }
-
-   //   public static void Postfix( object __instance )
-   //   {
-   //      Logger.Current.Error( "SpriteRenderer_get_size_Hook" );
-   //      AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance, null, false, false );
-   //   }
-   //}
-
-   //[Harmony]
-   //public static class SpriteRenderer_set_size_Hook
-   //{
-   //   static bool Prepare( HarmonyInstance instance )
-   //   {
-   //      return ClrTypes.SpriteRenderer != null;
-   //   }
-
-   //   static MethodBase TargetMethod( HarmonyInstance instance )
-   //   {
-   //      return AccessTools.Property( ClrTypes.SpriteRenderer, "size" ).GetSetMethod();
-   //   }
-
-   //   public static void Postfix( object __instance )
-   //   {
-   //      Logger.Current.Error( "SpriteRenderer_set_size_Hook" );
-   //      AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance, null, false, false );
-   //   }
-   //}
-
-   //[Harmony]
-   //public static class SpriteRenderer_get_color_Hook
-   //{
-   //   static bool Prepare( HarmonyInstance instance )
-   //   {
-   //      return ClrTypes.SpriteRenderer != null;
-   //   }
-
-   //   static MethodBase TargetMethod( HarmonyInstance instance )
-   //   {
-   //      return AccessTools.Property( ClrTypes.SpriteRenderer, "color" ).GetGetMethod();
-   //   }
-
-   //   public static void Postfix( object __instance )
-   //   {
-   //      Logger.Current.Error( "SpriteRenderer_get_color_Hook" );
-   //      AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance, null, false, false );
-   //   }
-   //}
-
-   //[Harmony]
-   //public static class SpriteRenderer_set_color_Hook
-   //{
-   //   static bool Prepare( HarmonyInstance instance )
-   //   {
-   //      return ClrTypes.SpriteRenderer != null;
-   //   }
-
-   //   static MethodBase TargetMethod( HarmonyInstance instance )
-   //   {
-   //      return AccessTools.Property( ClrTypes.SpriteRenderer, "color" ).GetSetMethod();
-   //   }
-
-   //   public static void Postfix( object __instance )
-   //   {
-   //      Logger.Current.Error( "SpriteRenderer_set_color_Hook" );
-   //      AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance, null, false, false );
-   //   }
-   //}
-
-   //[Harmony]
-   //public static class SpriteRenderer_GetSpriteBounds_Hook
-   //{
-   //   static bool Prepare( HarmonyInstance instance )
-   //   {
-   //      return ClrTypes.SpriteRenderer != null;
-   //   }
-
-   //   static MethodBase TargetMethod( HarmonyInstance instance )
-   //   {
-   //      return AccessTools.Method( ClrTypes.SpriteRenderer, "GetSpriteBounds" );
-   //   }
-
-   //   public static void Postfix( object __instance )
-   //   {
-   //      Logger.Current.Error( "SpriteRenderer_GetSpriteBounds_Hook" );
-   //      AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance, null, false, false );
-   //   }
-   //}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
    [Harmony]
-   public static class CubismRenderer_MainTexture_Hook
+   internal static class CubismRenderer_MainTexture_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -232,7 +83,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class CubismRenderer_TryInitialize_Hook
+   internal static class CubismRenderer_TryInitialize_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -251,7 +102,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class Material_mainTexture_Hook
+   internal static class Material_mainTexture_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -273,7 +124,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class MaskableGraphic_OnEnable_Hook
+   internal static class MaskableGraphic_OnEnable_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -295,7 +146,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class Image_sprite_Hook
+   internal static class Image_sprite_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -314,7 +165,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class Image_overrideSprite_Hook
+   internal static class Image_overrideSprite_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -333,7 +184,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class Image_material_Hook
+   internal static class Image_material_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -352,7 +203,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class RawImage_texture_Hook
+   internal static class RawImage_texture_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -374,7 +225,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class Cursor_SetCursor_Hook
+   internal static class Cursor_SetCursor_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -393,7 +244,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UIAtlas_spriteMaterial_Hook
+   internal static class UIAtlas_spriteMaterial_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -412,7 +263,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UISprite_OnInit_Hook
+   internal static class UISprite_OnInit_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -431,7 +282,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UISprite_material_Hook
+   internal static class UISprite_material_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -450,7 +301,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UISprite_atlas_Hook
+   internal static class UISprite_atlas_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -469,7 +320,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UITexture_mainTexture_Hook
+   internal static class UITexture_mainTexture_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -488,7 +339,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UITexture_material_Hook
+   internal static class UITexture_material_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -507,7 +358,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UIRect_OnInit_Hook
+   internal static class UIRect_OnInit_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -526,7 +377,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UI2DSprite_sprite2D_Hook
+   internal static class UI2DSprite_sprite2D_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -545,7 +396,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UI2DSprite_material_Hook
+   internal static class UI2DSprite_material_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -564,7 +415,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UIPanel_clipTexture_Hook
+   internal static class UIPanel_clipTexture_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -584,7 +435,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
 
    [Harmony]
-   public static class UIFont_material_Hook
+   internal static class UIFont_material_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -603,7 +454,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UIFont_dynamicFont_Hook
+   internal static class UIFont_dynamicFont_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -622,7 +473,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UILabel_bitmapFont_Hook
+   internal static class UILabel_bitmapFont_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -641,7 +492,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class UILabel_trueTypeFont_Hook
+   internal static class UILabel_trueTypeFont_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {

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

@@ -10,18 +10,18 @@ using XUnity.AutoTranslator.Plugin.Core.Constants;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
 {
-   public static class NGUIHooks
+   internal static class NGUIHooks
    {
       public static bool HooksOverriden = false;
 
       public static readonly Type[] All = new[] {
-         typeof( TextPropertyHook ),
-         typeof( OnEnableHook )
+         typeof( UILabel_text_Hook ),
+         typeof( UILabel_OnEnable_Hook )
       };
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class TextPropertyHook
+   internal static class UILabel_text_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -44,7 +44,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class OnEnableHook
+   internal static class UILabel_OnEnable_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {

+ 9 - 8
src/XUnity.AutoTranslator.Plugin.Core/Hooks/TextGetterCompatHooks.cs

@@ -2,19 +2,20 @@
 using System.Reflection;
 using Harmony;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
+using XUnity.AutoTranslator.Plugin.Core.Utilities;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
+namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextGetterCompat
 {
-   public static class TextGetterCompatHooks
+   internal static class TextGetterCompatHooks
    {
       public static readonly Type[] All = new[] {
-         typeof( TextPropertyGetterHook1 ),
-         typeof( TextPropertyGetterHook2 ),
+         typeof( Text_text_Hook ),
+         typeof( TMP_Text_text_Hook ),
       };
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class TextPropertyGetterHook1
+   internal static class Text_text_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -29,12 +30,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
 
       static void Postfix( object __instance, ref string __result )
       {
-         TextGetterCompatMode.ReplaceTextWithOriginal( __instance, ref __result );
+         TextGetterCompatModeHelper.ReplaceTextWithOriginal( __instance, ref __result );
       }
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class TextPropertyGetterHook2
+   internal static class TMP_Text_text_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -48,7 +49,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
 
       static void Postfix( object __instance, ref string __result )
       {
-         TextGetterCompatMode.ReplaceTextWithOriginal( __instance, ref __result );
+         TextGetterCompatModeHelper.ReplaceTextWithOriginal( __instance, ref __result );
       }
    }
 }

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

@@ -8,25 +8,25 @@ using XUnity.AutoTranslator.Plugin.Core.Constants;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
 {
-   public static class TextMeshProHooks
+   internal static class TextMeshProHooks
    {
       public static bool HooksOverriden = false;
 
       public static readonly Type[] All = new[] {
-         typeof( TeshMeshProUGUIOnEnableHook ),
-         typeof( TeshMeshProOnEnableHook ),
-         typeof( TextPropertyHook ),
-         typeof( SetTextHook1 ),
-         typeof( SetTextHook2 ),
-         typeof( SetTextHook3 ),
-         typeof( SetCharArrayHook1 ),
-         typeof( SetCharArrayHook2 ),
-         typeof( SetCharArrayHook3 ),
+         typeof( TeshMeshProUGUI_OnEnable_Hook ),
+         typeof( TeshMeshPro_OnEnable_Hook ),
+         typeof( TMP_Text_text_Hook ),
+         typeof( TMP_Text_SetText_Hook1 ),
+         typeof( TMP_Text_SetText_Hook2 ),
+         typeof( TMP_Text_SetText_Hook3 ),
+         typeof( TMP_Text_SetCharArray_Hook1 ),
+         typeof( TMP_Text_SetCharArray_Hook2 ),
+         typeof( TMP_Text_SetCharArray_Hook3 ),
       };
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class TeshMeshProUGUIOnEnableHook
+   internal static class TeshMeshProUGUI_OnEnable_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -49,7 +49,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class TeshMeshProOnEnableHook
+   internal static class TeshMeshPro_OnEnable_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -72,7 +72,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class TextPropertyHook
+   internal static class TMP_Text_text_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -95,7 +95,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class SetTextHook1
+   internal static class TMP_Text_SetText_Hook1
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -118,7 +118,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class SetTextHook2
+   internal static class TMP_Text_SetText_Hook2
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -141,7 +141,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class SetTextHook3
+   internal static class TMP_Text_SetText_Hook3
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -164,7 +164,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class SetCharArrayHook1
+   internal static class TMP_Text_SetCharArray_Hook1
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -187,7 +187,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class SetCharArrayHook2
+   internal static class TMP_Text_SetCharArray_Hook2
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -210,7 +210,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class SetCharArrayHook3
+   internal static class TMP_Text_SetCharArray_Hook3
    {
       static bool Prepare( HarmonyInstance instance )
       {

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

@@ -8,19 +8,18 @@ using XUnity.AutoTranslator.Plugin.Core.Constants;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
 {
-
-   public static class UGUIHooks
+   internal static class UGUIHooks
    {
       public static bool HooksOverriden = false;
 
       public static readonly Type[] All = new[] {
-         typeof( TextPropertyHook ),
-         typeof( OnEnableHook ),
+         typeof( Text_text_Hook ),
+         typeof( Text_OnEnable_Hook ),
       };
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class TextPropertyHook
+   internal static class Text_text_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -44,7 +43,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
    }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   public static class OnEnableHook
+   internal static class Text_OnEnable_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {

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

@@ -4,20 +4,20 @@ using System.Linq;
 using System.Reflection;
 using System.Text;
 using Harmony;
-using XUnity.AutoTranslator.Plugin.Core.UtageSupport;
+using XUnity.AutoTranslator.Plugin.Core.Utilities;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 {
-   public static class UtageHooks
+   internal static class UtageHooks
    {
       public static readonly Type[] All = new[] {
-         typeof( AdvCommand_ParseCellLocalizedTextHook ),
-         typeof( AdvEngine_JumpScenario ),
+         typeof( AdvCommand_ParseCellLocalizedText_Hook ),
+         typeof( AdvEngine_JumpScenario_Hook ),
       };
    }
 
    [Harmony]
-   public static class AdvCommand_ParseCellLocalizedTextHook
+   internal static class AdvCommand_ParseCellLocalizedText_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -40,7 +40,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    }
 
    [Harmony]
-   public static class AdvEngine_JumpScenario
+   internal static class AdvEngine_JumpScenario_Hook
    {
       static bool Prepare( HarmonyInstance instance )
       {
@@ -54,7 +54,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
       static void Prefix( ref string label )
       {
-         UtageHelpers.FixLabel( ref label );
+         UtageHelper.FixLabel( ref label );
       }
    }
 }

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

@@ -1,33 +0,0 @@
-using System;
-using System.Collections;
-using System.IO;
-using XUnity.AutoTranslator.Plugin.Core.Configuration;
-using XUnity.AutoTranslator.Plugin.Core.Extensions;
-
-namespace XUnity.AutoTranslator.Plugin.Core
-{
-   public interface IKnownEndpoint
-   {
-      /// <summary>
-      /// Attempt to translated the provided untranslated text. Will be used in a "coroutine", so it can be implemented
-      /// in an async fashion.
-      /// </summary>
-      IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action failure );
-
-      /// <summary>
-      /// Gets a boolean indicating if we are allowed to call "Translate".
-      /// </summary>
-      bool IsBusy { get; }
-
-      /// <summary>
-      /// "Update" game loop method.
-      /// </summary>
-      void OnUpdate();
-
-      /// <summary>
-      /// Gets a bool indicating if the plugin is capable of distinguishing between the untranslated text
-      /// on a line per line basis.
-      /// </summary>
-      bool SupportsLineSplitting { get; }
-   }
-}

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/ImageTranslationInfo.cs

@@ -1,6 +1,6 @@
 namespace XUnity.AutoTranslator.Plugin.Core
 {
-   public class ImageTranslationInfo
+   internal class ImageTranslationInfo
    {
       public bool IsTranslated { get; set; }
    }

+ 0 - 42
src/XUnity.AutoTranslator.Plugin.Core/KnownEndpoints.cs

@@ -1,42 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using XUnity.AutoTranslator.Plugin.Core.Configuration;
-using XUnity.AutoTranslator.Plugin.Core.Constants;
-using XUnity.AutoTranslator.Plugin.Core.Web;
-
-namespace XUnity.AutoTranslator.Plugin.Core
-{
-   public static class KnownEndpoints
-   {
-      public static IKnownEndpoint FindEndpoint( string identifier )
-      {
-         if( string.IsNullOrEmpty( identifier ) ) return null;
-
-         switch( identifier )
-         {
-            case KnownEndpointNames.GoogleTranslate:
-            case KnownEndpointNames.GoogleTranslateHack:
-               return new GoogleTranslateEndpoint();
-               //return new GoogleTranslateHackEndpoint();
-            case KnownEndpointNames.GoogleTranslateLegitimate:
-               return new GoogleTranslateLegitimateEndpoint( Settings.GoogleAPIKey );
-            case KnownEndpointNames.BaiduTranslate:
-               return new BaiduTranslateEndpoint( Settings.BaiduAppId, Settings.BaiduAppSecret );
-            case KnownEndpointNames.YandexTranslate:
-               return new YandexTranslateEndpoint( Settings.YandexAPIKey );
-            case KnownEndpointNames.WatsonTranslate:
-               return new WatsonTranslateEndpoint( Settings.WatsonAPIUrl, Settings.WatsonAPIUsername, Settings.WatsonAPIPassword );
-            case KnownEndpointNames.ExciteTranslate:
-               return new ExciteTranslateEndpoint();
-            //case KnownEndpointNames.BingTranslate:
-            //   return new BingTranslateEndpoint();
-            case KnownEndpointNames.BingTranslateLegitimate:
-               return new BingTranslateLegitimateEndpoint( Settings.BingOcpApimSubscriptionKey );
-            default:
-               return new DefaultEndpoint( identifier );
-         }
-      }
-   }
-}

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/MonoHttp/Helpers.cs

@@ -35,4 +35,4 @@ namespace RestSharp.Contrib
 	{
 		public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
 	}
-}
+}

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/MonoHttp/HtmlEncoder.cs

@@ -915,4 +915,4 @@ namespace RestSharp.Contrib
 			entities.Add("euro", '\u20AC');
 		}
 	}
-}
+}

+ 73 - 0
src/XUnity.AutoTranslator.Plugin.Core/Parsing/GameLogTextParser.cs

@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Parsing
+{
+   internal class GameLogTextParser : ITextParser
+   {
+      private Func<string, bool> _isTranslatable;
+
+      public GameLogTextParser( Func<string, bool> isTranslatable )
+      {
+         _isTranslatable = isTranslatable;
+      }
+
+      public bool CanApply( object ui )
+      {
+         return ui.SupportsLineParser();
+      }
+
+      public ParserResult Parse( string input )
+      {
+         var reader = new StringReader( input );
+         bool containsTranslatable = false;
+         //bool containsTranslated = false;
+         var template = new StringBuilder( input.Length );
+         var args = new Dictionary<string, string>();
+         var reverseArgs = new Dictionary<string, string>();
+         var arg = 'A';
+
+         string line = null;
+         while( ( line = reader.ReadLine() ) != null )
+         {
+            if( !string.IsNullOrEmpty( line ) )
+            {
+               if( _isTranslatable( line ) )
+               {
+                  // template it!
+                  containsTranslatable = true;
+                  if( reverseArgs.TryGetValue( line, out var existingKey ) )
+                  {
+                     template.Append( existingKey ).Append( '\n' );
+                  }
+                  else
+                  {
+                     var key = "{{" + ( arg++ ) + "}}";
+                     template.Append( key ).Append( '\n' );
+                     args.Add( key, line );
+                     reverseArgs[ line ] = key;
+                  }
+               }
+               else
+               {
+                  // add it
+                  //containsTranslated = true;
+                  template.Append( line ).Append( '\n' );
+               }
+            }
+            else
+            {
+               // add new line
+               template.Append( '\n' );
+            }
+         }
+
+         if( !input.EndsWith( "\r\n" ) && !input.EndsWith( "\n" ) ) template.Remove( template.Length - 1, 1 );
+
+         return new ParserResult( input, template.ToString(), containsTranslatable, false, args );
+      }
+   }
+}

+ 9 - 0
src/XUnity.AutoTranslator.Plugin.Core/Parsing/ITextParser.cs

@@ -0,0 +1,9 @@
+namespace XUnity.AutoTranslator.Plugin.Core.Parsing
+{
+   internal interface ITextParser
+   {
+      ParserResult Parse( string input );
+
+      bool CanApply( object ui );
+   }
+}

+ 7 - 4
src/XUnity.AutoTranslator.Plugin.Core/Parsing/ParserResult.cs

@@ -2,13 +2,14 @@
 
 namespace XUnity.AutoTranslator.Plugin.Core.Parsing
 {
-   public class ParserResult
+   internal class ParserResult
    {
-      public ParserResult( string originalText, string template, bool hasRichText, Dictionary<string, string> args )
+      public ParserResult( string originalText, string template, bool succeeded, bool persistCombinedResult, Dictionary<string, string> args )
       {
          OriginalText = originalText;
          Template = template;
-         HasRichSyntax = hasRichText;
+         Succeeded = succeeded;
+         PersistCombinedResult = persistCombinedResult;
          Arguments = args;
       }
 
@@ -18,7 +19,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
 
       public Dictionary<string, string> Arguments { get; private set; }
 
-      public bool HasRichSyntax { get; private set; }
+      public bool Succeeded { get; private set; }
+
+      public bool PersistCombinedResult { get; private set; }
 
       public string Untemplate( Dictionary<string, string> arguments )
       {

+ 9 - 2
src/XUnity.AutoTranslator.Plugin.Core/Parsing/RichTextParser.cs

@@ -1,10 +1,12 @@
 using System.Collections.Generic;
 using System.Text;
 using System.Text.RegularExpressions;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Parsing
 {
-   public class RichTextParser
+
+   internal class RichTextParser : ITextParser
    {
       private static readonly char[] TagNameEnders = new char[] { '=', ' ' };
       private static readonly Regex TagRegex = new Regex( "<.*?>" );
@@ -16,6 +18,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
 
       }
 
+      public bool CanApply( object ui )
+      {
+         return ui.SupportsRichText();
+      }
+
       public ParserResult Parse( string input )
       {
          var matches = TagRegex.Matches( input );
@@ -123,7 +130,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Parsing
             templateString = templateString.Replace( fullKey, newKey );
          }
 
-         return new ParserResult( input, templateString, arg != 'A', args );
+         return new ParserResult( input, templateString, arg != 'A', true, args );
       }
    }
 }

+ 6 - 4
src/XUnity.AutoTranslator.Plugin.Core/Parsing/UnityTextParsers.cs

@@ -6,13 +6,15 @@ using XUnity.AutoTranslator.Plugin.Core.Constants;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Parsing
 {
-   public static class UnityTextParsers
+   internal static class UnityTextParsers
    {
-      private static readonly RichTextParser RichTextParser = new RichTextParser();
+      public static RichTextParser RichTextParser;
+      public static GameLogTextParser GameLogTextParser;
 
-      public static RichTextParser GetTextParserByGameEngine()
+      public static void Initialize( Func<string, bool> isTranslatable )
       {
-         return RichTextParser;
+         RichTextParser = new RichTextParser();
+         GameLogTextParser = new GameLogTextParser( isTranslatable );
       }
    }
 }

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

@@ -33,6 +33,4 @@ namespace XUnity.AutoTranslator.Plugin.Core.Shim
 
       public abstract bool keepWaiting { get; }
    }
-
-
 }

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/TemplatedString.cs

@@ -2,7 +2,7 @@
 
 namespace XUnity.AutoTranslator.Plugin.Core
 {
-   public class TemplatedString
+   internal class TemplatedString
    {
       public TemplatedString( string template, Dictionary<string, string> arguments )
       {

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/TextTranslationInfo.cs

@@ -12,7 +12,7 @@ using XUnity.AutoTranslator.Plugin.Core.Fonts;
 namespace XUnity.AutoTranslator.Plugin.Core
 {
 
-   public class TextTranslationInfo
+   internal class TextTranslationInfo
    {
       private static readonly string MultiLinePropertyName = "multiLine";
       private static readonly string OverflowMethodPropertyName = "overflowMethod";

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/TextureHashGenerationStrategy.cs

@@ -1,6 +1,6 @@
 namespace XUnity.AutoTranslator.Plugin.Core
 {
-   public enum TextureHashGenerationStrategy
+   internal enum TextureHashGenerationStrategy
    {
       FromImageName,
       FromImageData,

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/TextureReloadContext.cs

@@ -3,7 +3,7 @@ using UnityEngine;
 
 namespace XUnity.AutoTranslator.Plugin.Core
 {
-   public class TextureReloadContext
+   internal class TextureReloadContext
    {
       private readonly HashSet<Texture2D> _textures;
 

+ 3 - 2
src/XUnity.AutoTranslator.Plugin.Core/TextureTranslationInfo.cs

@@ -5,10 +5,11 @@ using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
 using XUnity.AutoTranslator.Plugin.Core.Utilities;
+using XUnity.AutoTranslator.Plugin.Utilities;
 
 namespace XUnity.AutoTranslator.Plugin.Core
 {
-   public class TextureTranslationInfo
+   internal class TextureTranslationInfo
    {
       //private static readonly Dictionary<int, string> KnownHashes = new Dictionary<int, string>();
       private static readonly Encoding UTF8 = new UTF8Encoding( false );
@@ -113,7 +114,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                var name = texture.name; // name may be duplicate, WILL be duplicate!
                if( string.IsNullOrEmpty( name ) ) return;
 
-               name += "|" + SceneManagerEx.GetActiveSceneId();
+               name += "|" + SceneManagerHelper.GetActiveSceneId();
 
                _key = HashHelper.Compute( UTF8.GetBytes( name ) );
 

+ 21 - 0
src/XUnity.AutoTranslator.Plugin.Core/TranslationContext.cs

@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+using XUnity.AutoTranslator.Plugin.Core.Parsing;
+
+namespace XUnity.AutoTranslator.Plugin.Core
+{
+   internal class TranslationContext
+   {
+      public TranslationContext( object component, ParserResult result )
+      {
+         Jobs = new HashSet<TranslationJob>();
+         Component = component;
+         Result = result;
+      }
+
+      public ParserResult Result { get; private set; }
+
+      public HashSet<TranslationJob> Jobs { get; private set; }
+
+      public object Component { get; private set; }
+   }
+}

+ 1 - 25
src/XUnity.AutoTranslator.Plugin.Core/TranslationJob.cs

@@ -5,11 +5,10 @@ using System.Net;
 using System.Text;
 using UnityEngine.UI;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
-using XUnity.AutoTranslator.Plugin.Core.Parsing;
 
 namespace XUnity.AutoTranslator.Plugin.Core
 {
-   public class TranslationJob
+   internal class TranslationJob
    {
       public TranslationJob( TranslationKey key )
       {
@@ -66,27 +65,4 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
    }
-
-   public enum TranslationJobState
-   {
-      RunningOrQueued,
-      Succeeded,
-      Failed
-   }
-
-   public class TranslationContext
-   {
-      public TranslationContext( object component, ParserResult result )
-      {
-         Jobs = new HashSet<TranslationJob>();
-         Component = component;
-         Result = result;
-      }
-
-      public ParserResult Result { get; private set; }
-
-      public HashSet<TranslationJob> Jobs { get; private set; }
-
-      public object Component { get; private set; }
-   }
 }

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

@@ -0,0 +1,9 @@
+namespace XUnity.AutoTranslator.Plugin.Core
+{
+   internal enum TranslationJobState
+   {
+      RunningOrQueued,
+      Succeeded,
+      Failed
+   }
+}

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/TranslationKey.cs

@@ -7,7 +7,7 @@ using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
 namespace XUnity.AutoTranslator.Plugin.Core
 {
-   public struct TranslationKey
+   internal struct TranslationKey
    {
       public TranslationKey( object ui, string key, bool templatizeByNumbers, bool neverRemoveWhitespace = false )
       {

+ 21 - 0
src/XUnity.AutoTranslator.Plugin.Core/UI/ButtonViewModel.cs

@@ -0,0 +1,21 @@
+using System;
+using UnityEngine;
+
+namespace XUnity.AutoTranslator.Plugin.Core.UI
+{
+   internal class ButtonViewModel
+   {
+      public ButtonViewModel( string text, string tooltip, Action onClicked, Func<bool> canClick )
+      {
+         Text = new GUIContent( text, tooltip );
+         OnClicked = onClicked;
+         CanClick = canClick;
+      }
+
+      public GUIContent Text { get; set; }
+
+      public Action OnClicked { get; set; }
+
+      public Func<bool> CanClick { get; set; }
+   }
+}

+ 93 - 0
src/XUnity.AutoTranslator.Plugin.Core/UI/DropdownGUI.cs

@@ -0,0 +1,93 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace XUnity.AutoTranslator.Plugin.Core.UI
+{
+   internal class DropdownGUI<TDropdownOptionViewModel, TSelection>
+      where TDropdownOptionViewModel : DropdownOptionViewModel<TSelection>
+   {
+
+      private const int MaxHeight = GUIUtil.RowHeight * 5;
+
+      private GUIContent _noSelection;
+      private List<TDropdownOptionViewModel> _options;
+      private TDropdownOptionViewModel _currentSelection;
+
+      private int _x;
+      private int _y;
+      private int _width;
+      private bool _isShown;
+      private Vector2 _scrollPosition;
+
+      public DropdownGUI( int x, int y, int width, IEnumerable<TDropdownOptionViewModel> options )
+      {
+         _x = x;
+         _y = y;
+         _width = width;
+         _noSelection = new GUIContent( "----", "SELECT TRANSLATOR. No translator is currently selected, which means no new translations will be performed. Please select one from the dropdown." );
+
+         _options = new List<TDropdownOptionViewModel>();
+         foreach( var item in options )
+         {
+            if( item.IsSelected() )
+            {
+               _currentSelection = item;
+            }
+            _options.Add( item );
+         }
+      }
+
+      public void Select( TDropdownOptionViewModel option )
+      {
+         if( option.IsSelected() ) return;
+
+         _currentSelection = option;
+         _currentSelection.OnSelected?.Invoke( _currentSelection.Selection );
+      }
+
+      public void OnGUI()
+      {
+         bool clicked = GUI.Button( GUIUtil.R( _x, _y, _width, GUIUtil.RowHeight ), _currentSelection?.Text ?? _noSelection, _isShown ? GUIUtil.NoMarginButtonPressedStyle : GUI.skin.button );
+         if( clicked )
+         {
+            _isShown = !_isShown;
+         }
+
+         if( _isShown )
+         {
+            _scrollPosition = ShowDropdown( _x, _y + GUIUtil.RowHeight, _width, GUI.skin.button, _scrollPosition );
+         }
+
+         if( !clicked && Event.current.isMouse )
+         {
+            _isShown = false;
+         }
+      }
+
+      private Vector2 ShowDropdown( int x, int y, int width, GUIStyle buttonStyle, Vector2 scrollPosition )
+      {
+         var rect = GUIUtil.R( x, y, width, _options.Count * GUIUtil.RowHeight > MaxHeight ? MaxHeight : _options.Count * GUIUtil.RowHeight );
+
+         GUILayout.BeginArea( rect, GUIUtil.NoSpacingBoxStyle );
+         scrollPosition = GUILayout.BeginScrollView( scrollPosition );
+
+         foreach( var option in _options )
+         {
+            var style = option.IsSelected() ? GUIUtil.NoMarginButtonPressedStyle : GUIUtil.NoMarginButtonStyle;
+
+            GUI.enabled = option?.IsEnabled() ?? true;
+            if( GUILayout.Button( option.Text, style ) )
+            {
+               Select( option );
+               _isShown = false;
+            }
+            GUI.enabled = true;
+         }
+
+         GUILayout.EndScrollView();
+         GUILayout.EndArea();
+
+         return scrollPosition;
+      }
+   }
+}

+ 61 - 0
src/XUnity.AutoTranslator.Plugin.Core/UI/DropdownOptionViewModel.cs

@@ -0,0 +1,61 @@
+using System;
+using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Endpoints;
+
+namespace XUnity.AutoTranslator.Plugin.Core.UI
+{
+   internal class DropdownOptionViewModel<TSelection>
+   {
+      public DropdownOptionViewModel( string text, Func<bool> isSelected, Func<bool> isEnabled, TSelection selection, Action<TSelection> onSelected )
+      {
+         Text = new GUIContent( text );
+         IsSelected = isSelected;
+         IsEnabled = isEnabled;
+         Selection = selection;
+         OnSelected = onSelected;
+      }
+
+      public virtual GUIContent Text { get; set; }
+
+      public Func<bool> IsEnabled { get; set; }
+
+      public Func<bool> IsSelected { get; set; }
+
+      public TSelection Selection { get; set; }
+
+      public Action<TSelection> OnSelected { get; set; }
+   }
+
+   internal class TranslatorDropdownOptionViewModel : DropdownOptionViewModel<ConfiguredEndpoint>
+   {
+      private GUIContent _selected;
+      private GUIContent _normal;
+      private GUIContent _disabled;
+
+      public TranslatorDropdownOptionViewModel( Func<bool> isSelected, ConfiguredEndpoint selection, Action<ConfiguredEndpoint> onSelected ) : base( selection.Endpoint.FriendlyName, isSelected, () => selection.Error == null, selection, onSelected )
+      {
+         _selected = new GUIContent( selection.Endpoint.FriendlyName, $"<b>CURRENT TRANSLATOR</b>\n{selection.Endpoint.FriendlyName} is the currently selected translator that will be used to perform translations." );
+         _disabled = new GUIContent( selection.Endpoint.FriendlyName, $"<b>CANNOT SELECT TRANSLATOR</b>\n{selection.Endpoint.FriendlyName} cannot be selected because misses vital configuration. {selection.Error?.Message}" );
+         _normal = new GUIContent( selection.Endpoint.FriendlyName, $"<b>SELECT TRANSLATOR</b>\n{selection.Endpoint.FriendlyName} will be selected as translator." );
+      }
+
+      public override GUIContent Text
+      {
+         get
+         {
+            if( Selection.Error != null )
+            {
+               return _disabled;
+            }
+            else if( IsSelected() )
+            {
+               return _selected;
+            }
+            else
+            {
+               return _normal;
+            }
+         }
+      }
+   }
+}

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

@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+
+namespace XUnity.AutoTranslator.Plugin.Core.UI
+{
+   internal static class GUIUtil
+   {
+      public const int WindowTitleClearance = 15;
+      public const int ComponentSpacing = 10;
+      public const int LabelWidth = 60;
+      public const int LabelHeight = 21;
+      public const int RowHeight = 21;
+
+      public static readonly RectOffset Empty = new RectOffset( 0, 0, 0, 0 );
+
+      public static readonly GUIStyle LabelCenter = new GUIStyle( GUI.skin.label )
+      {
+         alignment = TextAnchor.UpperCenter,
+         richText = true
+      };
+
+      public static readonly GUIStyle LabelRight = new GUIStyle( GUI.skin.label )
+      {
+         alignment = TextAnchor.UpperRight
+      };
+
+      public static readonly GUIStyle LabelRich = new GUIStyle( GUI.skin.label )
+      {
+         richText = true
+      };
+
+      public static readonly GUIStyle NoMarginButtonStyle = new GUIStyle( GUI.skin.button ) { margin = Empty };
+
+      public static readonly GUIStyle NoMarginButtonPressedStyle = new GUIStyle( GUI.skin.button )
+      {
+         margin = Empty,
+         onNormal = GUI.skin.button.onActive,
+         onFocused = GUI.skin.button.onActive,
+         onHover = GUI.skin.button.onActive,
+         normal = GUI.skin.button.onActive,
+         focused = GUI.skin.button.onActive,
+         hover = GUI.skin.button.onActive,
+      };
+
+      public static readonly GUIStyle NoSpacingBoxStyle = new GUIStyle( GUI.skin.box )
+      {
+         margin = Empty,
+         padding = Empty
+      };
+
+      public static Rect R( int x, int y, int width, int height ) => new Rect( x, y, width, height );
+   }
+}

+ 17 - 0
src/XUnity.AutoTranslator.Plugin.Core/UI/LabelViewModel.cs

@@ -0,0 +1,17 @@
+using System;
+
+namespace XUnity.AutoTranslator.Plugin.Core.UI
+{
+   internal class LabelViewModel
+   {
+      public LabelViewModel( string title, Func<string> getValue )
+      {
+         Title = title;
+         GetValue = getValue;
+      }
+
+      public string Title { get; set; }
+
+      public Func<string> GetValue { get; set; }
+   }
+}

+ 35 - 0
src/XUnity.AutoTranslator.Plugin.Core/UI/ToggleViewModel.cs

@@ -0,0 +1,35 @@
+using System;
+using UnityEngine;
+
+namespace XUnity.AutoTranslator.Plugin.Core.UI
+{
+   internal class ToggleViewModel
+   {
+      private GUIContent _enabled;
+      private GUIContent _disabled;
+
+      public ToggleViewModel( string text, string enabledTooltip, string disabledTooltip, Action onToggled, Func<bool> isToggled )
+      {
+         _enabled = new GUIContent( text, enabledTooltip );
+         _disabled = new GUIContent( text, disabledTooltip );
+         OnToggled = onToggled;
+         IsToggled = isToggled;
+      }
+
+      public GUIContent Text
+      {
+         get
+         {
+            if( IsToggled() )
+            {
+               return _enabled;
+            }
+            return _disabled;
+         }
+      }
+
+      public Action OnToggled { get; set; }
+
+      public Func<bool> IsToggled { get; set; }
+   }
+}

+ 129 - 0
src/XUnity.AutoTranslator.Plugin.Core/UI/XuaWindow.cs

@@ -0,0 +1,129 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Endpoints;
+
+namespace XUnity.AutoTranslator.Plugin.Core.UI
+{
+   internal class XuaWindow
+   {
+      private const int WindowHeight = 510;
+      private const int WindowWidth = 320;
+
+      private const int AvailableWidth = WindowWidth - ( GUIUtil.ComponentSpacing * 2 );
+      private const int AvailableHeight = WindowHeight - GUIUtil.WindowTitleClearance - ( GUIUtil.ComponentSpacing * 2 );
+
+      private Rect _windowRect = new Rect( 20, 20, WindowWidth, WindowHeight );
+
+      private DropdownGUI<TranslatorDropdownOptionViewModel, ConfiguredEndpoint> _endpointDropdown;
+
+      private List<ToggleViewModel> _toggles;
+      private List<TranslatorDropdownOptionViewModel> _endpointOptions;
+      private List<ButtonViewModel> _commandButtons;
+      private List<LabelViewModel> _labels;
+
+      public bool IsShown { get; set; }
+
+      public XuaWindow(
+         List<ToggleViewModel> toggles,
+         List<TranslatorDropdownOptionViewModel> endpoints,
+         List<ButtonViewModel> commandButtons,
+         List<LabelViewModel> labels )
+      {
+         _toggles = toggles;
+         _endpointOptions = endpoints;
+         _commandButtons = commandButtons;
+         _labels = labels;
+      }
+
+      public void OnGUI()
+      {
+         _windowRect = GUI.Window( 5464332, _windowRect, CreateWindowUI, "---- XUnity.AutoTranslator UI ----" );
+      }
+
+      private void CreateWindowUI( int id )
+      {
+         int posx = GUIUtil.ComponentSpacing;
+         int posy = GUIUtil.WindowTitleClearance + GUIUtil.ComponentSpacing;
+         const int col1 = GUIUtil.LabelWidth;
+         const int col2 = WindowWidth - GUIUtil.LabelWidth - ( 3 * GUIUtil.ComponentSpacing );
+         const int col1x = GUIUtil.ComponentSpacing;
+         const int col2x = GUIUtil.LabelWidth + ( GUIUtil.ComponentSpacing * 2 );
+         const int col12 = WindowWidth - ( 2 * GUIUtil.ComponentSpacing );
+
+         if( GUI.Button( GUIUtil.R( WindowWidth - 22, 2, 20, 16 ), "X" ) )
+         {
+            IsShown = false;
+         }
+
+         foreach( var vm in _toggles )
+         {
+            var previousValue = vm.IsToggled();
+            var newValue = GUI.Toggle( GUIUtil.R( col1x, posy, col12, GUIUtil.RowHeight ), previousValue, vm.Text );
+            if( previousValue != newValue )
+            {
+               vm.OnToggled();
+            }
+            posy += GUIUtil.RowHeight + GUIUtil.ComponentSpacing;
+         }
+
+         GUI.Label( GUIUtil.R( col1x, posy, col12, GUIUtil.LabelHeight ), "---- Command Panel ----", GUIUtil.LabelCenter );
+         posy += GUIUtil.RowHeight + GUIUtil.ComponentSpacing;
+
+         const int buttonsPerRow = 3;
+         const int buttonWidth = ( col12 - ( GUIUtil.ComponentSpacing * ( buttonsPerRow - 1 ) ) ) / buttonsPerRow;
+         var rows = _commandButtons.Count / buttonsPerRow;
+         if( _commandButtons.Count % 3 != 0 ) rows++;
+         for( int row = 0 ; row < rows ; row++ )
+         {
+            for( int col = 0 ; col < buttonsPerRow ; col++ )
+            {
+               int idx = ( row * buttonsPerRow ) + col;
+               if( idx >= _commandButtons.Count ) break;
+
+               var vm = _commandButtons[ idx ];
+
+               GUI.enabled = vm.CanClick?.Invoke() != false;
+               if( GUI.Button( GUIUtil.R( posx, posy, buttonWidth, GUIUtil.RowHeight ), vm.Text ) )
+               {
+                  vm.OnClicked?.Invoke();
+               }
+               GUI.enabled = true;
+
+               posx += GUIUtil.ComponentSpacing + buttonWidth;
+            }
+            posy += GUIUtil.RowHeight + GUIUtil.ComponentSpacing;
+         }
+
+
+
+         GUI.Label( GUIUtil.R( col1x, posy, col12, GUIUtil.LabelHeight ), "---- Select a Translator ----", GUIUtil.LabelCenter );
+         posy += GUIUtil.RowHeight + GUIUtil.ComponentSpacing;
+
+         GUI.Label( GUIUtil.R( col1x, posy, GUIUtil.LabelWidth, GUIUtil.LabelHeight ), "Translator: " );
+         int endpointDropdownPosy = posy;
+         posy += GUIUtil.RowHeight + GUIUtil.ComponentSpacing;
+
+         GUI.Label( GUIUtil.R( col1x, posy, col12, GUIUtil.LabelHeight ), "---- Status ----", GUIUtil.LabelCenter );
+         posy += GUIUtil.RowHeight + GUIUtil.ComponentSpacing;
+
+         foreach( var label in _labels )
+         {
+            GUI.Label( GUIUtil.R( col1x, posy, col12, GUIUtil.LabelHeight ), label.Title );
+            GUI.Label( GUIUtil.R( col2x, posy, col2, GUIUtil.LabelHeight ), label.GetValue(), GUIUtil.LabelRight );
+            posy += GUIUtil.RowHeight + GUIUtil.ComponentSpacing;
+         }
+
+         GUI.Label( GUIUtil.R( col1x, posy, col12, GUIUtil.LabelHeight ), "<b>_______________________________________</b>", GUIUtil.LabelCenter );
+         posy += GUIUtil.RowHeight + GUIUtil.ComponentSpacing;
+
+         var endpointDropdown = _endpointDropdown ?? ( _endpointDropdown = new DropdownGUI<TranslatorDropdownOptionViewModel, ConfiguredEndpoint>( col2x, endpointDropdownPosy, col2, _endpointOptions ) );
+         endpointDropdown.OnGUI();
+
+         GUI.Label( GUIUtil.R( col1x, posy, col12, GUIUtil.RowHeight * 5 ), GUI.tooltip, GUIUtil.LabelRich );
+
+         GUI.DragWindow();
+      }
+   }
+}

+ 20 - 0
src/XUnity.AutoTranslator.Plugin.Core/Utilities/DirectoryHelper.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Utilities
+{
+   internal static class DirectoryHelper
+   {
+      public static string Parameterize( this string path )
+      {
+         if( Settings.ApplicationName != null )
+         {
+            return path.Replace( "{GameExeName}", Settings.ApplicationName );
+         }
+         return path;
+      }
+   }
+}

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/Utilities/HashHelper.cs

@@ -6,7 +6,7 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Utilities
 {
-   public static class HashHelper
+   internal static class HashHelper
    {
       private static readonly SHA1Managed SHA1 = new SHA1Managed();
       private static readonly uint[] Lookup32 = CreateLookup32();

+ 2 - 2
src/XUnity.AutoTranslator.Plugin.Core/Files/IndentedTextWriter.cs → src/XUnity.AutoTranslator.Plugin.Core/Utilities/IndentedTextWriter.cs

@@ -4,9 +4,9 @@ using System.IO;
 using System.Linq;
 using System.Text;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Files
+namespace XUnity.AutoTranslator.Plugin.Core.Utilties
 {
-   public class IndentedTextWriter
+   internal class IndentedTextWriter
    {
       private readonly TextWriter _writer;
       private readonly char _indent;

+ 122 - 0
src/XUnity.AutoTranslator.Plugin.Core/Utilities/LanguageHelper.cs

@@ -0,0 +1,122 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Utilities
+{
+   internal static class LanguageHelper
+   {
+      private static readonly Dictionary<string, Func<string, bool>> LanguageSymbolChecks = new Dictionary<string, Func<string, bool>>( StringComparer.OrdinalIgnoreCase )
+      {
+         { "ja", ContainsJapaneseSymbols },
+         { "ru", ContainsRussianSymbols },
+         { "zh-CN", ContainsChineseSymbols },
+         { "zh-TW", ContainsChineseSymbols },
+         { "ko", ContainsKoreanSymbols },
+         { "en", ContainsStandardLatinSymbols },
+      };
+
+      private static readonly HashSet<string> WhitespaceLanguages = new HashSet<string>
+      {
+         "ru", "ko", "en"
+      };
+
+      public static bool IsFromLanguageSupported( string code )
+      {
+         return LanguageSymbolChecks.ContainsKey( code );
+      }
+
+      public static bool RequiresWhitespaceUponLineMerging( string code )
+      {
+         return WhitespaceLanguages.Contains( code );
+      }
+
+      public static Func<string, bool> GetSymbolCheck( string language )
+      {
+         if( LanguageSymbolChecks.TryGetValue( language, out Func<string, bool> check ) )
+         {
+            return check;
+         }
+         return text => true;
+      }
+
+      public static bool ContainsJapaneseSymbols( string text )
+      {
+         // Unicode Kanji Table:
+         // http://www.rikai.com/library/kanjitables/kanji_codes.unicode.shtml
+         foreach( var c in text )
+         {
+            if( ( c >= '\u3021' && c <= '\u3029' ) // kana-like symbols
+               || ( c >= '\u3031' && c <= '\u3035' ) // kana-like symbols
+               || ( c >= '\u3041' && c <= '\u3096' ) // hiragana
+               || ( c >= '\u30a1' && c <= '\u30fa' ) // katakana
+               || ( c >= '\uff66' && c <= '\uff9d' ) // half-width katakana
+               || ( c >= '\u4e00' && c <= '\u9faf' ) // CJK unifed ideographs - Common and uncommon kanji
+               || ( c >= '\u3400' && c <= '\u4dbf' ) // CJK unified ideographs Extension A - Rare kanji ( 3400 - 4dbf)
+               || ( c >= '\uf900' && c <= '\ufaff' ) ) // CJK Compatibility Ideographs
+            {
+               return true;
+            }
+         }
+         return false;
+      }
+
+      public static bool ContainsKoreanSymbols( string text )
+      {
+         foreach( var c in text )
+         {
+            if( ( c >= '\uac00' && c <= '\ud7af' ) ) // Hangul Syllables
+            {
+               return true;
+            }
+         }
+         return false;
+      }
+
+      public static bool ContainsChineseSymbols( string text )
+      {
+         foreach( var c in text )
+         {
+            if( ( c >= '\u4e00' && c <= '\u9faf' )
+               || ( c >= '\u3400' && c <= '\u4dbf' )
+               || ( c >= '\uf900' && c <= '\ufaff' ) )
+            {
+               return true;
+            }
+         }
+         return false;
+      }
+
+      public static bool ContainsRussianSymbols( string text )
+      {
+         foreach( var c in text )
+         {
+            if( ( c >= '\u0400' && c <= '\u04ff' )
+               || ( c >= '\u0500' && c <= '\u052f' )
+               || ( c >= '\u2de0' && c <= '\u2dff' )
+               || ( c >= '\ua640' && c <= '\ua69f' )
+               || ( c >= '\u1c80' && c <= '\u1c88' )
+               || ( c >= '\ufe2e' && c <= '\ufe2f' )
+               || ( c == '\u1d2b' || c == '\u1d78' ) )
+            {
+               return true;
+            }
+         }
+         return false;
+      }
+
+      public static bool ContainsStandardLatinSymbols( string text )
+      {
+         foreach( var c in text )
+         {
+            if( ( c >= '\u0041' && c <= '\u005a' )
+               || ( c >= '\u0061' && c <= '\u007a' ) )
+            {
+               return true;
+            }
+         }
+         return false;
+      }
+   }
+}

+ 3 - 84
src/XUnity.AutoTranslator.Plugin.Core/Extensions/ObjectExtensions.cs → src/XUnity.AutoTranslator.Plugin.Core/Utilities/ObjectReferenceMapper.cs

@@ -8,96 +8,17 @@ using UnityEngine.UI;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Utilities;
 using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
-namespace XUnity.AutoTranslator.Plugin.Core.Extensions
+namespace XUnity.AutoTranslator.Plugin.Core.Utilities
 {
-   public static class ObjectExtensions
+   internal static class ObjectReferenceMapper
    {
-      private static readonly string RichTextPropertyName = "richText";
-
       private static readonly object Sync = new object();
       private static readonly WeakDictionary<object, object> DynamicFields = new WeakDictionary<object, object>();
 
-      public static bool IsKnownTextType( this object ui )
-      {
-         if( ui == null ) return false;
-
-         var type = ui.GetType();
-
-         return ( Settings.EnableUGUI && ui is Text )
-            || ( 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 ) );
-      }
-
-      public static bool IsKnownImageType( this object ui )
-      {
-         var type = ui.GetType();
-
-         return ( ui is Material || ui is Image || ui is RawImage || ui is SpriteRenderer )
-            || ( ClrTypes.CubismRenderer != null && ClrTypes.CubismRenderer.IsAssignableFrom( type ) )
-            || ( ClrTypes.UIWidget != null && type != ClrTypes.UILabel && ClrTypes.UIWidget.IsAssignableFrom( type ) )
-            || ( ClrTypes.UIAtlas != null && ClrTypes.UIAtlas.IsAssignableFrom( type ) )
-            || ( ClrTypes.UITexture != null && ClrTypes.UITexture.IsAssignableFrom( type ) )
-            || ( ClrTypes.UIPanel != null && ClrTypes.UIPanel.IsAssignableFrom( type ) );
-      }
-
-      public static bool SupportsStabilization( this object ui )
-      {
-         if( ui == null ) return false;
-
-         // shortcircuit for spammy component, to avoid reflective calls
-         if( ui is GUIContent ) return false;
-
-         var type = ui.GetType();
-
-         return ui is Text
-            || ( ClrTypes.UILabel != null && ClrTypes.UILabel.IsAssignableFrom( type ) )
-            || ( ClrTypes.TMP_Text != null && ClrTypes.TMP_Text.IsAssignableFrom( type ) );
-      }
-
-      public static bool SupportsRichText( this object ui )
-      {
-         if( ui == null ) return false;
-
-         var type = ui.GetType();
-
-         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 ) );
-      }
-
-      public static bool IsSpammingComponent( this object ui )
-      {
-         if( ui == null ) return false;
-
-         return ui is UnityEngine.GUIContent;
-      }
-
-      public static bool IsWhitelistedForImmediateRichTextTranslation( this object ui )
-      {
-         if( ui == null ) return false;
-
-         var type = ui.GetType();
-
-         return ClrTypes.AdvCommand != null && ClrTypes.AdvCommand.IsAssignableFrom( type );
-      }
-
-      public static bool IsNGUI( this object ui )
-      {
-         if( ui == null ) return false;
-
-         var type = ui.GetType();
-
-         return ClrTypes.UILabel != null && ClrTypes.UILabel.IsAssignableFrom( type );
-      }
-
       public static TextTranslationInfo GetOrCreateTextTranslationInfo( this object obj )
       {
-         if( !Settings.EnableObjectTracking ) return null;
-
          if( !obj.SupportsStabilization() ) return null;
 
          var info = obj.GetOrCreate<TextTranslationInfo>();
@@ -107,8 +28,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 
       public static TextTranslationInfo GetTextTranslationInfo( this object obj )
       {
-         if( !Settings.EnableObjectTracking ) return null;
-
          if( !obj.SupportsStabilization() ) return null;
 
          var info = obj.Get<TextTranslationInfo>();

+ 3 - 2
src/XUnity.AutoTranslator.Plugin.Core/SceneManagerEx.cs → src/XUnity.AutoTranslator.Plugin.Core/Utilities/SceneManagerHelper.cs

@@ -4,10 +4,11 @@ using System.Linq;
 using System.Text;
 using UnityEngine;
 using UnityEngine.SceneManagement;
+using XUnity.AutoTranslator.Plugin.Core;
 
-namespace XUnity.AutoTranslator.Plugin.Core
+namespace XUnity.AutoTranslator.Plugin.Utilities
 {
-   public static class SceneManagerEx
+   internal static class SceneManagerHelper
    {
       public static string GetActiveSceneId()
       {

+ 3 - 3
src/XUnity.AutoTranslator.Plugin.Core/TextGetterCompatMode.cs → src/XUnity.AutoTranslator.Plugin.Core/Utilities/TextGetterCompatModeHelper.cs

@@ -8,11 +8,11 @@ using System.Text;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
-namespace XUnity.AutoTranslator.Plugin.Core
+namespace XUnity.AutoTranslator.Plugin.Core.Utilities
 {
-   public static class TextGetterCompatMode
+   internal static class TextGetterCompatModeHelper
    {
-      private static readonly Assembly XUnityAutoTranslatorAssembly = typeof( TextGetterCompatMode ).Assembly;
+      private static readonly Assembly XUnityAutoTranslatorAssembly = typeof( TextGetterCompatModeHelper ).Assembly;
 
       [MethodImpl( MethodImplOptions.NoInlining )]
       public static void ReplaceTextWithOriginal( object instance, ref string __result )

+ 127 - 112
src/XUnity.AutoTranslator.Plugin.Core/Utilities/TextHelper.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Text;
 
@@ -7,118 +8,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Utilities
 {
    public static class TextHelper
    {
-      private static readonly Dictionary<string, Func<string, bool>> LanguageSymbolChecks = new Dictionary<string, Func<string, bool>>( StringComparer.OrdinalIgnoreCase )
-      {
-         { "ja", ContainsJapaneseSymbols },
-         { "ru", ContainsRussianSymbols },
-         { "zh-CN", ContainsChineseSymbols },
-         { "zh-TW", ContainsChineseSymbols },
-         { "ko", ContainsKoreanSymbols },
-         { "en", ContainsStandardLatinSymbols },
-      };
-
-      private static readonly HashSet<string> WhitespaceLanguages = new HashSet<string>
-      {
-         "ru", "ko", "en"
-      };
-
-      public static bool IsFromLanguageSupported( string code )
-      {
-         return LanguageSymbolChecks.ContainsKey( code );
-      }
-
-      public static bool RequiresWhitespaceUponLineMerging( string code )
-      {
-         return WhitespaceLanguages.Contains( code );
-      }
-
-      public static Func<string, bool> GetSymbolCheck( string language )
-      {
-         if( LanguageSymbolChecks.TryGetValue( language, out Func<string, bool> check ) )
-         {
-            return check;
-         }
-         return text => true;
-      }
-
-      public static bool ContainsJapaneseSymbols( string text )
-      {
-         // Unicode Kanji Table:
-         // http://www.rikai.com/library/kanjitables/kanji_codes.unicode.shtml
-         foreach( var c in text )
-         {
-            if( ( c >= '\u3021' && c <= '\u3029' ) // kana-like symbols
-               || ( c >= '\u3031' && c <= '\u3035' ) // kana-like symbols
-               || ( c >= '\u3041' && c <= '\u3096' ) // hiragana
-               || ( c >= '\u30a1' && c <= '\u30fa' ) // katakana
-               || ( c >= '\uff66' && c <= '\uff9d' ) // half-width katakana
-               || ( c >= '\u4e00' && c <= '\u9faf' ) // CJK unifed ideographs - Common and uncommon kanji
-               || ( c >= '\u3400' && c <= '\u4dbf' ) // CJK unified ideographs Extension A - Rare kanji ( 3400 - 4dbf)
-               || ( c >= '\uf900' && c <= '\ufaff' ) ) // CJK Compatibility Ideographs
-            {
-               return true;
-            }
-         }
-         return false;
-      }
-
-      public static bool ContainsKoreanSymbols( string text )
-      {
-         foreach( var c in text )
-         {
-            if( ( c >= '\uac00' && c <= '\ud7af' ) ) // Hangul Syllables
-            {
-               return true;
-            }
-         }
-         return false;
-      }
-
-      public static bool ContainsChineseSymbols( string text )
-      {
-         foreach( var c in text )
-         {
-            if( ( c >= '\u4e00' && c <= '\u9faf' )
-               || ( c >= '\u3400' && c <= '\u4dbf' )
-               || ( c >= '\uf900' && c <= '\ufaff' ) )
-            {
-               return true;
-            }
-         }
-         return false;
-      }
-
-      public static bool ContainsRussianSymbols( string text )
-      {
-         foreach( var c in text )
-         {
-            if( ( c >= '\u0400' && c <= '\u04ff' )
-               || ( c >= '\u0500' && c <= '\u052f' )
-               || ( c >= '\u2de0' && c <= '\u2dff' )
-               || ( c >= '\ua640' && c <= '\ua69f' )
-               || ( c >= '\u1c80' && c <= '\u1c88' )
-               || ( c >= '\ufe2e' && c <= '\ufe2f' )
-               || ( c == '\u1d2b' || c == '\u1d78' ) )
-            {
-               return true;
-            }
-         }
-         return false;
-      }
-
-      public static bool ContainsStandardLatinSymbols( string text )
-      {
-         foreach( var c in text )
-         {
-            if( ( c >= '\u0041' && c <= '\u005a' )
-               || ( c >= '\u0061' && c <= '\u007a' ) )
-            {
-               return true;
-            }
-         }
-         return false;
-      }
-
       /// <summary>
       /// Decodes a text from a single-line serializable format.
       /// 
@@ -150,5 +39,131 @@ namespace XUnity.AutoTranslator.Plugin.Core.Utilities
             .Replace( "=", "%3D" )
             .Replace( "//", "%2F%2F" );
       }
+
+      public static string UnescapeJson( this string str )
+      {
+         if( str == null ) return null;
+
+         var builder = new StringBuilder( str );
+
+         bool escapeNext = false;
+         for( int i = 0 ; i < builder.Length ; i++ )
+         {
+            var c = builder[ i ];
+            if( escapeNext )
+            {
+               bool found = true;
+               char escapeWith = default( char );
+               switch( c )
+               {
+                  case 'b':
+                     escapeWith = '\b';
+                     break;
+                  case 'f':
+                     escapeWith = '\f';
+                     break;
+                  case 'n':
+                     escapeWith = '\n';
+                     break;
+                  case 'r':
+                     escapeWith = '\r';
+                     break;
+                  case 't':
+                     escapeWith = '\t';
+                     break;
+                  case '"':
+                     escapeWith = '\"';
+                     break;
+                  case '\\':
+                     escapeWith = '\\';
+                     break;
+                  case 'u':
+                     escapeWith = 'u';
+                     break;
+                  default:
+                     found = false;
+                     break;
+               }
+
+               // remove previous char and go one back
+               if( found )
+               {
+                  if( escapeWith == 'u' )
+                  {
+                     // unicode crap, lets handle the next 4 characters manually
+                     int code = int.Parse( new string( new char[] { builder[ i + 1 ], builder[ i + 2 ], builder[ i + 3 ], builder[ i + 4 ] } ), NumberStyles.HexNumber );
+                     var replacingChar = (char)code;
+                     builder.Remove( --i, 6 );
+                     builder.Insert( i, replacingChar );
+                  }
+                  else
+                  {
+                     // found proper escaping
+                     builder.Remove( --i, 2 );
+                     builder.Insert( i, escapeWith );
+                  }
+               }
+               else
+               {
+                  // dont do anything
+               }
+
+               escapeNext = false;
+            }
+            else if( c == '\\' )
+            {
+               escapeNext = true;
+            }
+         }
+
+         return builder.ToString();
+      }
+
+      public static string EscapeJson( this string str )
+      {
+         if( str == null || str.Length == 0 )
+         {
+            return "";
+         }
+
+         char c;
+         int len = str.Length;
+         StringBuilder sb = new StringBuilder( len + 4 );
+         for( int i = 0 ; i < len ; i += 1 )
+         {
+            c = str[ i ];
+            switch( c )
+            {
+               case '\\':
+               case '"':
+                  sb.Append( '\\' );
+                  sb.Append( c );
+                  break;
+               case '/':
+                  sb.Append( '\\' );
+                  sb.Append( c );
+                  break;
+               case '\b':
+                  sb.Append( "\\b" );
+                  break;
+               case '\t':
+                  sb.Append( "\\t" );
+                  break;
+               case '\n':
+                  sb.Append( "\\n" );
+                  break;
+               case '\f':
+                  sb.Append( "\\f" );
+                  break;
+               case '\r':
+                  sb.Append( "\\r" );
+                  break;
+               default:
+                  sb.Append( c );
+                  break;
+            }
+         }
+         return sb.ToString();
+      }
    }
 }

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

@@ -8,7 +8,7 @@ using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Utilities
 {
-   public static class TextureHelper
+   internal static class TextureHelper
    {
       private static readonly Color Transparent = new Color( 0, 0, 0, 0 );
       
@@ -52,7 +52,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Utilities
       }
    }
 
-   public struct TextureDataResult
+   internal struct TextureDataResult
    {
       public TextureDataResult( byte[] data, bool nonReadable, float calculationTime )
       {

+ 2 - 2
src/XUnity.AutoTranslator.Plugin.Core/UtageSupport/UtageHelpers.cs → src/XUnity.AutoTranslator.Plugin.Core/Utilities/UtageHelper.cs

@@ -5,9 +5,9 @@ using System.Linq;
 using System.Text;
 using UnityEngine;
 
-namespace XUnity.AutoTranslator.Plugin.Core.UtageSupport
+namespace XUnity.AutoTranslator.Plugin.Core.Utilities
 {
-   public static class UtageHelpers
+   internal static class UtageHelper
    {
       private static object AdvManager;
       private static HashSet<string> Labels = new HashSet<string>();

+ 129 - 40
src/XUnity.AutoTranslator.Plugin.Core/Web/MyWebClient.cs → src/XUnity.AutoTranslator.Plugin.Core/Web/ConnectionTrackingWebClient.cs

@@ -13,14 +13,14 @@ using System.Threading;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Web
 {
-   public delegate void MyDownloadStringCompletedEventHandler( object sender, MyDownloadStringCompletedEventArgs e );
-   public delegate void MyUploadStringCompletedEventHandler( object sender, MyUploadStringCompletedEventArgs e );
+   public delegate void UnityDownloadStringCompletedEventHandler( object sender, UnityDownloadStringCompletedEventArgs e );
+   public delegate void UnityUploadStringCompletedEventHandler( object sender, UnityUploadStringCompletedEventArgs e );
 
-   public class MyDownloadStringCompletedEventArgs : AsyncCompletedEventArgs
+   public class UnityDownloadStringCompletedEventArgs : AsyncCompletedEventArgs
    {
       private string result;
 
-      internal MyDownloadStringCompletedEventArgs( string result, Exception error, bool cancelled, object userState ) : base( error, cancelled, userState )
+      internal UnityDownloadStringCompletedEventArgs( string result, Exception error, bool cancelled, object userState ) : base( error, cancelled, userState )
       {
          this.result = result;
       }
@@ -34,11 +34,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          }
       }
    }
-   public class MyUploadStringCompletedEventArgs : AsyncCompletedEventArgs
+   public class UnityUploadStringCompletedEventArgs : AsyncCompletedEventArgs
    {
       private string result;
 
-      internal MyUploadStringCompletedEventArgs( string result, Exception error, bool cancelled, object userState ) : base( error, cancelled, userState )
+      internal UnityUploadStringCompletedEventArgs( string result, Exception error, bool cancelled, object userState ) : base( error, cancelled, userState )
       {
          this.result = result;
       }
@@ -53,13 +53,13 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       }
    }
 
-   public class MyAsyncCompletedEventArgs : EventArgs
+   public class UnityAsyncCompletedEventArgs : EventArgs
    {
       private bool _cancelled;
       private Exception _error;
       private object _userState;
 
-      public MyAsyncCompletedEventArgs( Exception error, bool cancelled, object userState )
+      public UnityAsyncCompletedEventArgs( Exception error, bool cancelled, object userState )
       {
          this._error = error;
          this._cancelled = cancelled;
@@ -103,27 +103,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       }
    }
 
-   public class MyWebClient
+   public class ConnectionTrackingWebClient
    {
-      public static readonly string ConnectionGroupName = Guid.NewGuid().ToString();
+      private static readonly TimeSpan MaxUnusedLifespan = TimeSpan.FromSeconds( 50 );
+      private static readonly string ConnectionGroupName = Guid.NewGuid().ToString();
+      private static readonly Dictionary<string, ServicePointState> ActiveConnections = new Dictionary<string, ServicePointState>();
+      private static readonly Dictionary<string, ServicePoint> TouchedServicePoints = new Dictionary<string, ServicePoint>();
 
-      private bool async;
-      private Uri baseAddress;
-      private string baseString;
-      private ICredentials credentials;
-      private System.Text.Encoding encoding = System.Text.Encoding.Default;
-      private WebHeaderCollection headers;
-      private static byte[] hexBytes = new byte[ 0x10 ];
-      private bool is_busy;
-      private IWebProxy proxy;
-      private NameValueCollection queryString;
-      private WebHeaderCollection responseHeaders;
-      private static readonly string urlEncodedCType = "application/x-www-form-urlencoded";
-
-      public event MyDownloadStringCompletedEventHandler DownloadStringCompleted;
-      public event MyUploadStringCompletedEventHandler UploadStringCompleted;
-
-      static MyWebClient()
+      static ConnectionTrackingWebClient()
       {
          int index = 0;
          int num2 = 0x30;
@@ -142,6 +129,98 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          }
       }
 
+      private static void UpdateActiveConnections( Uri address )
+      {
+         var key = address.Scheme + "://" + address.Host + ":" + address.Port;
+         var cleanUri = new Uri( key );
+         lock( ActiveConnections )
+         {
+            if( !ActiveConnections.TryGetValue( key, out var spt ) )
+            {
+               if( !TouchedServicePoints.TryGetValue( key, out var sp ) )
+               {
+                  sp = ServicePointManager.FindServicePoint( cleanUri );
+                  TouchedServicePoints.Add( key, sp );
+               }
+
+               spt = new ServicePointState( sp );
+               ActiveConnections.Add( key, spt );
+            }
+            spt.LastUse = DateTime.UtcNow;
+         }
+      }
+
+      public static void CheckServicePoints()
+      {
+         List<KeyValuePair<string, ServicePointState>> idleEntries = null;
+
+         lock( ActiveConnections )
+         {
+            var timestamp = DateTime.UtcNow;
+            foreach( var kvp in ActiveConnections )
+            {
+               if( timestamp - kvp.Value.LastUse > MaxUnusedLifespan )
+               {
+                  if( idleEntries == null )
+                  {
+                     idleEntries = new List<KeyValuePair<string, ServicePointState>>();
+                  }
+
+                  idleEntries.Add( kvp );
+               }
+            }
+
+            if( idleEntries != null )
+            {
+               foreach( var idleEntry in idleEntries )
+               {
+                  ActiveConnections.Remove( idleEntry.Key );
+                  Logger.Current.Debug( $"Closing connections to endpoint '{idleEntry.Key}' due to inactivity." );
+               }
+            }
+         }
+
+         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 )
+         {
+            ServicePoint = servicePoint;
+         }
+
+         public ServicePoint ServicePoint { get; }
+
+         public DateTime LastUse { get; set; }
+      }
+
+      private bool async;
+      private Uri baseAddress;
+      private string baseString;
+      private ICredentials credentials;
+      private System.Text.Encoding encoding = System.Text.Encoding.Default;
+      private WebHeaderCollection headers;
+      private static byte[] hexBytes = new byte[ 0x10 ];
+      private bool is_busy;
+      private IWebProxy proxy;
+      private NameValueCollection queryString;
+      private WebHeaderCollection responseHeaders;
+      private static readonly string urlEncodedCType = "application/x-www-form-urlencoded";
+
+      public event UnityDownloadStringCompletedEventHandler DownloadStringCompleted;
+      public event UnityUploadStringCompletedEventHandler UploadStringCompleted;
+
       //public void CancelAsync()
       //{
       //   MyWebClient client = this;
@@ -166,7 +245,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       private void CompleteAsync()
       {
-         MyWebClient client = this;
+         ConnectionTrackingWebClient client = this;
          lock( client )
          {
             this.is_busy = false;
@@ -462,7 +541,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          {
             throw new ArgumentNullException( "address" );
          }
-         MyWebClient client = this;
+         ConnectionTrackingWebClient client = this;
          lock( client )
          {
             this.SetBusy();
@@ -474,15 +553,15 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
                try
                {
                   string result = this.encoding.GetString( this.DownloadDataCore( (Uri)objArray[ 0 ], objArray[ 1 ] ) );
-                  this.OnDownloadStringCompleted( new MyDownloadStringCompletedEventArgs( result, null, false, objArray[ 1 ] ) );
+                  this.OnDownloadStringCompleted( new UnityDownloadStringCompletedEventArgs( result, null, false, objArray[ 1 ] ) );
                }
                catch( ThreadInterruptedException )
                {
-                  this.OnDownloadStringCompleted( new MyDownloadStringCompletedEventArgs( null, null, true, objArray[ 1 ] ) );
+                  this.OnDownloadStringCompleted( new UnityDownloadStringCompletedEventArgs( null, null, true, objArray[ 1 ] ) );
                }
                catch( Exception exception )
                {
-                  this.OnDownloadStringCompleted( new MyDownloadStringCompletedEventArgs( null, exception, false, objArray[ 1 ] ) );
+                  this.OnDownloadStringCompleted( new UnityDownloadStringCompletedEventArgs( null, exception, false, objArray[ 1 ] ) );
                }
             }, parameter );
          }
@@ -534,13 +613,20 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       protected virtual WebRequest GetWebRequest( Uri address )
       {
-         return WebRequest.Create( address );
+         var request = WebRequest.Create( address );
+
+         UpdateActiveConnections( address );
+
+         return request;
       }
 
       protected virtual WebResponse GetWebResponse( WebRequest request )
       {
          WebResponse response = request.GetResponse();
          this.responseHeaders = response.Headers;
+
+         UpdateActiveConnections( request.RequestUri );
+
          return response;
       }
 
@@ -548,6 +634,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       {
          WebResponse response = request.EndGetResponse( result );
          this.responseHeaders = response.Headers;
+
+         UpdateActiveConnections( request.RequestUri );
+
          return response;
       }
 
@@ -608,7 +697,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       //   }
       //}
 
-      protected virtual void OnDownloadStringCompleted( MyDownloadStringCompletedEventArgs args )
+      protected virtual void OnDownloadStringCompleted( UnityDownloadStringCompletedEventArgs args )
       {
          this.CompleteAsync();
          if( this.DownloadStringCompleted != null )
@@ -661,7 +750,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       //   }
       //}
 
-      protected virtual void OnUploadStringCompleted( MyUploadStringCompletedEventArgs args )
+      protected virtual void OnUploadStringCompleted( UnityUploadStringCompletedEventArgs args )
       {
          this.CompleteAsync();
          if( this.UploadStringCompleted != null )
@@ -893,7 +982,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       private void SetBusy()
       {
-         MyWebClient client = this;
+         ConnectionTrackingWebClient client = this;
          lock( client )
          {
             this.CheckBusy();
@@ -1331,7 +1420,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          {
             throw new ArgumentNullException( "data" );
          }
-         MyWebClient client = this;
+         ConnectionTrackingWebClient client = this;
          lock( client )
          {
             this.CheckBusy();
@@ -1343,15 +1432,15 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
                try
                {
                   string result = this.UploadString( (Uri)objArray[ 0 ], (string)objArray[ 1 ], (string)objArray[ 2 ] );
-                  this.OnUploadStringCompleted( new MyUploadStringCompletedEventArgs( result, null, false, objArray[ 3 ] ) );
+                  this.OnUploadStringCompleted( new UnityUploadStringCompletedEventArgs( result, null, false, objArray[ 3 ] ) );
                }
                catch( ThreadInterruptedException )
                {
-                  this.OnUploadStringCompleted( new MyUploadStringCompletedEventArgs( null, null, true, objArray[ 3 ] ) );
+                  this.OnUploadStringCompleted( new UnityUploadStringCompletedEventArgs( null, null, true, objArray[ 3 ] ) );
                }
                catch( Exception exception )
                {
-                  this.OnUploadStringCompleted( new MyUploadStringCompletedEventArgs( null, exception, false, objArray[ 3 ] ) );
+                  this.OnUploadStringCompleted( new UnityUploadStringCompletedEventArgs( null, exception, false, objArray[ 3 ] ) );
                }
             }, parameter );
          }

+ 0 - 39
src/XUnity.AutoTranslator.Plugin.Core/Web/DefaultEndpoint.cs

@@ -1,39 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net;
-using UnityEngine;
-using XUnity.AutoTranslator.Plugin.Core.Configuration;
-
-namespace XUnity.AutoTranslator.Plugin.Core.Web
-{
-   public class DefaultEndpoint : KnownHttpEndpoint
-   {
-      private static readonly string ServicePointTemplateUrl = "{0}?from={1}&to={2}&text={3}";
-      private string _endpoint;
-
-      public DefaultEndpoint( string endpoint )
-      {
-         _endpoint = endpoint;
-
-         var uri = new Uri( endpoint );
-         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( uri.Host );
-
-         SetupServicePoints( uri.Scheme + "://" + uri.Host + ":" + uri.Port );
-      }
-
-      public override void ApplyHeaders( WebHeaderCollection headers )
-      {
-      }
-
-      public override bool TryExtractTranslated( string result, out string translated )
-      {
-         translated = result;
-         return true;
-      }
-
-      public override string GetServiceUrl( string untranslatedText, string from, string to )
-      {
-         return string.Format( ServicePointTemplateUrl, _endpoint, from, to, WWW.EscapeURL( untranslatedText ) );
-      }
-   }
-}

+ 0 - 53
src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateHackEndpoint.cs

@@ -1,53 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Net;
-using System.Text;
-using SimpleJSON;
-using UnityEngine;
-using XUnity.AutoTranslator.Plugin.Core.Configuration;
-using XUnity.AutoTranslator.Plugin.Core.Constants;
-using XUnity.AutoTranslator.Plugin.Core.Extensions;
-
-namespace XUnity.AutoTranslator.Plugin.Core.Web
-{
-   public class GoogleTranslateHackEndpoint : KnownHttpEndpoint
-   {
-      private static readonly string HttpsServicePointTemplateUrl = "https://translate.google.com/m?hl=pl&sl={0}&tl={1}&ie=UTF-8&q={2}";
-
-      public GoogleTranslateHackEndpoint()
-      {
-         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "translate.google.com" );
-      }
-
-      public override void ApplyHeaders( WebHeaderCollection headers )
-      {
-         headers[ HttpRequestHeader.UserAgent ] = Settings.GetUserAgent( "Opera/9.80 (J2ME/MIDP; Opera Mini/5.1.21214/28.2725; U; en) Presto/2.8.119 Version/11.10" );
-         headers[ HttpRequestHeader.Accept ] = "*/*";
-         headers[ HttpRequestHeader.AcceptCharset ] = "UTF-8";
-      }
-
-      public override bool TryExtractTranslated( string result, out string translated )
-      {
-         try
-         {
-
-            String extracted = result.GetBetween( "class=\"t0\">", "</div>" );
-            translated = RestSharp.Contrib.HttpUtility.HtmlDecode( extracted ?? string.Empty );
-
-            var success = !string.IsNullOrEmpty( translated );
-            return success;
-         }
-         catch
-         {
-            translated = null;
-            return false;
-         }
-      }
-
-      public override string GetServiceUrl( string untranslatedText, string from, string to )
-      {
-         return string.Format( HttpsServicePointTemplateUrl, from, to, WWW.EscapeURL( untranslatedText ) );
-      }
-   }
-}

+ 0 - 205
src/XUnity.AutoTranslator.Plugin.Core/Web/KnownHttpEndpoint.cs

@@ -1,205 +0,0 @@
-using System;
-using System.Collections;
-using System.Net;
-using System.Threading;
-using UnityEngine;
-using XUnity.AutoTranslator.Plugin.Core.Configuration;
-
-namespace XUnity.AutoTranslator.Plugin.Core.Web
-{
-   public abstract class KnownHttpEndpoint : IKnownEndpoint
-   {
-      private static readonly TimeSpan MaxUnusedLifespan = TimeSpan.FromSeconds( 50 );
-
-      private ServicePoint[] _servicePoints;
-      private bool _isBusy = false;
-      private UnityWebClient _client;
-      private DateTime? _clientLastUse = null;
-
-      public KnownHttpEndpoint()
-      {
-      }
-
-      public bool IsBusy => _isBusy;
-
-      public virtual bool SupportsLineSplitting
-      {
-         get
-         {
-            return false;
-         }
-      }
-
-      protected void SetupServicePoints( params string[] endpoints )
-      {
-         _servicePoints = new ServicePoint[ endpoints.Length ];
-
-         for( int i = 0 ; i < endpoints.Length ; i++ )
-         {
-            var endpoint = endpoints[ i ];
-            var servicePoint = ServicePointManager.FindServicePoint( new Uri( endpoint ) );
-            _servicePoints[ i ] = servicePoint;
-         }
-      }
-
-      public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action failure )
-      {
-         _isBusy = true;
-         try
-         {
-            var setup = OnBeforeTranslate( Settings.TranslationCount );
-            if( setup != null )
-            {
-               while( setup.MoveNext() )
-               {
-                  yield return setup.Current;
-               }
-            }
-            Logger.Current.Debug( "Starting translation for: " + untranslatedText );
-            DownloadResult downloadResult = null;
-            try
-            {
-               var client = GetClient();
-               var url = GetServiceUrl( untranslatedText, from, to );
-               var request = GetRequestObject( untranslatedText, from, to );
-               ApplyHeaders( client.Headers );
-
-               if( request != null )
-               {
-                  downloadResult = client.GetDownloadResult( new Uri( url ), request );
-               }
-               else
-               {
-                  downloadResult = client.GetDownloadResult( new Uri( url ) );
-               }
-            }
-            catch( Exception e )
-            {
-               Logger.Current.Error( e, "Error occurred while setting up translation request." );
-            }
-
-            if( downloadResult != null )
-            {
-               if( Features.SupportsCustomYieldInstruction )
-               {
-                  yield return downloadResult;
-               }
-               else
-               {
-                  while( !downloadResult.IsCompleted )
-                  {
-                     yield return new WaitForSeconds( 0.2f );
-                  }
-               }
-
-               try
-               {
-                  if( downloadResult.Succeeded && downloadResult.Result != null )
-                  {
-                     if( TryExtractTranslated( downloadResult.Result, out var translatedText ) )
-                     {
-                        Logger.Current.Debug( $"Translation for '{untranslatedText}' succeded. Result: {translatedText}" );
-
-                        translatedText = translatedText ?? string.Empty;
-                        success( translatedText );
-                     }
-                     else
-                     {
-                        Logger.Current.Error( "Error occurred while extracting translation." );
-                        failure();
-                     }
-                  }
-                  else
-                  {
-                     Logger.Current.Error( "Error occurred while retrieving translation." + Environment.NewLine + downloadResult.Error );
-                     failure();
-                  }
-               }
-               catch( Exception e )
-               {
-                  Logger.Current.Error( e, "Error occurred while retrieving translation." );
-                  failure();
-               }
-            }
-            else
-            {
-               failure();
-            }
-         }
-         finally
-         {
-            _clientLastUse = DateTime.UtcNow;
-            _isBusy = false;
-         }
-      }
-
-      public virtual void OnUpdate()
-      {
-         if( !_isBusy && _clientLastUse.HasValue && DateTime.UtcNow - _clientLastUse > MaxUnusedLifespan && !_client.IsBusy
-            && _servicePoints != null && _servicePoints.Length > 0 )
-         {
-            Logger.Current.Debug( $"Closing service points because they were not used for {(int)MaxUnusedLifespan.TotalSeconds} seconds." );
-
-            _isBusy = true;
-            _clientLastUse = null;
-
-            ThreadPool.QueueUserWorkItem( delegate ( object state )
-            {
-               // never do a job like this on the game loop thread
-               try
-               {
-                  foreach( var servicePoint in _servicePoints )
-                  {
-                     servicePoint.CloseConnectionGroup( MyWebClient.ConnectionGroupName );
-                  }
-               }
-               finally
-               {
-                  _isBusy = false;
-               }
-            } );
-         }
-      }
-
-      public virtual bool ShouldGetSecondChanceAfterFailure()
-      {
-         return false;
-      }
-
-      public abstract string GetServiceUrl( string untranslatedText, string from, string to );
-
-      public abstract void ApplyHeaders( WebHeaderCollection headers );
-
-      public abstract bool TryExtractTranslated( string result, out string translated );
-
-      public virtual string GetRequestObject( string untranslatedText, string from, string to )
-      {
-         return null;
-      }
-
-      public virtual void WriteCookies( HttpWebResponse response )
-      {
-
-      }
-
-      public virtual CookieContainer ReadCookies()
-      {
-         return null;
-      }
-
-      public virtual IEnumerator OnBeforeTranslate( int translationCount )
-      {
-         return null;
-      }
-
-      public UnityWebClient GetClient()
-      {
-         if( _client == null )
-         {
-            _client = new UnityWebClient( this );
-            _clientLastUse = DateTime.UtcNow;
-         }
-         return _client;
-      }
-   }
-}

+ 0 - 144
src/XUnity.AutoTranslator.Plugin.Core/Web/KnownWwwEndpoint.cs

@@ -1,144 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Net;
-using System.Reflection;
-using Harmony;
-using UnityEngine;
-using XUnity.AutoTranslator.Plugin.Core.Configuration;
-
-namespace XUnity.AutoTranslator.Plugin.Core.Web
-{
-   public abstract class KnownWwwEndpoint : IKnownEndpoint
-   {
-      protected static readonly ConstructorInfo WwwConstructor = Constants.ClrTypes.WWW.GetConstructor( new[] { typeof( string ), typeof( byte[] ), typeof( Dictionary<string, string> ) } );
-
-      private bool _isBusy = false;
-
-      public KnownWwwEndpoint()
-      {
-      }
-
-      public bool IsBusy => _isBusy;
-
-      public virtual bool SupportsLineSplitting
-      {
-         get
-         {
-            return false;
-         }
-      }
-
-      public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action failure )
-      {
-         _isBusy = true;
-         try
-         {
-            var setup = OnBeforeTranslate( Settings.TranslationCount );
-            if( setup != null )
-            {
-               while( setup.MoveNext() )
-               {
-                  yield return setup.Current;
-               }
-            }
-
-            Logger.Current.Debug( "Starting translation for: " + untranslatedText );
-            object www = null;
-            try
-            {
-               var headers = new Dictionary<string, string>();
-               ApplyHeaders( headers );
-               var url = GetServiceUrl( untranslatedText, from, to );
-               www = WwwConstructor.Invoke( new object[] { url, null, headers } );
-            }
-            catch( Exception e )
-            {
-               Logger.Current.Error( e, "Error occurred while setting up translation request." );
-            }
-
-            if( www != null )
-            {
-               yield return www;
-
-               try
-               {
-                  string error = null;
-                  try
-                  {
-                     error = (string)AccessTools.Property( Constants.ClrTypes.WWW, "error" ).GetValue( www, null );
-                  }
-                  catch( Exception e )
-                  {
-                     error = e.ToString();
-                  }
-
-                  if( error != null )
-                  {
-                     Logger.Current.Error( "Error occurred while retrieving translation." + Environment.NewLine + error );
-                     failure();
-                  }
-                  else
-                  {
-                     var text = (string)AccessTools.Property( Constants.ClrTypes.WWW, "text" ).GetValue( www, null );
-
-                     if( text != null )
-                     {
-                        if( TryExtractTranslated( text, out var translatedText ) )
-                        {
-                           Logger.Current.Debug( $"Translation for '{untranslatedText}' succeded. Result: {translatedText}" );
-
-                           translatedText = translatedText ?? string.Empty;
-                           success( translatedText );
-                        }
-                        else
-                        {
-                           Logger.Current.Error( "Error occurred while extracting translation." );
-                           failure();
-                        }
-                     }
-                     else
-                     {
-                        Logger.Current.Error( "Error occurred while extracting text from response." );
-                        failure();
-                     }
-                  }
-               }
-               catch( Exception e )
-               {
-                  Logger.Current.Error( e, "Error occurred while retrieving translation." );
-                  failure();
-               }
-            }
-            else
-            {
-               failure();
-            }
-         }
-         finally
-         {
-            _isBusy = false;
-         }
-      }
-
-      public virtual void OnUpdate()
-      {
-      }
-
-      public virtual bool ShouldGetSecondChanceAfterFailure()
-      {
-         return false;
-      }
-
-      public abstract string GetServiceUrl( string untranslatedText, string from, string to );
-
-      public abstract void ApplyHeaders( Dictionary<string, string> headers );
-
-      public abstract bool TryExtractTranslated( string result, out string translated );
-
-      public virtual IEnumerator OnBeforeTranslate( int translationCount )
-      {
-         return null;
-      }
-   }
-}

+ 14 - 4
src/XUnity.AutoTranslator.Plugin.Core/Web/Security.cs → src/XUnity.AutoTranslator.Plugin.Core/Web/ServiceEndpointConfiguration.cs

@@ -8,18 +8,28 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Web
 {
-   public static class Security
+   public class ServiceEndpointConfiguration
    {
-      public static RemoteCertificateValidationCallback AlwaysAllowByHosts( params string[] hosts )
+      public readonly HashSet<string> _hosts = new HashSet<string>();
+
+      public void EnableHttps( params string[] hosts )
+      {
+         foreach( var host in hosts )
+         {
+            _hosts.Add( host );
+         }
+      }
+
+      internal RemoteCertificateValidationCallback GetCertificateValidationCheck()
       {
-         var lookup = new HashSet<string>( hosts, StringComparer.OrdinalIgnoreCase );
+         if( _hosts.Count == 0 ) return null;
 
          return ( sender, certificate, chain, sslPolicyErrors ) =>
          {
             var request = sender as HttpWebRequest;
             if( request != null )
             {
-               return lookup.Contains( request.Address.Host );
+               return _hosts.Contains( request.Address.Host );
             }
             return false;
          };

+ 47 - 23
src/XUnity.AutoTranslator.Plugin.Core/Web/UnityWebClient.cs

@@ -9,25 +9,26 @@ using System.Reflection;
 using System.Text;
 using Harmony;
 using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Endpoints.Http;
 using XUnity.AutoTranslator.Plugin.Core.Shim;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Web
 {
-   public class UnityWebClient : MyWebClient
+   public class UnityWebClient : ConnectionTrackingWebClient
    {
-      private KnownHttpEndpoint _httpEndpoint;
+      private HttpEndpoint _httpEndpoint;
 
-      public UnityWebClient( KnownHttpEndpoint endpoint )
+      public UnityWebClient( HttpEndpoint endpoint )
       {
          _httpEndpoint = endpoint;
          Encoding = Encoding.UTF8;
-         DownloadStringCompleted += UnityWebClient_DownloadStringCompleted;
-         UploadStringCompleted += UnityWebClient_UploadStringCompleted;
       }
 
-      private void UnityWebClient_UploadStringCompleted( object sender, MyUploadStringCompletedEventArgs ev )
+      private void UnityWebClient_UploadStringCompleted( object sender, UnityUploadStringCompletedEventArgs ev )
       {
-         var handle = ev.UserState as DownloadResult;
+         UploadStringCompleted -= UnityWebClient_UploadStringCompleted;
+
+         var handle = ev.UserState as UnityWebResponse;
 
          // obtain result, error, etc.
          string text = null;
@@ -52,9 +53,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          handle.SetCompleted( text, error );
       }
 
-      private void UnityWebClient_DownloadStringCompleted( object sender, MyDownloadStringCompletedEventArgs ev )
+      private void UnityWebClient_DownloadStringCompleted( object sender, UnityDownloadStringCompletedEventArgs ev )
       {
-         var handle = ev.UserState as DownloadResult;
+         DownloadStringCompleted -= UnityWebClient_DownloadStringCompleted;
+
+         var handle = ev.UserState as UnityWebResponse;
 
          // obtain result, error, etc.
          string text = null;
@@ -85,7 +88,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          var httpRequest = request as HttpWebRequest;
          if( httpRequest != null )
          {
-            var cookies = _httpEndpoint.ReadCookies();
+            var cookies = _httpEndpoint.GetCookiesForNewRequest();
             httpRequest.CookieContainer = cookies;
          }
          return request;
@@ -110,43 +113,64 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          var response = r as HttpWebResponse;
          if( response != null )
          {
-            _httpEndpoint.WriteCookies( response );
+            _httpEndpoint.StoreCookiesFromResponse( response );
          }
       }
 
-      public DownloadResult GetDownloadResult( Uri address )
+      public UnityWebResponse DownloadStringByUnityInstruction( Uri address )
       {
-         var handle = new DownloadResult();
-         DownloadStringAsync( address, handle );
+         var handle = new UnityWebResponse();
+
+         try
+         {
+            DownloadStringCompleted += UnityWebClient_DownloadStringCompleted;
+            DownloadStringAsync( address, handle );
+         }
+         catch
+         {
+            DownloadStringCompleted -= UnityWebClient_DownloadStringCompleted;
+            throw;
+         }
+
          return handle;
       }
 
-      public DownloadResult GetDownloadResult( Uri address, string request )
+      public UnityWebResponse UploadStringByUnityInstruction( Uri address, string request )
       {
-         var handle = new DownloadResult();
-         UploadStringAsync( address, "POST", request, handle );
+         var handle = new UnityWebResponse();
+
+         try
+         {
+            UploadStringCompleted += UnityWebClient_UploadStringCompleted;
+            UploadStringAsync( address, "POST", request, handle );
+         }
+         catch
+         {
+            UploadStringCompleted -= UnityWebClient_UploadStringCompleted;
+            throw;
+         }
+
          return handle;
       }
    }
 
-   public class DownloadResult : CustomYieldInstructionShim
+   public class UnityWebResponse : CustomYieldInstructionShim
    {
-      private bool _isCompleted = false;
-
       public void SetCompleted( string result, string error )
       {
-         _isCompleted = true;
+         IsCompleted = true;
+
          Result = result;
          Error = error;
       }
 
-      public override bool keepWaiting => !_isCompleted;
+      public override bool keepWaiting => !IsCompleted;
 
       public string Result { get; set; }
 
       public string Error { get; set; }
 
-      public bool IsCompleted => _isCompleted;
+      public bool IsCompleted { get; private set; } = false;
 
       public bool Succeeded => Error == null;
    }

+ 1 - 1
src/XUnity.AutoTranslator.Plugin.Core/WhitespaceHandlingStrategy.cs

@@ -5,7 +5,7 @@ using System.Text;
 
 namespace XUnity.AutoTranslator.Plugin.Core
 {
-   public enum WhitespaceHandlingStrategy
+   internal enum WhitespaceHandlingStrategy
    {
       AllOccurrences,
       TrimPerNewline

Some files were not shown because too many files changed in this diff