Browse Source

Merge branch 'v290' into unityinjector

Scrublord1336 6 năm trước cách đây
mục cha
commit
2ee0d6066f
43 tập tin đã thay đổi với 2922 bổ sung443 xóa
  1. 15 0
      .editorconfig
  2. 35 23
      CHANGELOG.md
  3. 15 3
      README.md
  4. 4 0
      XUnity.AutoTranslator.sln
  5. 9 7
      src/XUnity.AutoTranslator.Patcher/Patcher.cs
  6. 0 4
      src/XUnity.AutoTranslator.Patcher/XUnity.AutoTranslator.Patcher.csproj
  7. 2 2
      src/XUnity.AutoTranslator.Plugin.BepIn/XUnity.AutoTranslator.Plugin.BepIn.csproj
  8. 101 61
      src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs
  9. 13 5
      src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs
  10. 8 0
      src/XUnity.AutoTranslator.Plugin.Core/Constants/KnownEndpointNames.cs
  11. 1 0
      src/XUnity.AutoTranslator.Plugin.Core/Constants/KnownEvents.cs
  12. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Constants/PluginData.cs
  13. 0 2
      src/XUnity.AutoTranslator.Plugin.Core/Constants/Types.cs
  14. 114 5
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/StringExtensions.cs
  15. 12 1
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/HooksSetup.cs
  16. 21 22
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/IMGUIHooks.cs
  17. 38 0
      src/XUnity.AutoTranslator.Plugin.Core/MonoHttp/Helpers.cs
  18. 918 0
      src/XUnity.AutoTranslator.Plugin.Core/MonoHttp/HtmlEncoder.cs
  19. 766 0
      src/XUnity.AutoTranslator.Plugin.Core/MonoHttp/HttpUtility.cs
  20. 32 0
      src/XUnity.AutoTranslator.Plugin.Core/TemplatedString.cs
  21. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/TranslationJob.cs
  22. 50 8
      src/XUnity.AutoTranslator.Plugin.Core/TranslationKeys.cs
  23. 2 2
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/TextHelper.cs
  24. 0 139
      src/XUnity.AutoTranslator.Plugin.Core/Web/AutoTranslateClient.cs
  25. 4 25
      src/XUnity.AutoTranslator.Plugin.Core/Web/BaiduTranslateEndpoint.cs
  26. 5 25
      src/XUnity.AutoTranslator.Plugin.Core/Web/DefaultEndpoint.cs
  27. 69 0
      src/XUnity.AutoTranslator.Plugin.Core/Web/ExciteTranslateEndpoint.cs
  28. 123 48
      src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateEndpoint.cs
  29. 69 0
      src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateHackEndpoint.cs
  30. 34 0
      src/XUnity.AutoTranslator.Plugin.Core/Web/IKnownEndpoint.cs
  31. 0 38
      src/XUnity.AutoTranslator.Plugin.Core/Web/KnownEndpoint.cs
  32. 11 6
      src/XUnity.AutoTranslator.Plugin.Core/Web/KnownEndpoints.cs
  33. 127 0
      src/XUnity.AutoTranslator.Plugin.Core/Web/KnownHttpEndpoint.cs
  34. 28 0
      src/XUnity.AutoTranslator.Plugin.Core/Web/Security.cs
  35. 115 0
      src/XUnity.AutoTranslator.Plugin.Core/Web/UnityWebClient.cs
  36. 61 0
      src/XUnity.AutoTranslator.Plugin.Core/Web/WatsonTranslateEndpoint.cs
  37. 72 0
      src/XUnity.AutoTranslator.Plugin.Core/Web/YandexTranslateEndpoint.cs
  38. 9 1
      src/XUnity.AutoTranslator.Plugin.Core/XUnity.AutoTranslator.Plugin.Core.csproj
  39. 2 2
      src/XUnity.AutoTranslator.Plugin.IPA/XUnity.AutoTranslator.Plugin.IPA.csproj
  40. 17 7
      src/XUnity.AutoTranslator.Setup/Program.cs
  41. 10 0
      src/XUnity.AutoTranslator.Setup/Properties/Resources.Designer.cs
  42. 4 1
      src/XUnity.AutoTranslator.Setup/Properties/Resources.resx
  43. 4 4
      src/XUnity.AutoTranslator.Setup/XUnity.AutoTranslator.Setup.csproj

+ 15 - 0
.editorconfig

@@ -0,0 +1,15 @@
+root = true
+
+[*]
+charset                  = utf-8-bom
+insert_final_newline     = true
+trim_trailing_whitespace = true
+
+# CSharp code style settings:
+[*.cs]
+indent_size                                                        = 3
+csharp_space_between_method_call_parameter_list_parentheses        = true
+csharp_space_between_method_declaration_parameter_list_parentheses = true
+csharp_space_between_square_brackets                               = true
+csharp_space_after_keywords_in_control_flow_statements             = false
+csharp_space_between_parentheses                                   = control_flow_statements, expressions

+ 35 - 23
CHANGELOG.md

@@ -1,21 +1,33 @@
+### 2.8.0
+ * CHANGE - Whether SSL is enabled or not is now entirely based on chosen endpoint support
+ * FEATURE - Support for IMGUI translation texts with numbers
+ * FEATURE - Support for overwriting IMGUI hook events
+ * BUG FIX - Improved fix for gtrans (23.07.2018) by supporting persistent HTTP connections and cookies and recalculation of TKK and SSL
+ * BUG FIX - Fixed whitespace handling to honor configuration more appropriately
+ * BUG FIX - User-interaction (hotkeys) now works when in shutdown mode
+ * MISC - Prints out to console errors that occurrs during translation
+ * MISC - IMGUI is still disabled by default. Often other mods UI are implemented in IMGUI. Enabling it will allow those UIs to be translated as well. 
+   * Simply change the config, such that: EnableIMGUI=True
+
 ### 2.7.0
- * Additional installation instructions for standalone installation
- * Fixed a bug with NGUI that caused those texts not to be translated
+ * FEATURE - Additional installation instructions for standalone installation through ReiPatcher
+ * BUG FIX - Fixed a bug with NGUI that caused those texts not to be translated
+ * BUG FIX - Improved fix for gtrans (23.07.2018)
 
 ### 2.6.0
- * Fix for current issue with gtrans (23.07.2018)
- * Support for newer versions of unity engine (those including UnityEngine.CoreModule, etc. in Managed folder)
- * Keeps functioning if web services fails, but no requests will be sent in such scenario. Texts will simply be translated from cache
- * Concurrency now based on which type of endpoint. For gtrans it is set to 1
- * Changed hooking, such that if text framework fails, the others wont also fail
- * Bit more leniency in translation queue spam detection to prevent shutdown of plugin under normal circumstances
+ * FEATURE - Support for newer versions of unity engine (those including UnityEngine.CoreModule, etc. in Managed folder)
+ * BUG FIX - Fix for current issue with gtrans (23.07.2018)
+ * BUG FIX - Keeps functioning if web services fails, but no requests will be sent in such scenario. Texts will simply be translated from cache
+ * BUG FIX - Changed hooking, such that if text framework fails, the others wont also fail
+ * MISC - Concurrency now based on which type of endpoint. For gtrans it is set to 1
+ * MISC - Bit more leniency in translation queue spam detection to prevent shutdown of plugin under normal circumstances
 
 ### 2.5.0
- * Various new rate limiting patterns to prevent spam to configured translate endpoint
- * Copy to clipboard feature
+ * FEATURE Copy to clipboard feature
+ * BUG FIX - Various new rate limiting patterns to prevent spam to configured translate endpoint
 
 ### 2.4.1
- * Disabled IMGUI hook due to bug
+ * BUG FIX - Disabled IMGUI hook due to bug
 
 ### 2.4.0
  * CHANGE - Completely reworked configuration for more organization
@@ -29,25 +41,25 @@
  * BUG FIX - More leniency in allowing text formats (in translation files) to be included as translations
 
 ### 2.3.1
- * Fixed bug that caused the application to quit if any hooks were overriden.
+ * BUG FIX - Fixed bug that caused the application to quit if any hooks were overriden.
 
 ### 2.3.0
- * Allow usage of SSL
- * Better dialogue caching handling. Often a dialogue might get translated multiple times because of small differences in the source text in regards to whitespace.
+ * FEATURE - Allow usage of SSL
+ * BUG FIX - Better dialogue caching handling. Often a dialogue might get translated multiple times because of small differences in the source text in regards to whitespace.
 
 ### 2.2.0
- * Added anti-spam safeguards to web requests that are sent. What it means: The plugin will no longer be able to attempt to translate a text it already considers translated.
- * Changed internal programmatic HTTP service provider from .NET WebClient to Unity WWW.
+ * MISC - Added anti-spam safeguards to web requests that are sent. What it means: The plugin will no longer be able to attempt to translate a text it already considers translated.
+ * MISC - Changed internal programmatic HTTP service provider from .NET WebClient to Unity WWW.
 
 ### 2.1.0
- * Fixed a bug that could cause a StackOverflowException in unfortunate scenarios, if other mods interfered.
- * Added configuration options to control which text frameworks to translate
- * Added integration feature that allows other translation plugins to use this plugin as a fallback
- * MUCH improved dialogue handling. Translations for dialogues should be significantly better than 2.0.1
+ * FEATURE - Added configuration options to control which text frameworks to translate
+ * FEATURE - Added integration feature that allows other translation plugins to use this plugin as a fallback
+ * BUG FIX - Fixed a bug that could cause a StackOverflowException in unfortunate scenarios, if other mods interfered.
+ * BUG FIX - MUCH improved dialogue handling. Translations for dialogues should be significantly better than 2.0.1
 
 ### 2.0.1
- * Changed configuration path so to not conflict with the configuration files that other mods uses, as it does not use the shared configuration system. The previous version could override configuration from other mods.
- * General performance improvements.
+ * BUG FIX - Changed configuration path so to not conflict with the configuration files that other mods uses, as it does not use the shared configuration system. The previous version could override configuration from other mods.
+ * MISC - General performance improvements.
 
 ### 2.0.0
- * The initial release
+ * Initial release

+ 15 - 3
README.md

@@ -16,13 +16,14 @@ The mod can be installed into the following Plugin Managers:
 
 Installations instructions for both methods can be found below.
 
+Additionally it can be installed without a dependency on a plugin manager through ReiPatcher. However, this approach is not recommended if you use one of the above mentioned Plugin Managers!
+
 ## Configuration
-The default configuration file, looks as such (2.4.0+):
+The default configuration file, looks as such (2.6.0+):
 
 ```ini
 [Service]
-Endpoint=GoogleTranslate         ;Endpoint to use. Can be ["GoogleTranslate", "BaiduTranslate"]
-EnableSSL=False                  ;Whether or not to use HTTPS endpoint over standard HTTP
+Endpoint=GoogleTranslate         ;Endpoint to use. Can be ["GoogleTranslate", "BaiduTranslate", "GoogleTranslateHack", "BaiduTranslate", "YandexTranslate", "WatsonTranslate"]
 
 [General]
 Language=en                      ;The language to translate into
@@ -52,6 +53,14 @@ MaxClipboardCopyCharacters=450   ;Max number of characters to hook to clipboard
 BaiduAppId=                      ;OPTIONAL, needed if BaiduTranslate is configured
 BaiduAppSecret=                  ;OPTIONAL, needed if BaiduTranslate is configured
 
+[Yandex]
+YandexAPIKey=                    ;OPTIONAL, needed if YandexTranslate is configured
+
+[Watson]
+WatsonAPIUrl=                    ;OPTIONAL, needed if WatsonTranslate is configured
+WatsonAPIUsername=               ;OPTIONAL, needed if WatsonTranslate is configured
+WatsonAPIPassword=               ;OPTIONAL, needed if WatsonTranslate is configured
+
 [Debug]
 EnablePrintHierarchy=False       ;Used for debugging
 ```
@@ -120,6 +129,8 @@ The file structure should like like this
 {GameDirectory}/AutoTranslator/AnyTranslationFile.txt (this files will be auto generated by plugin!)
  ```
 
+## Translating Mods
+Often other mods UI are implemented through IMGUI. As you can see above, this is disabled by default. By changing the "EnableIMGUI" value to "True", it will start translating IMGUI as well, which likely means that other mods UI will be translated.
 
 ## Integrating with Auto Translator
 I have implemented a system that allows other dedicated translation mods to integrate with XUnity AutoTranslator.
@@ -136,4 +147,5 @@ Here's how it works, and what is required:
     1. UGUI: public static event Func<object, string, string> OnUnableToTranslateUGUI
     2. TextMeshPro: public static event Func<object, string, string> OnUnableToTranslateTextMeshPro
     3. NGUI: public static event Func<object, string, string> OnUnableToTranslateNGUI
+    3. IMGUI: public static event Func<object, string, string> OnUnableToTranslateIMGUI
  * Also, the events can be either instance based or static.

+ 4 - 0
XUnity.AutoTranslator.sln

@@ -20,6 +20,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnity.AutoTranslator.Setup
 	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("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{ACADAE2C-1642-428A-84D4-CC53E24F1348}"
+	ProjectSection(SolutionItems) = preProject
+		.editorconfig = .editorconfig
+	EndProjectSection
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution

+ 9 - 7
src/XUnity.AutoTranslator.Patcher/Patcher.cs

@@ -14,8 +14,8 @@ namespace XUnity.AutoTranslator.Patcher
    public class Patcher : PatchBase
    {
       private static readonly HashSet<string> EntryClasses = new HashSet<string> { "Display", "Input" };
-
       private AssemblyDefinition _hookAssembly;
+      private string _assemblyName;
 
       public override string Name
       {
@@ -29,19 +29,21 @@ namespace XUnity.AutoTranslator.Patcher
       {
          get
          {
-            return "2.6.0";
+            return "2.8.0";
          }
       }
 
       public override void PrePatch()
       {
-         if( ManagedDllExists( "UnityEngine.dll" ) )
-         {
-            RPConfig.RequestAssembly( "UnityEngine.dll" );
-         }
          if( ManagedDllExists( "UnityEngine.CoreModule.dll" ) )
          {
             RPConfig.RequestAssembly( "UnityEngine.CoreModule.dll" );
+            _assemblyName = "UnityEngine.CoreModule";
+         }
+         else if( ManagedDllExists( "UnityEngine.dll" ) )
+         {
+            RPConfig.RequestAssembly( "UnityEngine.dll" );
+            _assemblyName = "UnityEngine";
          }
 
          _hookAssembly = LoadAssembly( "XUnity.AutoTranslator.Plugin.Core.dll" );
@@ -49,7 +51,7 @@ namespace XUnity.AutoTranslator.Patcher
 
       public override bool CanPatch( PatcherArguments args )
       {
-         return ( args.Assembly.Name.Name == "UnityEngine" ) && !HasAttribute( this, args.Assembly, "XUnity.AutoTranslator.Plugin.Core" );
+         return ( args.Assembly.Name.Name == _assemblyName ) && !HasAttribute( this, args.Assembly, "XUnity.AutoTranslator.Plugin.Core" );
       }
 
       public override void Patch( PatcherArguments args )

+ 0 - 4
src/XUnity.AutoTranslator.Patcher/XUnity.AutoTranslator.Patcher.csproj

@@ -4,10 +4,6 @@
     <TargetFramework>net35</TargetFramework>
   </PropertyGroup>
 
-  <ItemGroup>
-    <ProjectReference Include="..\XUnity.AutoTranslator.Plugin.Core\XUnity.AutoTranslator.Plugin.Core.csproj" />
-  </ItemGroup>
-
   <ItemGroup>
     <Reference Include="ExIni">
       <HintPath>..\..\libs\ExIni.dll</HintPath>

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

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

+ 101 - 61
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs

@@ -77,6 +77,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// </summary>
       private Func<string, bool> _symbolCheck;
 
+      private IKnownEndpoint _endpoint;
+
       private int[] _currentTranslationsQueuedPerSecondRollingWindow = new int[ Settings.TranslationQueueWatchWindow ];
       private float? _timeExceededThreshold;
 
@@ -91,14 +93,21 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
          HooksSetup.InstallHooks( Override_TextChanged );
 
-         AutoTranslateClient.Configure();
+         try
+         {
+            _endpoint = KnownEndpoints.FindEndpoint( Settings.ServiceEndpoint );
+         }
+         catch( Exception e )
+         {
+            Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: An unexpected error occurred during initialization of endpoint." + Environment.NewLine + e );
+         }
 
          _symbolCheck = TextHelper.GetSymbolCheck( Settings.FromLanguage );
 
          LoadTranslations();
 
          // start a thread that will periodically removed unused references
-         var t1 = new Thread( RemovedUnusedReferences );
+         var t1 = new Thread( MaintenanceLoop );
          t1.IsBackground = true;
          t1.Start();
 
@@ -118,7 +127,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             .ToArray();
       }
 
-      private void RemovedUnusedReferences( object state )
+      private void MaintenanceLoop( object state )
       {
          while( true )
          {
@@ -130,10 +139,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
             {
                Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: An unexpected error occurred while removing GC'ed resources." + Environment.NewLine + e );
             }
-            finally
-            {
-               Thread.Sleep( 1000 * 60 );
-            }
+
+            Thread.Sleep( 1000 * 60 );
          }
       }
 
@@ -201,8 +208,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
                            if( !string.IsNullOrEmpty( key ) && !string.IsNullOrEmpty( value ) )
                            {
-                              var translationKey = new TranslationKeys( key );
-                              AddTranslation( translationKey, value );
+                              AddTranslation( key, value );
                            }
                         }
                      }
@@ -218,21 +224,21 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       private TranslationJob GetOrCreateTranslationJobFor( TranslationKeys key )
       {
-         if( _unstartedJobs.TryGetValue( key.ForcedRelevantKey, out TranslationJob job ) )
+         if( _unstartedJobs.TryGetValue( key.GetDictionaryLookupKey(), out TranslationJob job ) )
          {
             return job;
          }
 
          foreach( var completedJob in _completedJobs )
          {
-            if( completedJob.Keys.ForcedRelevantKey == key.ForcedRelevantKey )
+            if( completedJob.Keys.GetDictionaryLookupKey() == key.GetDictionaryLookupKey() )
             {
                return completedJob;
             }
          }
 
          job = new TranslationJob( key );
-         _unstartedJobs.Add( key.ForcedRelevantKey, job );
+         _unstartedJobs.Add( key.GetDictionaryLookupKey(), job );
 
          CheckThresholds();
 
@@ -241,6 +247,15 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       private void CheckThresholds()
       {
+         if( _unstartedJobs.Count > Settings.MaxUnstartedJobs )
+         {
+            _unstartedJobs.Clear();
+            _completedJobs.Clear();
+            Settings.IsShutdown = true;
+
+            Console.WriteLine( $"[XUnity.AutoTranslator][ERROR]: SPAM DETECTED: More than {Settings.MaxUnstartedJobs} queued for translations due to unknown reasons. Shutting down plugin." );
+         }
+
          var previousIdx = ( (int)( Time.time - Time.deltaTime ) ) % Settings.TranslationQueueWatchWindow;
          var newIdx = ( (int)Time.time ) % Settings.TranslationQueueWatchWindow;
          if( previousIdx != newIdx )
@@ -258,13 +273,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
                _timeExceededThreshold = Time.time;
             }
 
-            if( Time.time - _timeExceededThreshold.Value > Settings.MaxSecondsAboveTranslationThreshold || _unstartedJobs.Count > Settings.MaxUnstartedJobs )
+            if( Time.time - _timeExceededThreshold.Value > Settings.MaxSecondsAboveTranslationThreshold )
             {
                _unstartedJobs.Clear();
                _completedJobs.Clear();
                Settings.IsShutdown = true;
 
-               Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: Shutting down... spam detected." );
+               Console.WriteLine( $"[XUnity.AutoTranslator][ERROR]: SPAM DETECTED: More than {Settings.MaxTranslationsQueuedPerSecond} translations per seconds queued for a {Settings.MaxSecondsAboveTranslationThreshold} second period. Shutting down plugin." );
             }
          }
          else
@@ -291,25 +306,26 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      private void AddTranslation( TranslationKeys key, string value )
+      private void AddTranslation( string key, string value )
       {
-         _translations[ key.OriginalKey ] = value;
+         _translations[ key ] = value;
          _translatedTexts.Add( value );
+      }
 
-         if( Settings.IgnoreWhitespaceInDialogue && key.IsDialogue )
-         {
-            _translations[ key.DialogueKey ] = value;
-         }
+      private void AddTranslation( TranslationKeys key, string value )
+      {
+         _translations[ key.GetDictionaryLookupKey() ] = value;
+         _translatedTexts.Add( value );
       }
 
       private void QueueNewUntranslatedForClipboard( TranslationKeys key )
       {
          if( Settings.CopyToClipboard )
          {
-            if( !_textsToCopyToClipboard.Contains( key.ForcedRelevantKey ) )
+            if( !_textsToCopyToClipboard.Contains( key.RelevantKey ) )
             {
-               _textsToCopyToClipboard.Add( key.ForcedRelevantKey );
-               _textsToCopyToClipboardOrdered.Add( key.ForcedRelevantKey );
+               _textsToCopyToClipboard.Add( key.RelevantKey );
+               _textsToCopyToClipboardOrdered.Add( key.RelevantKey );
 
                _clipboardUpdated = Time.realtimeSinceStartup;
             }
@@ -318,20 +334,20 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       private void QueueNewUntranslatedForDisk( TranslationKeys key )
       {
-         _newUntranslated.Add( key.RelevantKey );
+         _newUntranslated.Add( key.GetDictionaryLookupKey() );
       }
 
       private void QueueNewTranslationForDisk( TranslationKeys key, string value )
       {
          lock( _writeToFileSync )
          {
-            _newTranslations[ key.RelevantKey ] = value;
+            _newTranslations[ key.GetDictionaryLookupKey() ] = value;
          }
       }
 
       private bool TryGetTranslation( TranslationKeys key, out string value )
       {
-         return _translations.TryGetValue( key.OriginalKey, out value ) || ( Settings.IgnoreWhitespaceInDialogue && _translations.TryGetValue( key.DialogueKey, out value ) );
+         return _translations.TryGetValue( key.GetDictionaryLookupKey(), out value );
       }
 
       private string Override_TextChanged( object ui, string text )
@@ -359,13 +375,15 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
-      private void SetTranslatedText( object ui, string text, TranslationInfo info )
+      private void SetTranslatedText( object ui, string translatedText, TranslationKeys key, TranslationInfo info )
       {
-         info?.SetTranslatedText( text );
+         var untemplatedTranslatedText = key.Untemplate( translatedText );
+
+         info?.SetTranslatedText( untemplatedTranslatedText );
 
          if( _isInTranslatedMode )
          {
-            SetText( ui, text, true, info );
+            SetText( ui, untemplatedTranslatedText, true, info );
          }
       }
 
@@ -398,6 +416,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
                   info?.UnresizeUI( ui );
                }
             }
+            catch( NullReferenceException )
+            {
+               // This is likely happened due to a scene change.
+            }
             catch( Exception e )
             {
                Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: An error occurred while setting text on a component." + Environment.NewLine + e );
@@ -455,17 +477,17 @@ namespace XUnity.AutoTranslator.Plugin.Core
             return null;
          }
 
-
-         if( Settings.Delay == 0 || !SupportsStabilization( ui ) )
+         var supportsStabilization = SupportsStabilization( ui );
+         if( Settings.Delay == 0 || !supportsStabilization )
          {
-            return TranslateOrQueueWebJobImmediate( ui, text, info );
+            return TranslateOrQueueWebJobImmediate( ui, text, info, supportsStabilization );
          }
          else
          {
             StartCoroutine(
                DelayForSeconds( Settings.Delay, () =>
                {
-                  TranslateOrQueueWebJobImmediate( ui, text, info );
+                  TranslateOrQueueWebJobImmediate( ui, text, info, supportsStabilization );
                } ) );
          }
 
@@ -483,7 +505,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// Translates the string of a UI  text or queues it up to be translated
       /// by the HTTP translation service.
       /// </summary>
-      private string TranslateOrQueueWebJobImmediate( object ui, string text, TranslationInfo info )
+      private string TranslateOrQueueWebJobImmediate( object ui, string text, TranslationInfo info, bool supportsStabilization )
       {
          // Get the trimmed text
          text = ( text ?? ui.GetText() ).Trim();
@@ -493,7 +515,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
             info?.Reset( text );
 
-            var textKey = new TranslationKeys( text );
+            var textKey = new TranslationKeys( text, !supportsStabilization );
 
             // if we already have translation loaded in our _translatios dictionary, simply load it and set text
             string translation;
@@ -503,13 +525,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
                if( !string.IsNullOrEmpty( translation ) )
                {
-                  SetTranslatedText( ui, translation, info );
+                  SetTranslatedText( ui, translation, textKey, info );
                   return translation;
                }
             }
             else
             {
-               if( SupportsStabilization( ui ) )
+               if( supportsStabilization )
                {
                   // if we dont know what text to translate it to, we need to figure it out.
                   // this might take a while, so add the UI text component to the ongoing operations
@@ -528,8 +550,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
                      StartCoroutine(
                         WaitForTextStablization(
                            ui: ui,
-                           delay: 0.5f,
-                           maxTries: 100, // 100 tries == 50 seconds
+                           delay: 1.0f, // 1 second to prevent '1 second tickers' from getting translated
+                           maxTries: 60, // 50 tries, about 1 minute
                            currentTries: 0,
                            onMaxTriesExceeded: () =>
                            {
@@ -541,7 +563,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
                               if( !string.IsNullOrEmpty( stabilizedText ) && IsTranslatable( stabilizedText ) )
                               {
-                                 var stabilizedTextKey = new TranslationKeys( stabilizedText );
+                                 var stabilizedTextKey = new TranslationKeys( stabilizedText, false );
 
                                  QueueNewUntranslatedForClipboard( stabilizedTextKey );
 
@@ -552,13 +574,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
                                  {
                                     if( !string.IsNullOrEmpty( translation ) )
                                     {
-                                       SetTranslatedText( ui, translation, info );
+                                       SetTranslatedText( ui, translation, stabilizedTextKey, info );
                                     }
                                  }
                                  else
                                  {
                                     // Lets try not to spam a service that might not be there...
-                                    if( AutoTranslateClient.IsConfigured )
+                                    if( _endpoint != null )
                                     {
                                        if( _consecutiveErrors < Settings.MaxErrors && !Settings.IsShutdown )
                                        {
@@ -582,14 +604,14 @@ namespace XUnity.AutoTranslator.Plugin.Core
                }
                else
                {
-                  if( !_startedOperationsForNonStabilizableComponents.Contains( text ) && !text.ContainsNumbers() )
+                  if( !_startedOperationsForNonStabilizableComponents.Contains( textKey.GetDictionaryLookupKey() ) )
                   {
-                     _startedOperationsForNonStabilizableComponents.Add( text );
+                     _startedOperationsForNonStabilizableComponents.Add( textKey.GetDictionaryLookupKey() );
 
                      QueueNewUntranslatedForClipboard( textKey );
 
                      // Lets try not to spam a service that might not be there...
-                     if( AutoTranslateClient.IsConfigured )
+                     if( _endpoint != null )
                      {
                         if( _consecutiveErrors < Settings.MaxErrors && !Settings.IsShutdown )
                         {
@@ -650,15 +672,21 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       public void Update()
       {
-         if( Settings.IsShutdown ) return;
-
          try
          {
+            if( _endpoint != null )
+            {
+               _endpoint.OnUpdate();
+            }
+
             CopyToClipboard();
-            ResetThresholdTimerIfRequired();
 
-            KickoffTranslations();
-            FinishTranslations();
+            if( !Settings.IsShutdown )
+            {
+               ResetThresholdTimerIfRequired();
+               KickoffTranslations();
+               FinishTranslations();
+            }
 
             if( Input.anyKey )
             {
@@ -691,9 +719,11 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       private void KickoffTranslations()
       {
+         if( _endpoint == null ) return;
+
          foreach( var kvp in _unstartedJobs )
          {
-            if( !AutoTranslateClient.HasAvailableClients ) break;
+            if( _endpoint.IsBusy ) break;
 
             var key = kvp.Key;
             var job = kvp.Value;
@@ -702,8 +732,19 @@ namespace XUnity.AutoTranslator.Plugin.Core
             // lets see if the text should still be translated before kicking anything off
             if( !job.AnyComponentsStillHasOriginalUntranslatedText() ) continue;
 
-            StartCoroutine( AutoTranslateClient.TranslateByWWW( job.Keys.ForcedRelevantKey, Settings.FromLanguage, Settings.Language, translatedText =>
+            StartCoroutine( _endpoint.Translate( job.Keys.GetDictionaryLookupKey(), Settings.FromLanguage, Settings.Language, translatedText =>
             {
+               Settings.TranslationCount++;
+
+               if( !Settings.IsShutdown )
+               {
+                  if( Settings.TranslationCount > Settings.MaxTranslationsBeforeShutdown )
+                  {
+                     Settings.IsShutdown = true;
+                     Console.WriteLine( $"[XUnity.AutoTranslator][ERROR]: Maximum translations ({Settings.MaxTranslationsBeforeShutdown}) per session reached. Shutting plugin down." );
+                  }
+               }
+
                _consecutiveErrors = 0;
 
                if( Settings.ForceSplitTextAfterCharacters > 0 )
@@ -711,8 +752,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                   translatedText = translatedText.SplitToLines( Settings.ForceSplitTextAfterCharacters, '\n', ' ', ' ' );
                }
 
-
-               job.TranslatedText = translatedText;
+               job.TranslatedText = job.Keys.RepairTemplate( translatedText );
 
                if( !string.IsNullOrEmpty( translatedText ) )
                {
@@ -729,15 +769,15 @@ namespace XUnity.AutoTranslator.Plugin.Core
                {
                   if( _consecutiveErrors > Settings.MaxErrors )
                   {
-                     if( AutoTranslateClient.Fallback() )
+                     if( _endpoint.ShouldGetSecondChanceAfterFailure() )
                      {
-                        Console.WriteLine( "[XUnity.AutoTranslator][WARN]: More than 5 consecutive errors occurred. Entering fallback mode." );
+                        Console.WriteLine( $"[XUnity.AutoTranslator][WARN]: More than {Settings.MaxErrors} consecutive errors occurred. Entering fallback mode." );
                         _consecutiveErrors = 0;
                      }
                      else
                      {
                         Settings.IsShutdown = true;
-                        Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: More than 5 consecutive errors occurred. Shutting down plugin." );
+                        Console.WriteLine( $"[XUnity.AutoTranslator][ERROR]: More than {Settings.MaxErrors} consecutive errors occurred. Shutting down plugin." );
                      }
                   }
                }
@@ -765,10 +805,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
                {
                   // update the original text, but only if it has not been chaanged already for some reason (could be other translator plugin or game itself)
                   var text = component.GetText().Trim();
-                  if( text == job.Keys.OriginalKey )
+                  if( text == job.Keys.OriginalText )
                   {
                      var info = component.GetTranslationInfo( false );
-                     SetTranslatedText( component, job.TranslatedText, info );
+                     SetTranslatedText( component, job.TranslatedText, job.Keys, info );
                   }
                }
 
@@ -786,10 +826,10 @@ namespace XUnity.AutoTranslator.Plugin.Core
             var info = kvp.Value as TranslationInfo;
             if( info != null && !string.IsNullOrEmpty( info.OriginalText ) )
             {
-               var key = new TranslationKeys( info.OriginalText );
+               var key = new TranslationKeys( info.OriginalText, false );
                if( TryGetTranslation( key, out string translatedText ) && !string.IsNullOrEmpty( translatedText ) )
                {
-                  SetTranslatedText( kvp.Key, translatedText, info );
+                  SetTranslatedText( kvp.Key, translatedText, key, info );
                }
             }
          }

+ 13 - 5
src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs

@@ -12,12 +12,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       // cannot be changed
       public static readonly int MaxErrors = 5;
       public static readonly float ClipboardDebounceTime = 1f;
-      public static readonly int MaxTranslationsBeforeSlowdown = 1000;
       public static readonly int MaxTranslationsBeforeShutdown = 10000;
       public static readonly int MaxUnstartedJobs = 3500;
 
-      public static bool IsSlowdown = false;
       public static bool IsShutdown = false;
+      public static int TranslationCount = 0;
 
       public static readonly float MaxTranslationsQueuedPerSecond = 5;
       public static readonly int MaxSecondsAboveTranslationThreshold = 30;
@@ -40,9 +39,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static bool AllowPluginHookOverride;
       public static bool IgnoreWhitespaceInDialogue;
       public static int MinDialogueChars;
-      public static bool EnableSSL;
       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 int ForceSplitTextAfterCharacters;
 
       public static bool CopyToClipboard;
@@ -60,6 +62,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
             }
 
             Config.Current.Preferences.DeleteSection( "AutoTranslator" );
+            Config.Current.Preferences[ "Service" ].DeleteKey( "EnableSSL" );
          }
          catch( Exception e )
          {
@@ -69,7 +72,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
 
 
          ServiceEndpoint = Config.Current.Preferences[ "Service" ][ "Endpoint" ].GetOrDefault( KnownEndpointNames.GoogleTranslate, true );
-         EnableSSL = Config.Current.Preferences[ "Service" ][ "EnableSSL" ].GetOrDefault( false );
 
          Language = Config.Current.Preferences[ "General" ][ "Language" ].GetOrDefault( "en" );
          FromLanguage = Config.Current.Preferences[ "General" ][ "FromLanguage" ].GetOrDefault( "ja", true );
@@ -93,7 +95,13 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
 
          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 );
 
          AutoTranslationsFilePath = Path.Combine( Config.Current.DataPath, OutputFile.Replace( "{lang}", Language ) );

+ 8 - 0
src/XUnity.AutoTranslator.Plugin.Core/Constants/KnownEndpointNames.cs

@@ -9,6 +9,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
    {
       public const string GoogleTranslate = "GoogleTranslate";
 
+      public const string GoogleTranslateHack = "GoogleTranslateHack";
+
       public const string BaiduTranslate = "BaiduTranslate";
+
+      public const string YandexTranslate = "YandexTranslate";
+
+      public const string WatsonTranslate = "WatsonTranslate";
+
+      public const string ExciteTranslate = "ExciteTranslate";
    }
 }

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

@@ -10,5 +10,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
       public static string OnUnableToTranslateUGUI = "OnUnableToTranslateUGUI";
       public static string OnUnableToTranslateTextMeshPro = "OnUnableToTranslateTextMeshPro";
       public static string OnUnableToTranslateNGUI = "OnUnableToTranslateNGUI";
+      public static string OnUnableToTranslateIMGUI = "OnUnableToTranslateIMGUI";
    }
 }

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

@@ -11,6 +11,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
 
       public const string Name = "XUnity Auto Translator";
 
-      public const string Version = "2.6.0";
+      public const string Version = "2.8.0";
    }
 }

+ 0 - 2
src/XUnity.AutoTranslator.Plugin.Core/Constants/Types.cs

@@ -18,8 +18,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Constants
 
       public static readonly Type UILabel = FindType( "UILabel" );
 
-      public static readonly Type WWW = FindType( "UnityEngine.WWW" );
-
       private static Type FindType( string name )
       {
          return AppDomain.CurrentDomain.GetAssemblies()

+ 114 - 5
src/XUnity.AutoTranslator.Plugin.Core/Extensions/StringExtensions.cs

@@ -33,16 +33,125 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          '9'
       };
 
-      public static string ChangeToSingleLineForDialogue( this string that )
+      private static readonly HashSet<char> NumbersWithDot = new HashSet<char>
       {
-         if( that.Length > Settings.MinDialogueChars ) // long strings often indicate dialog
+         '0',
+         '1',
+         '2',
+         '3',
+         '4',
+         '5',
+         '6',
+         '7',
+         '8',
+         '9',
+         '0',
+         '1',
+         '2',
+         '3',
+         '4',
+         '5',
+         '6',
+         '7',
+         '8',
+         '9',
+         '.'
+      };
+
+      public static TemplatedString TemplatizeByNumbers( this string str )
+      {
+         var dict = new Dictionary<string, string>();
+         bool isNumber = false;
+         StringBuilder carg = null;
+         char arg = 'A';
+
+         for( int i = 0 ; i < str.Length ; i++ )
          {
-            // Always change dialogue into one line. Otherwise translation services gets confused.
-            return that.RemoveWhitespace();
+            var c = str[ i ];
+            if( isNumber )
+            {
+               if( NumbersWithDot.Contains( c ) )
+               {
+                  carg.Append( c );
+               }
+               else
+               {
+                  // end current number
+                  var variable = carg.ToString();
+                  var ok = true;
+                  var c1 = variable[ 0 ];
+                  if( c1 == '.' )
+                  {
+                     if( variable.Length == 1 )
+                     {
+                        ok = false;
+                     }
+                     else
+                     {
+                        var c2 = variable[ 1 ];
+                        ok = Numbers.Contains( c2 );
+                     }
+                  }
+
+                  if( ok && !dict.ContainsKey( variable ) )
+                  {
+                     dict.Add( variable, "{{" + arg + "}}" );
+                     arg++;
+                  }
+
+                  carg = null;
+                  isNumber = false;
+               }
+            }
+            else
+            {
+               if( NumbersWithDot.Contains( c ) )
+               {
+                  isNumber = true;
+                  carg = new StringBuilder();
+                  carg.Append( c );
+               }
+            }
+         }
+
+         if( carg != null )
+         {
+            // end current number
+            var variable = carg.ToString();
+            var ok = true;
+            var c1 = variable[ 0 ];
+            if( c1 == '.' )
+            {
+               if( variable.Length == 1 )
+               {
+                  ok = false;
+               }
+               else
+               {
+                  var c2 = variable[ 1 ];
+                  ok = Numbers.Contains( c2 );
+               }
+            }
+
+            if( ok && !dict.ContainsKey( variable ) )
+            {
+               dict.Add( variable, "{{" + arg + "}}" );
+               arg++;
+            }
+         }
+
+         if( dict.Count > 0 )
+         {
+            foreach( var kvp in dict )
+            {
+               str = str.Replace( kvp.Key, kvp.Value );
+            }
+
+            return new TemplatedString( str, dict.ToDictionary( x => x.Value, x => x.Key ) );
          }
          else
          {
-            return that;
+            return null;
          }
       }
 

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

@@ -76,7 +76,18 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          {
             if( Settings.EnableIMGUI )
             {
-               harmony.PatchAll( IMGUIHooks.All );
+               success = SetupHook( KnownEvents.OnUnableToTranslateNGUI, defaultHook );
+               if( !success )
+               {
+                  harmony.PatchAll( IMGUIHooks.All );
+
+                  // This wont work in "newer" unity versions!
+                  try
+                  {
+                     harmony.PatchType( typeof( DoButtonGridHook ) );
+                  }
+                  catch { }
+               }
             }
          }
          catch( Exception e )

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

@@ -19,7 +19,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
          typeof( DoButtonHook ),
          typeof( DoModalWindowHook ),
          typeof( DoWindowHook ),
-         //typeof( DoButtonGridHook ),
          typeof( DoTextFieldHook ),
          typeof( DoToggleHook ),
       };
@@ -159,27 +158,27 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
       }
    }
 
-   //[Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
-   //public static class DoButtonGridHook
-   //{
-   //   static bool Prepare( HarmonyInstance instance )
-   //   {
-   //      return Constants.Types.GUI != null;
-   //   }
-
-   //   static MethodBase TargetMethod( HarmonyInstance instance )
-   //   {
-   //      return AccessTools.Method( Constants.Types.GUI, "DoButtonGrid", new[] { typeof( Rect ), typeof( int ), typeof( GUIContent[] ), typeof( int ), typeof( GUIStyle ), typeof( GUIStyle ), typeof( GUIStyle ), typeof( GUIStyle ) } );
-   //   }
-
-   //   static void Prefix( GUIContent[] contents )
-   //   {
-   //      foreach( var content in contents )
-   //      {
-   //         AutoTranslationPlugin.Current.Hook_TextChanged( content );
-   //      }
-   //   }
-   //}
+   [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
+   public static class DoButtonGridHook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return Constants.Types.GUI != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Method( Constants.Types.GUI, "DoButtonGrid", new[] { typeof( Rect ), typeof( int ), typeof( GUIContent[] ), typeof( int ), typeof( GUIStyle ), typeof( GUIStyle ), typeof( GUIStyle ), typeof( GUIStyle ) } );
+      }
+
+      static void Prefix( GUIContent[] contents )
+      {
+         foreach( var content in contents )
+         {
+            AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         }
+      }
+   }
 
    [Harmony, HarmonyAfter( Constants.KnownPlugins.DynamicTranslationLoader )]
    public static class DoTextFieldHook

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

@@ -0,0 +1,38 @@
+//
+// System.Web.Util.Helpers
+//
+// Authors:
+//	Marek Habersack ([email protected])
+//
+// (C) 2009 Novell, Inc (http://novell.com)
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System;
+using System.Globalization;
+
+namespace RestSharp.Contrib
+{
+	class Helpers
+	{
+		public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
+	}
+}

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

@@ -0,0 +1,918 @@
+//
+// Authors:
+//   Patrik Torstensson ([email protected])
+//   Wictor Wilén (decode/encode functions) ([email protected])
+//   Tim Coleman ([email protected])
+//   Gonzalo Paniagua Javier ([email protected])
+
+//   Marek Habersack <[email protected]>
+//
+// (C) 2005-2010 Novell, Inc (http://novell.com/)
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.IO;
+using System.Text;
+#if NET_4_0
+using System.Web.Configuration;
+#endif
+
+namespace RestSharp.Contrib
+{
+#if NET_4_0
+	public
+#endif
+	class HttpEncoder
+	{
+		static char[] hexChars = "0123456789abcdef".ToCharArray();
+		static object entitiesLock = new object();
+		static SortedDictionary<string, char> entities;
+#if NET_4_0
+		static Lazy <HttpEncoder> defaultEncoder;
+		static Lazy <HttpEncoder> currentEncoderLazy;
+#else
+		static HttpEncoder defaultEncoder;
+#endif
+		static HttpEncoder currentEncoder;
+
+		static IDictionary<string, char> Entities
+		{
+			get
+			{
+				lock (entitiesLock)
+				{
+					if (entities == null)
+						InitEntities();
+
+					return entities;
+				}
+			}
+		}
+
+		public static HttpEncoder Current
+		{
+			get
+			{
+#if NET_4_0
+				if (currentEncoder == null)
+					currentEncoder = currentEncoderLazy.Value;
+#endif
+				return currentEncoder;
+			}
+#if NET_4_0
+			set {
+				if (value == null)
+					throw new ArgumentNullException ("value");
+				currentEncoder = value;
+			}
+#endif
+		}
+
+		public static HttpEncoder Default
+		{
+			get
+			{
+#if NET_4_0
+				return defaultEncoder.Value;
+#else
+				return defaultEncoder;
+#endif
+			}
+		}
+
+		static HttpEncoder()
+		{
+#if NET_4_0
+			defaultEncoder = new Lazy <HttpEncoder> (() => new HttpEncoder ());
+			currentEncoderLazy = new Lazy <HttpEncoder> (new Func <HttpEncoder> (GetCustomEncoderFromConfig));
+#else
+			defaultEncoder = new HttpEncoder();
+			currentEncoder = defaultEncoder;
+#endif
+		}
+
+		public HttpEncoder()
+		{
+		}
+#if NET_4_0	
+		protected internal virtual
+#else
+		internal static
+#endif
+ void HeaderNameValueEncode(string headerName, string headerValue, out string encodedHeaderName, out string encodedHeaderValue)
+		{
+			if (String.IsNullOrEmpty(headerName))
+				encodedHeaderName = headerName;
+			else
+				encodedHeaderName = EncodeHeaderString(headerName);
+
+			if (String.IsNullOrEmpty(headerValue))
+				encodedHeaderValue = headerValue;
+			else
+				encodedHeaderValue = EncodeHeaderString(headerValue);
+		}
+
+		static void StringBuilderAppend(string s, ref StringBuilder sb)
+		{
+			if (sb == null)
+				sb = new StringBuilder(s);
+			else
+				sb.Append(s);
+		}
+
+		static string EncodeHeaderString(string input)
+		{
+			StringBuilder sb = null;
+			char ch;
+
+			for (int i = 0; i < input.Length; i++)
+			{
+				ch = input[i];
+
+				if ((ch < 32 && ch != 9) || ch == 127)
+					StringBuilderAppend(String.Format("%{0:x2}", (int)ch), ref sb);
+			}
+
+			if (sb != null)
+				return sb.ToString();
+
+			return input;
+		}
+#if NET_4_0		
+		protected internal virtual void HtmlAttributeEncode (string value, TextWriter output)
+		{
+
+			if (output == null)
+				throw new ArgumentNullException ("output");
+
+			if (String.IsNullOrEmpty (value))
+				return;
+
+			output.Write (HtmlAttributeEncode (value));
+		}
+
+		protected internal virtual void HtmlDecode (string value, TextWriter output)
+		{
+			if (output == null)
+				throw new ArgumentNullException ("output");
+
+			output.Write (HtmlDecode (value));
+		}
+
+		protected internal virtual void HtmlEncode (string value, TextWriter output)
+		{
+			if (output == null)
+				throw new ArgumentNullException ("output");
+
+			output.Write (HtmlEncode (value));
+		}
+
+		protected internal virtual byte[] UrlEncode (byte[] bytes, int offset, int count)
+		{
+			return UrlEncodeToBytes (bytes, offset, count);
+		}
+
+		static HttpEncoder GetCustomEncoderFromConfig ()
+		{
+			var cfg = WebConfigurationManager.GetSection ("system.web/httpRuntime") as HttpRuntimeSection;
+			string typeName = cfg.EncoderType;
+
+			if (String.Compare (typeName, "System.Web.Util.HttpEncoder", StringComparison.OrdinalIgnoreCase) == 0)
+				return Default;
+			
+			Type t = Type.GetType (typeName, false);
+			if (t == null)
+				throw new ConfigurationErrorsException (String.Format ("Could not load type '{0}'.", typeName));
+			
+			if (!typeof (HttpEncoder).IsAssignableFrom (t))
+				throw new ConfigurationErrorsException (
+					String.Format ("'{0}' is not allowed here because it does not extend class 'System.Web.Util.HttpEncoder'.", typeName)
+				);
+
+			return Activator.CreateInstance (t, false) as HttpEncoder;
+		}
+#endif
+#if NET_4_0
+		protected internal virtual
+#else
+		internal static
+#endif
+ string UrlPathEncode(string value)
+		{
+			if (String.IsNullOrEmpty(value))
+				return value;
+
+			MemoryStream result = new MemoryStream();
+			int length = value.Length;
+			for (int i = 0; i < length; i++)
+				UrlPathEncodeChar(value[i], result);
+
+			return Encoding.ASCII.GetString(result.ToArray());
+		}
+
+		internal static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count)
+		{
+			if (bytes == null)
+				throw new ArgumentNullException("bytes");
+
+			int blen = bytes.Length;
+			if (blen == 0)
+				return new byte[0];
+
+			if (offset < 0 || offset >= blen)
+				throw new ArgumentOutOfRangeException("offset");
+
+			if (count < 0 || count > blen - offset)
+				throw new ArgumentOutOfRangeException("count");
+
+			MemoryStream result = new MemoryStream(count);
+			int end = offset + count;
+			for (int i = offset; i < end; i++)
+				UrlEncodeChar((char)bytes[i], result, false);
+
+			return result.ToArray();
+		}
+
+		internal static string HtmlEncode(string s)
+		{
+			if (s == null)
+				return null;
+
+			if (s.Length == 0)
+				return String.Empty;
+
+			bool needEncode = false;
+			for (int i = 0; i < s.Length; i++)
+			{
+				char c = s[i];
+				if (c == '&' || c == '"' || c == '<' || c == '>' || c > 159
+#if NET_4_0
+				    || c == '\''
+#endif
+)
+				{
+					needEncode = true;
+					break;
+				}
+			}
+
+			if (!needEncode)
+				return s;
+
+			StringBuilder output = new StringBuilder();
+			char ch;
+			int len = s.Length;
+
+			for (int i = 0; i < len; i++)
+			{
+				switch (s[i])
+				{
+					case '&':
+						output.Append("&amp;");
+						break;
+					case '>':
+						output.Append("&gt;");
+						break;
+					case '<':
+						output.Append("&lt;");
+						break;
+					case '"':
+						output.Append("&quot;");
+						break;
+#if NET_4_0
+					case '\'':
+						output.Append ("&#39;");
+						break;
+#endif
+					case '\uff1c':
+						output.Append("&#65308;");
+						break;
+
+					case '\uff1e':
+						output.Append("&#65310;");
+						break;
+
+					default:
+						ch = s[i];
+						if (ch > 159 && ch < 256)
+						{
+							output.Append("&#");
+							output.Append(((int)ch).ToString(Helpers.InvariantCulture));
+							output.Append(";");
+						}
+						else
+							output.Append(ch);
+						break;
+				}
+			}
+
+			return output.ToString();
+		}
+
+		internal static string HtmlAttributeEncode(string s)
+		{
+#if NET_4_0
+			if (String.IsNullOrEmpty (s))
+				return String.Empty;
+#else
+			if (s == null)
+				return null;
+
+			if (s.Length == 0)
+				return String.Empty;
+#endif
+			bool needEncode = false;
+			for (int i = 0; i < s.Length; i++)
+			{
+				char c = s[i];
+				if (c == '&' || c == '"' || c == '<'
+#if NET_4_0
+				    || c == '\''
+#endif
+)
+				{
+					needEncode = true;
+					break;
+				}
+			}
+
+			if (!needEncode)
+				return s;
+
+			StringBuilder output = new StringBuilder();
+			int len = s.Length;
+			for (int i = 0; i < len; i++)
+				switch (s[i])
+				{
+					case '&':
+						output.Append("&amp;");
+						break;
+					case '"':
+						output.Append("&quot;");
+						break;
+					case '<':
+						output.Append("&lt;");
+						break;
+#if NET_4_0
+				case '\'':
+					output.Append ("&#39;");
+					break;
+#endif
+					default:
+						output.Append(s[i]);
+						break;
+				}
+
+			return output.ToString();
+		}
+
+		internal static string HtmlDecode(string s)
+		{
+			if (s == null)
+				return null;
+
+			if (s.Length == 0)
+				return String.Empty;
+
+			if (s.IndexOf('&') == -1)
+				return s;
+#if NET_4_0
+			StringBuilder rawEntity = new StringBuilder ();
+#endif
+			StringBuilder entity = new StringBuilder();
+			StringBuilder output = new StringBuilder();
+			int len = s.Length;
+			// 0 -> nothing,
+			// 1 -> right after '&'
+			// 2 -> between '&' and ';' but no '#'
+			// 3 -> '#' found after '&' and getting numbers
+			int state = 0;
+			int number = 0;
+			bool is_hex_value = false;
+			bool have_trailing_digits = false;
+
+			for (int i = 0; i < len; i++)
+			{
+				char c = s[i];
+				if (state == 0)
+				{
+					if (c == '&')
+					{
+						entity.Append(c);
+#if NET_4_0
+						rawEntity.Append (c);
+#endif
+						state = 1;
+					}
+					else
+					{
+						output.Append(c);
+					}
+					continue;
+				}
+
+				if (c == '&')
+				{
+					state = 1;
+					if (have_trailing_digits)
+					{
+						entity.Append(number.ToString(Helpers.InvariantCulture));
+						have_trailing_digits = false;
+					}
+
+					output.Append(entity.ToString());
+					entity.Length = 0;
+					entity.Append('&');
+					continue;
+				}
+
+				if (state == 1)
+				{
+					if (c == ';')
+					{
+						state = 0;
+						output.Append(entity.ToString());
+						output.Append(c);
+						entity.Length = 0;
+					}
+					else
+					{
+						number = 0;
+						is_hex_value = false;
+						if (c != '#')
+						{
+							state = 2;
+						}
+						else
+						{
+							state = 3;
+						}
+						entity.Append(c);
+#if NET_4_0
+						rawEntity.Append (c);
+#endif
+					}
+				}
+				else if (state == 2)
+				{
+					entity.Append(c);
+					if (c == ';')
+					{
+						string key = entity.ToString();
+						if (key.Length > 1 && Entities.ContainsKey(key.Substring(1, key.Length - 2)))
+							key = Entities[key.Substring(1, key.Length - 2)].ToString();
+
+						output.Append(key);
+						state = 0;
+						entity.Length = 0;
+#if NET_4_0
+						rawEntity.Length = 0;
+#endif
+					}
+				}
+				else if (state == 3)
+				{
+					if (c == ';')
+					{
+#if NET_4_0
+						if (number == 0)
+							output.Append (rawEntity.ToString () + ";");
+						else
+#endif
+						if (number > 65535)
+						{
+							output.Append("&#");
+							output.Append(number.ToString(Helpers.InvariantCulture));
+							output.Append(";");
+						}
+						else
+						{
+							output.Append((char)number);
+						}
+						state = 0;
+						entity.Length = 0;
+#if NET_4_0
+						rawEntity.Length = 0;
+#endif
+						have_trailing_digits = false;
+					}
+					else if (is_hex_value && Uri.IsHexDigit(c))
+					{
+						number = number * 16 + Uri.FromHex(c);
+						have_trailing_digits = true;
+#if NET_4_0
+						rawEntity.Append (c);
+#endif
+					}
+					else if (Char.IsDigit(c))
+					{
+						number = number * 10 + ((int)c - '0');
+						have_trailing_digits = true;
+#if NET_4_0
+						rawEntity.Append (c);
+#endif
+					}
+					else if (number == 0 && (c == 'x' || c == 'X'))
+					{
+						is_hex_value = true;
+#if NET_4_0
+						rawEntity.Append (c);
+#endif
+					}
+					else
+					{
+						state = 2;
+						if (have_trailing_digits)
+						{
+							entity.Append(number.ToString(Helpers.InvariantCulture));
+							have_trailing_digits = false;
+						}
+						entity.Append(c);
+					}
+				}
+			}
+
+			if (entity.Length > 0)
+			{
+				output.Append(entity.ToString());
+			}
+			else if (have_trailing_digits)
+			{
+				output.Append(number.ToString(Helpers.InvariantCulture));
+			}
+			return output.ToString();
+		}
+
+		internal static bool NotEncoded(char c)
+		{
+			return (c == '!' || c == '(' || c == ')' || c == '*' || c == '-' || c == '.' || c == '_'
+#if !NET_4_0
+ || c == '\''
+#endif
+);
+		}
+
+		internal static void UrlEncodeChar(char c, Stream result, bool isUnicode)
+		{
+			if (c > 255)
+			{
+				//FIXME: what happens when there is an internal error?
+				//if (!isUnicode)
+				//	throw new ArgumentOutOfRangeException ("c", c, "c must be less than 256");
+				int idx;
+				int i = (int)c;
+
+				result.WriteByte((byte)'%');
+				result.WriteByte((byte)'u');
+				idx = i >> 12;
+				result.WriteByte((byte)hexChars[idx]);
+				idx = (i >> 8) & 0x0F;
+				result.WriteByte((byte)hexChars[idx]);
+				idx = (i >> 4) & 0x0F;
+				result.WriteByte((byte)hexChars[idx]);
+				idx = i & 0x0F;
+				result.WriteByte((byte)hexChars[idx]);
+				return;
+			}
+
+			if (c > ' ' && NotEncoded(c))
+			{
+				result.WriteByte((byte)c);
+				return;
+			}
+			if (c == ' ')
+			{
+				result.WriteByte((byte)'+');
+				return;
+			}
+			if ((c < '0') ||
+				(c < 'A' && c > '9') ||
+				(c > 'Z' && c < 'a') ||
+				(c > 'z'))
+			{
+				if (isUnicode && c > 127)
+				{
+					result.WriteByte((byte)'%');
+					result.WriteByte((byte)'u');
+					result.WriteByte((byte)'0');
+					result.WriteByte((byte)'0');
+				}
+				else
+					result.WriteByte((byte)'%');
+
+				int idx = ((int)c) >> 4;
+				result.WriteByte((byte)hexChars[idx]);
+				idx = ((int)c) & 0x0F;
+				result.WriteByte((byte)hexChars[idx]);
+			}
+			else
+				result.WriteByte((byte)c);
+		}
+
+		internal static void UrlPathEncodeChar(char c, Stream result)
+		{
+			if (c < 33 || c > 126)
+			{
+				byte[] bIn = Encoding.UTF8.GetBytes(c.ToString());
+				for (int i = 0; i < bIn.Length; i++)
+				{
+					result.WriteByte((byte)'%');
+					int idx = ((int)bIn[i]) >> 4;
+					result.WriteByte((byte)hexChars[idx]);
+					idx = ((int)bIn[i]) & 0x0F;
+					result.WriteByte((byte)hexChars[idx]);
+				}
+			}
+			else if (c == ' ')
+			{
+				result.WriteByte((byte)'%');
+				result.WriteByte((byte)'2');
+				result.WriteByte((byte)'0');
+			}
+			else
+				result.WriteByte((byte)c);
+		}
+
+		static void InitEntities()
+		{
+			// Build the hash table of HTML entity references.  This list comes
+			// from the HTML 4.01 W3C recommendation.
+			entities = new SortedDictionary<string, char>(StringComparer.Ordinal);
+
+			entities.Add("nbsp", '\u00A0');
+			entities.Add("iexcl", '\u00A1');
+			entities.Add("cent", '\u00A2');
+			entities.Add("pound", '\u00A3');
+			entities.Add("curren", '\u00A4');
+			entities.Add("yen", '\u00A5');
+			entities.Add("brvbar", '\u00A6');
+			entities.Add("sect", '\u00A7');
+			entities.Add("uml", '\u00A8');
+			entities.Add("copy", '\u00A9');
+			entities.Add("ordf", '\u00AA');
+			entities.Add("laquo", '\u00AB');
+			entities.Add("not", '\u00AC');
+			entities.Add("shy", '\u00AD');
+			entities.Add("reg", '\u00AE');
+			entities.Add("macr", '\u00AF');
+			entities.Add("deg", '\u00B0');
+			entities.Add("plusmn", '\u00B1');
+			entities.Add("sup2", '\u00B2');
+			entities.Add("sup3", '\u00B3');
+			entities.Add("acute", '\u00B4');
+			entities.Add("micro", '\u00B5');
+			entities.Add("para", '\u00B6');
+			entities.Add("middot", '\u00B7');
+			entities.Add("cedil", '\u00B8');
+			entities.Add("sup1", '\u00B9');
+			entities.Add("ordm", '\u00BA');
+			entities.Add("raquo", '\u00BB');
+			entities.Add("frac14", '\u00BC');
+			entities.Add("frac12", '\u00BD');
+			entities.Add("frac34", '\u00BE');
+			entities.Add("iquest", '\u00BF');
+			entities.Add("Agrave", '\u00C0');
+			entities.Add("Aacute", '\u00C1');
+			entities.Add("Acirc", '\u00C2');
+			entities.Add("Atilde", '\u00C3');
+			entities.Add("Auml", '\u00C4');
+			entities.Add("Aring", '\u00C5');
+			entities.Add("AElig", '\u00C6');
+			entities.Add("Ccedil", '\u00C7');
+			entities.Add("Egrave", '\u00C8');
+			entities.Add("Eacute", '\u00C9');
+			entities.Add("Ecirc", '\u00CA');
+			entities.Add("Euml", '\u00CB');
+			entities.Add("Igrave", '\u00CC');
+			entities.Add("Iacute", '\u00CD');
+			entities.Add("Icirc", '\u00CE');
+			entities.Add("Iuml", '\u00CF');
+			entities.Add("ETH", '\u00D0');
+			entities.Add("Ntilde", '\u00D1');
+			entities.Add("Ograve", '\u00D2');
+			entities.Add("Oacute", '\u00D3');
+			entities.Add("Ocirc", '\u00D4');
+			entities.Add("Otilde", '\u00D5');
+			entities.Add("Ouml", '\u00D6');
+			entities.Add("times", '\u00D7');
+			entities.Add("Oslash", '\u00D8');
+			entities.Add("Ugrave", '\u00D9');
+			entities.Add("Uacute", '\u00DA');
+			entities.Add("Ucirc", '\u00DB');
+			entities.Add("Uuml", '\u00DC');
+			entities.Add("Yacute", '\u00DD');
+			entities.Add("THORN", '\u00DE');
+			entities.Add("szlig", '\u00DF');
+			entities.Add("agrave", '\u00E0');
+			entities.Add("aacute", '\u00E1');
+			entities.Add("acirc", '\u00E2');
+			entities.Add("atilde", '\u00E3');
+			entities.Add("auml", '\u00E4');
+			entities.Add("aring", '\u00E5');
+			entities.Add("aelig", '\u00E6');
+			entities.Add("ccedil", '\u00E7');
+			entities.Add("egrave", '\u00E8');
+			entities.Add("eacute", '\u00E9');
+			entities.Add("ecirc", '\u00EA');
+			entities.Add("euml", '\u00EB');
+			entities.Add("igrave", '\u00EC');
+			entities.Add("iacute", '\u00ED');
+			entities.Add("icirc", '\u00EE');
+			entities.Add("iuml", '\u00EF');
+			entities.Add("eth", '\u00F0');
+			entities.Add("ntilde", '\u00F1');
+			entities.Add("ograve", '\u00F2');
+			entities.Add("oacute", '\u00F3');
+			entities.Add("ocirc", '\u00F4');
+			entities.Add("otilde", '\u00F5');
+			entities.Add("ouml", '\u00F6');
+			entities.Add("divide", '\u00F7');
+			entities.Add("oslash", '\u00F8');
+			entities.Add("ugrave", '\u00F9');
+			entities.Add("uacute", '\u00FA');
+			entities.Add("ucirc", '\u00FB');
+			entities.Add("uuml", '\u00FC');
+			entities.Add("yacute", '\u00FD');
+			entities.Add("thorn", '\u00FE');
+			entities.Add("yuml", '\u00FF');
+			entities.Add("fnof", '\u0192');
+			entities.Add("Alpha", '\u0391');
+			entities.Add("Beta", '\u0392');
+			entities.Add("Gamma", '\u0393');
+			entities.Add("Delta", '\u0394');
+			entities.Add("Epsilon", '\u0395');
+			entities.Add("Zeta", '\u0396');
+			entities.Add("Eta", '\u0397');
+			entities.Add("Theta", '\u0398');
+			entities.Add("Iota", '\u0399');
+			entities.Add("Kappa", '\u039A');
+			entities.Add("Lambda", '\u039B');
+			entities.Add("Mu", '\u039C');
+			entities.Add("Nu", '\u039D');
+			entities.Add("Xi", '\u039E');
+			entities.Add("Omicron", '\u039F');
+			entities.Add("Pi", '\u03A0');
+			entities.Add("Rho", '\u03A1');
+			entities.Add("Sigma", '\u03A3');
+			entities.Add("Tau", '\u03A4');
+			entities.Add("Upsilon", '\u03A5');
+			entities.Add("Phi", '\u03A6');
+			entities.Add("Chi", '\u03A7');
+			entities.Add("Psi", '\u03A8');
+			entities.Add("Omega", '\u03A9');
+			entities.Add("alpha", '\u03B1');
+			entities.Add("beta", '\u03B2');
+			entities.Add("gamma", '\u03B3');
+			entities.Add("delta", '\u03B4');
+			entities.Add("epsilon", '\u03B5');
+			entities.Add("zeta", '\u03B6');
+			entities.Add("eta", '\u03B7');
+			entities.Add("theta", '\u03B8');
+			entities.Add("iota", '\u03B9');
+			entities.Add("kappa", '\u03BA');
+			entities.Add("lambda", '\u03BB');
+			entities.Add("mu", '\u03BC');
+			entities.Add("nu", '\u03BD');
+			entities.Add("xi", '\u03BE');
+			entities.Add("omicron", '\u03BF');
+			entities.Add("pi", '\u03C0');
+			entities.Add("rho", '\u03C1');
+			entities.Add("sigmaf", '\u03C2');
+			entities.Add("sigma", '\u03C3');
+			entities.Add("tau", '\u03C4');
+			entities.Add("upsilon", '\u03C5');
+			entities.Add("phi", '\u03C6');
+			entities.Add("chi", '\u03C7');
+			entities.Add("psi", '\u03C8');
+			entities.Add("omega", '\u03C9');
+			entities.Add("thetasym", '\u03D1');
+			entities.Add("upsih", '\u03D2');
+			entities.Add("piv", '\u03D6');
+			entities.Add("bull", '\u2022');
+			entities.Add("hellip", '\u2026');
+			entities.Add("prime", '\u2032');
+			entities.Add("Prime", '\u2033');
+			entities.Add("oline", '\u203E');
+			entities.Add("frasl", '\u2044');
+			entities.Add("weierp", '\u2118');
+			entities.Add("image", '\u2111');
+			entities.Add("real", '\u211C');
+			entities.Add("trade", '\u2122');
+			entities.Add("alefsym", '\u2135');
+			entities.Add("larr", '\u2190');
+			entities.Add("uarr", '\u2191');
+			entities.Add("rarr", '\u2192');
+			entities.Add("darr", '\u2193');
+			entities.Add("harr", '\u2194');
+			entities.Add("crarr", '\u21B5');
+			entities.Add("lArr", '\u21D0');
+			entities.Add("uArr", '\u21D1');
+			entities.Add("rArr", '\u21D2');
+			entities.Add("dArr", '\u21D3');
+			entities.Add("hArr", '\u21D4');
+			entities.Add("forall", '\u2200');
+			entities.Add("part", '\u2202');
+			entities.Add("exist", '\u2203');
+			entities.Add("empty", '\u2205');
+			entities.Add("nabla", '\u2207');
+			entities.Add("isin", '\u2208');
+			entities.Add("notin", '\u2209');
+			entities.Add("ni", '\u220B');
+			entities.Add("prod", '\u220F');
+			entities.Add("sum", '\u2211');
+			entities.Add("minus", '\u2212');
+			entities.Add("lowast", '\u2217');
+			entities.Add("radic", '\u221A');
+			entities.Add("prop", '\u221D');
+			entities.Add("infin", '\u221E');
+			entities.Add("ang", '\u2220');
+			entities.Add("and", '\u2227');
+			entities.Add("or", '\u2228');
+			entities.Add("cap", '\u2229');
+			entities.Add("cup", '\u222A');
+			entities.Add("int", '\u222B');
+			entities.Add("there4", '\u2234');
+			entities.Add("sim", '\u223C');
+			entities.Add("cong", '\u2245');
+			entities.Add("asymp", '\u2248');
+			entities.Add("ne", '\u2260');
+			entities.Add("equiv", '\u2261');
+			entities.Add("le", '\u2264');
+			entities.Add("ge", '\u2265');
+			entities.Add("sub", '\u2282');
+			entities.Add("sup", '\u2283');
+			entities.Add("nsub", '\u2284');
+			entities.Add("sube", '\u2286');
+			entities.Add("supe", '\u2287');
+			entities.Add("oplus", '\u2295');
+			entities.Add("otimes", '\u2297');
+			entities.Add("perp", '\u22A5');
+			entities.Add("sdot", '\u22C5');
+			entities.Add("lceil", '\u2308');
+			entities.Add("rceil", '\u2309');
+			entities.Add("lfloor", '\u230A');
+			entities.Add("rfloor", '\u230B');
+			entities.Add("lang", '\u2329');
+			entities.Add("rang", '\u232A');
+			entities.Add("loz", '\u25CA');
+			entities.Add("spades", '\u2660');
+			entities.Add("clubs", '\u2663');
+			entities.Add("hearts", '\u2665');
+			entities.Add("diams", '\u2666');
+			entities.Add("quot", '\u0022');
+			entities.Add("amp", '\u0026');
+			entities.Add("lt", '\u003C');
+			entities.Add("gt", '\u003E');
+			entities.Add("OElig", '\u0152');
+			entities.Add("oelig", '\u0153');
+			entities.Add("Scaron", '\u0160');
+			entities.Add("scaron", '\u0161');
+			entities.Add("Yuml", '\u0178');
+			entities.Add("circ", '\u02C6');
+			entities.Add("tilde", '\u02DC');
+			entities.Add("ensp", '\u2002');
+			entities.Add("emsp", '\u2003');
+			entities.Add("thinsp", '\u2009');
+			entities.Add("zwnj", '\u200C');
+			entities.Add("zwj", '\u200D');
+			entities.Add("lrm", '\u200E');
+			entities.Add("rlm", '\u200F');
+			entities.Add("ndash", '\u2013');
+			entities.Add("mdash", '\u2014');
+			entities.Add("lsquo", '\u2018');
+			entities.Add("rsquo", '\u2019');
+			entities.Add("sbquo", '\u201A');
+			entities.Add("ldquo", '\u201C');
+			entities.Add("rdquo", '\u201D');
+			entities.Add("bdquo", '\u201E');
+			entities.Add("dagger", '\u2020');
+			entities.Add("Dagger", '\u2021');
+			entities.Add("permil", '\u2030');
+			entities.Add("lsaquo", '\u2039');
+			entities.Add("rsaquo", '\u203A');
+			entities.Add("euro", '\u20AC');
+		}
+	}
+}

+ 766 - 0
src/XUnity.AutoTranslator.Plugin.Core/MonoHttp/HttpUtility.cs

@@ -0,0 +1,766 @@
+// 
+// System.Web.HttpUtility
+//
+// Authors:
+//   Patrik Torstensson ([email protected])
+//   Wictor Wilén (decode/encode functions) ([email protected])
+//   Tim Coleman ([email protected])
+//   Gonzalo Paniagua Javier ([email protected])
+//
+// Copyright (C) 2005-2010 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Globalization;
+using System.IO;
+using System.Security.Permissions;
+using System.Text;
+
+namespace RestSharp.Contrib
+{
+
+//#if !MONOTOUCH
+//    // CAS - no InheritanceDemand here as the class is sealed
+//    [AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
+//#endif
+	public sealed class HttpUtility
+	{
+		sealed class HttpQSCollection : NameValueCollection
+		{
+			public override string ToString()
+			{
+				int count = Count;
+				if (count == 0)
+					return "";
+				StringBuilder sb = new StringBuilder();
+				string[] keys = AllKeys;
+				for (int i = 0; i < count; i++)
+				{
+					sb.AppendFormat("{0}={1}&", keys[i], this[keys[i]]);
+				}
+				if (sb.Length > 0)
+					sb.Length--;
+				return sb.ToString();
+			}
+		}
+
+		#region Constructors
+
+		public HttpUtility()
+		{
+		}
+
+		#endregion // Constructors
+
+		#region Methods
+
+		public static void HtmlAttributeEncode(string s, TextWriter output)
+		{
+			if (output == null)
+			{
+#if NET_4_0
+				throw new ArgumentNullException ("output");
+#else
+				throw new NullReferenceException(".NET emulation");
+#endif
+			}
+#if NET_4_0
+			HttpEncoder.Current.HtmlAttributeEncode (s, output);
+#else
+			output.Write(HttpEncoder.HtmlAttributeEncode(s));
+#endif
+		}
+
+		public static string HtmlAttributeEncode(string s)
+		{
+#if NET_4_0
+			if (s == null)
+				return null;
+			
+			using (var sw = new StringWriter ()) {
+				HttpEncoder.Current.HtmlAttributeEncode (s, sw);
+				return sw.ToString ();
+			}
+#else
+			return HttpEncoder.HtmlAttributeEncode(s);
+#endif
+		}
+
+		public static string UrlDecode(string str)
+		{
+			return UrlDecode(str, Encoding.UTF8);
+		}
+
+		static char[] GetChars(MemoryStream b, Encoding e)
+		{
+			return e.GetChars(b.GetBuffer(), 0, (int)b.Length);
+		}
+
+		static void WriteCharBytes(IList buf, char ch, Encoding e)
+		{
+			if (ch > 255)
+			{
+				foreach (byte b in e.GetBytes(new char[] { ch }))
+					buf.Add(b);
+			}
+			else
+				buf.Add((byte)ch);
+		}
+
+		public static string UrlDecode(string s, Encoding e)
+		{
+			if (null == s)
+				return null;
+
+			if (s.IndexOf('%') == -1 && s.IndexOf('+') == -1)
+				return s;
+
+			if (e == null)
+				e = Encoding.UTF8;
+
+			long len = s.Length;
+			var bytes = new List<byte>();
+			int xchar;
+			char ch;
+
+			for (int i = 0; i < len; i++)
+			{
+				ch = s[i];
+				if (ch == '%' && i + 2 < len && s[i + 1] != '%')
+				{
+					if (s[i + 1] == 'u' && i + 5 < len)
+					{
+						// unicode hex sequence
+						xchar = GetChar(s, i + 2, 4);
+						if (xchar != -1)
+						{
+							WriteCharBytes(bytes, (char)xchar, e);
+							i += 5;
+						}
+						else
+							WriteCharBytes(bytes, '%', e);
+					}
+					else if ((xchar = GetChar(s, i + 1, 2)) != -1)
+					{
+						WriteCharBytes(bytes, (char)xchar, e);
+						i += 2;
+					}
+					else
+					{
+						WriteCharBytes(bytes, '%', e);
+					}
+					continue;
+				}
+
+				if (ch == '+')
+					WriteCharBytes(bytes, ' ', e);
+				else
+					WriteCharBytes(bytes, ch, e);
+			}
+
+			byte[] buf = bytes.ToArray();
+			bytes = null;
+			return e.GetString(buf);
+
+		}
+
+		public static string UrlDecode(byte[] bytes, Encoding e)
+		{
+			if (bytes == null)
+				return null;
+
+			return UrlDecode(bytes, 0, bytes.Length, e);
+		}
+
+		static int GetInt(byte b)
+		{
+			char c = (char)b;
+			if (c >= '0' && c <= '9')
+				return c - '0';
+
+			if (c >= 'a' && c <= 'f')
+				return c - 'a' + 10;
+
+			if (c >= 'A' && c <= 'F')
+				return c - 'A' + 10;
+
+			return -1;
+		}
+
+		static int GetChar(byte[] bytes, int offset, int length)
+		{
+			int value = 0;
+			int end = length + offset;
+			for (int i = offset; i < end; i++)
+			{
+				int current = GetInt(bytes[i]);
+				if (current == -1)
+					return -1;
+				value = (value << 4) + current;
+			}
+
+			return value;
+		}
+
+		static int GetChar(string str, int offset, int length)
+		{
+			int val = 0;
+			int end = length + offset;
+			for (int i = offset; i < end; i++)
+			{
+				char c = str[i];
+				if (c > 127)
+					return -1;
+
+				int current = GetInt((byte)c);
+				if (current == -1)
+					return -1;
+				val = (val << 4) + current;
+			}
+
+			return val;
+		}
+
+		public static string UrlDecode(byte[] bytes, int offset, int count, Encoding e)
+		{
+			if (bytes == null)
+				return null;
+			if (count == 0)
+				return String.Empty;
+
+			if (bytes == null)
+				throw new ArgumentNullException("bytes");
+
+			if (offset < 0 || offset > bytes.Length)
+				throw new ArgumentOutOfRangeException("offset");
+
+			if (count < 0 || offset + count > bytes.Length)
+				throw new ArgumentOutOfRangeException("count");
+
+			StringBuilder output = new StringBuilder();
+			MemoryStream acc = new MemoryStream();
+
+			int end = count + offset;
+			int xchar;
+			for (int i = offset; i < end; i++)
+			{
+				if (bytes[i] == '%' && i + 2 < count && bytes[i + 1] != '%')
+				{
+					if (bytes[i + 1] == (byte)'u' && i + 5 < end)
+					{
+						if (acc.Length > 0)
+						{
+							output.Append(GetChars(acc, e));
+							acc.SetLength(0);
+						}
+						xchar = GetChar(bytes, i + 2, 4);
+						if (xchar != -1)
+						{
+							output.Append((char)xchar);
+							i += 5;
+							continue;
+						}
+					}
+					else if ((xchar = GetChar(bytes, i + 1, 2)) != -1)
+					{
+						acc.WriteByte((byte)xchar);
+						i += 2;
+						continue;
+					}
+				}
+
+				if (acc.Length > 0)
+				{
+					output.Append(GetChars(acc, e));
+					acc.SetLength(0);
+				}
+
+				if (bytes[i] == '+')
+				{
+					output.Append(' ');
+				}
+				else
+				{
+					output.Append((char)bytes[i]);
+				}
+			}
+
+			if (acc.Length > 0)
+			{
+				output.Append(GetChars(acc, e));
+			}
+
+			acc = null;
+			return output.ToString();
+		}
+
+		public static byte[] UrlDecodeToBytes(byte[] bytes)
+		{
+			if (bytes == null)
+				return null;
+
+			return UrlDecodeToBytes(bytes, 0, bytes.Length);
+		}
+
+		public static byte[] UrlDecodeToBytes(string str)
+		{
+			return UrlDecodeToBytes(str, Encoding.UTF8);
+		}
+
+		public static byte[] UrlDecodeToBytes(string str, Encoding e)
+		{
+			if (str == null)
+				return null;
+
+			if (e == null)
+				throw new ArgumentNullException("e");
+
+			return UrlDecodeToBytes(e.GetBytes(str));
+		}
+
+		public static byte[] UrlDecodeToBytes(byte[] bytes, int offset, int count)
+		{
+			if (bytes == null)
+				return null;
+			if (count == 0)
+				return new byte[0];
+
+			int len = bytes.Length;
+			if (offset < 0 || offset >= len)
+				throw new ArgumentOutOfRangeException("offset");
+
+			if (count < 0 || offset > len - count)
+				throw new ArgumentOutOfRangeException("count");
+
+			MemoryStream result = new MemoryStream();
+			int end = offset + count;
+			for (int i = offset; i < end; i++)
+			{
+				char c = (char)bytes[i];
+				if (c == '+')
+				{
+					c = ' ';
+				}
+				else if (c == '%' && i < end - 2)
+				{
+					int xchar = GetChar(bytes, i + 1, 2);
+					if (xchar != -1)
+					{
+						c = (char)xchar;
+						i += 2;
+					}
+				}
+				result.WriteByte((byte)c);
+			}
+
+			return result.ToArray();
+		}
+
+		public static string UrlEncode(string str)
+		{
+			return UrlEncode(str, Encoding.UTF8);
+		}
+
+		public static string UrlEncode(string s, Encoding Enc)
+		{
+			if (s == null)
+				return null;
+
+			if (s == String.Empty)
+				return String.Empty;
+
+			bool needEncode = false;
+			int len = s.Length;
+			for (int i = 0; i < len; i++)
+			{
+				char c = s[i];
+				if ((c < '0') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a') || (c > 'z'))
+				{
+					if (HttpEncoder.NotEncoded(c))
+						continue;
+
+					needEncode = true;
+					break;
+				}
+			}
+
+			if (!needEncode)
+				return s;
+
+			// avoided GetByteCount call
+			byte[] bytes = new byte[Enc.GetMaxByteCount(s.Length)];
+			int realLen = Enc.GetBytes(s, 0, s.Length, bytes, 0);
+			return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, 0, realLen));
+		}
+
+		public static string UrlEncode(byte[] bytes)
+		{
+			if (bytes == null)
+				return null;
+
+			if (bytes.Length == 0)
+				return String.Empty;
+
+			return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, 0, bytes.Length));
+		}
+
+		public static string UrlEncode(byte[] bytes, int offset, int count)
+		{
+			if (bytes == null)
+				return null;
+
+			if (bytes.Length == 0)
+				return String.Empty;
+
+			return Encoding.ASCII.GetString(UrlEncodeToBytes(bytes, offset, count));
+		}
+
+		public static byte[] UrlEncodeToBytes(string str)
+		{
+			return UrlEncodeToBytes(str, Encoding.UTF8);
+		}
+
+		public static byte[] UrlEncodeToBytes(string str, Encoding e)
+		{
+			if (str == null)
+				return null;
+
+			if (str.Length == 0)
+				return new byte[0];
+
+			byte[] bytes = e.GetBytes(str);
+			return UrlEncodeToBytes(bytes, 0, bytes.Length);
+		}
+
+		public static byte[] UrlEncodeToBytes(byte[] bytes)
+		{
+			if (bytes == null)
+				return null;
+
+			if (bytes.Length == 0)
+				return new byte[0];
+
+			return UrlEncodeToBytes(bytes, 0, bytes.Length);
+		}
+
+		public static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count)
+		{
+			if (bytes == null)
+				return null;
+#if NET_4_0
+			return HttpEncoder.Current.UrlEncode (bytes, offset, count);
+#else
+			return HttpEncoder.UrlEncodeToBytes(bytes, offset, count);
+#endif
+		}
+
+		public static string UrlEncodeUnicode(string str)
+		{
+			if (str == null)
+				return null;
+
+			return Encoding.ASCII.GetString(UrlEncodeUnicodeToBytes(str));
+		}
+
+		public static byte[] UrlEncodeUnicodeToBytes(string str)
+		{
+			if (str == null)
+				return null;
+
+			if (str.Length == 0)
+				return new byte[0];
+
+			MemoryStream result = new MemoryStream(str.Length);
+			foreach (char c in str)
+			{
+				HttpEncoder.UrlEncodeChar(c, result, true);
+			}
+			return result.ToArray();
+		}
+
+		/// <summary>
+		/// Decodes an HTML-encoded string and returns the decoded string.
+		/// </summary>
+		/// <param name="s">The HTML string to decode. </param>
+		/// <returns>The decoded text.</returns>
+		public static string HtmlDecode(string s)
+		{
+#if NET_4_0
+			if (s == null)
+				return null;
+			
+			using (var sw = new StringWriter ()) {
+				HttpEncoder.Current.HtmlDecode (s, sw);
+				return sw.ToString ();
+			}
+#else
+			return HttpEncoder.HtmlDecode(s);
+#endif
+		}
+
+		/// <summary>
+		/// Decodes an HTML-encoded string and sends the resulting output to a TextWriter output stream.
+		/// </summary>
+		/// <param name="s">The HTML string to decode</param>
+		/// <param name="output">The TextWriter output stream containing the decoded string. </param>
+		public static void HtmlDecode(string s, TextWriter output)
+		{
+			if (output == null)
+			{
+#if NET_4_0
+				throw new ArgumentNullException ("output");
+#else
+				throw new NullReferenceException(".NET emulation");
+#endif
+			}
+
+			if (!String.IsNullOrEmpty(s))
+			{
+#if NET_4_0
+				HttpEncoder.Current.HtmlDecode (s, output);
+#else
+				output.Write(HttpEncoder.HtmlDecode(s));
+#endif
+			}
+		}
+
+		public static string HtmlEncode(string s)
+		{
+#if NET_4_0
+			if (s == null)
+				return null;
+			
+			using (var sw = new StringWriter ()) {
+				HttpEncoder.Current.HtmlEncode (s, sw);
+				return sw.ToString ();
+			}
+#else
+			return HttpEncoder.HtmlEncode(s);
+#endif
+		}
+
+		/// <summary>
+		/// HTML-encodes a string and sends the resulting output to a TextWriter output stream.
+		/// </summary>
+		/// <param name="s">The string to encode. </param>
+		/// <param name="output">The TextWriter output stream containing the encoded string. </param>
+		public static void HtmlEncode(string s, TextWriter output)
+		{
+			if (output == null)
+			{
+#if NET_4_0
+				throw new ArgumentNullException ("output");
+#else
+				throw new NullReferenceException(".NET emulation");
+#endif
+			}
+
+			if (!String.IsNullOrEmpty(s))
+			{
+#if NET_4_0
+				HttpEncoder.Current.HtmlEncode (s, output);
+#else
+				output.Write(HttpEncoder.HtmlEncode(s));
+#endif
+			}
+		}
+#if NET_4_0
+		public static string HtmlEncode (object value)
+		{
+			if (value == null)
+				return null;
+
+			IHtmlString htmlString = value as IHtmlString;
+			if (htmlString != null)
+				return htmlString.ToHtmlString ();
+			
+			return HtmlEncode (value.ToString ());
+		}
+
+		public static string JavaScriptStringEncode (string value)
+		{
+			return JavaScriptStringEncode (value, false);
+		}
+
+		public static string JavaScriptStringEncode (string value, bool addDoubleQuotes)
+		{
+			if (String.IsNullOrEmpty (value))
+				return addDoubleQuotes ? "\"\"" : String.Empty;
+
+			int len = value.Length;
+			bool needEncode = false;
+			char c;
+			for (int i = 0; i < len; i++) {
+				c = value [i];
+
+				if (c >= 0 && c <= 31 || c == 34 || c == 39 || c == 60 || c == 62 || c == 92) {
+					needEncode = true;
+					break;
+				}
+			}
+
+			if (!needEncode)
+				return addDoubleQuotes ? "\"" + value + "\"" : value;
+
+			var sb = new StringBuilder ();
+			if (addDoubleQuotes)
+				sb.Append ('"');
+
+			for (int i = 0; i < len; i++) {
+				c = value [i];
+				if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 39 || c == 60 || c == 62)
+					sb.AppendFormat ("\\u{0:x4}", (int)c);
+				else switch ((int)c) {
+						case 8:
+							sb.Append ("\\b");
+							break;
+
+						case 9:
+							sb.Append ("\\t");
+							break;
+
+						case 10:
+							sb.Append ("\\n");
+							break;
+
+						case 12:
+							sb.Append ("\\f");
+							break;
+
+						case 13:
+							sb.Append ("\\r");
+							break;
+
+						case 34:
+							sb.Append ("\\\"");
+							break;
+
+						case 92:
+							sb.Append ("\\\\");
+							break;
+
+						default:
+							sb.Append (c);
+							break;
+					}
+			}
+
+			if (addDoubleQuotes)
+				sb.Append ('"');
+
+			return sb.ToString ();
+		}
+#endif
+		public static string UrlPathEncode(string s)
+		{
+#if NET_4_0
+			return HttpEncoder.Current.UrlPathEncode (s);
+#else
+			return HttpEncoder.UrlPathEncode(s);
+#endif
+		}
+
+		public static NameValueCollection ParseQueryString(string query)
+		{
+			return ParseQueryString(query, Encoding.UTF8);
+		}
+
+		public static NameValueCollection ParseQueryString(string query, Encoding encoding)
+		{
+			if (query == null)
+				throw new ArgumentNullException("query");
+			if (encoding == null)
+				throw new ArgumentNullException("encoding");
+			if (query.Length == 0 || (query.Length == 1 && query[0] == '?'))
+				return new NameValueCollection();
+			if (query[0] == '?')
+				query = query.Substring(1);
+
+			NameValueCollection result = new HttpQSCollection();
+			ParseQueryString(query, encoding, result);
+			return result;
+		}
+
+		internal static void ParseQueryString(string query, Encoding encoding, NameValueCollection result)
+		{
+			if (query.Length == 0)
+				return;
+
+			string decoded = HtmlDecode(query);
+			int decodedLength = decoded.Length;
+			int namePos = 0;
+			bool first = true;
+			while (namePos <= decodedLength)
+			{
+				int valuePos = -1, valueEnd = -1;
+				for (int q = namePos; q < decodedLength; q++)
+				{
+					if (valuePos == -1 && decoded[q] == '=')
+					{
+						valuePos = q + 1;
+					}
+					else if (decoded[q] == '&')
+					{
+						valueEnd = q;
+						break;
+					}
+				}
+
+				if (first)
+				{
+					first = false;
+					if (decoded[namePos] == '?')
+						namePos++;
+				}
+
+				string name, value;
+				if (valuePos == -1)
+				{
+					name = null;
+					valuePos = namePos;
+				}
+				else
+				{
+					name = UrlDecode(decoded.Substring(namePos, valuePos - namePos - 1), encoding);
+				}
+				if (valueEnd < 0)
+				{
+					namePos = -1;
+					valueEnd = decoded.Length;
+				}
+				else
+				{
+					namePos = valueEnd + 1;
+				}
+				value = UrlDecode(decoded.Substring(valuePos, valueEnd - valuePos), encoding);
+
+				result.Add(name, value);
+				if (namePos == -1)
+					break;
+			}
+		}
+		#endregion // Methods
+	}
+}

+ 32 - 0
src/XUnity.AutoTranslator.Plugin.Core/TemplatedString.cs

@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+
+namespace XUnity.AutoTranslator.Plugin.Core
+{
+   public class TemplatedString
+   {
+      public TemplatedString( string template, Dictionary<string, string> arguments )
+      {
+         Template = template;
+         Arguments = arguments;
+      }
+
+      public string Template { get; private set; }
+
+      public Dictionary<string, string> Arguments { get; private set; }
+
+      public string Untemplate( string text )
+      {
+         foreach( var kvp in Arguments )
+         {
+            text = text.Replace( kvp.Key, kvp.Value );
+         }
+         return text;
+      }
+
+      public string RepairTemplate( string text )
+      {
+         // TODO: Implement template repairation. The web services might have mangled our parameterization
+         return text;
+      }
+   }
+}

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

@@ -30,7 +30,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          foreach( var component in Components )
          {
             var text = component.GetText().Trim();
-            if( text == Keys.OriginalKey )
+            if( text == Keys.OriginalText )
             {
                return true;
             }

+ 50 - 8
src/XUnity.AutoTranslator.Plugin.Core/TranslationKeys.cs

@@ -9,20 +9,62 @@ namespace XUnity.AutoTranslator.Plugin.Core
 {
    public struct TranslationKeys
    {
-      public TranslationKeys( string key )
+      public TranslationKeys( string key, bool templatizeByNumbers )
       {
-         OriginalKey = key;
-         DialogueKey = key.RemoveWhitespace();
+         OriginalText = key;
+
+         if( Settings.IgnoreWhitespaceInDialogue && key.Length > Settings.MinDialogueChars )
+         {
+            RelevantKey = key.RemoveWhitespace();
+         }
+         else
+         {
+            RelevantKey = key;
+         }
+
+         if( templatizeByNumbers )
+         {
+            TemplatedKey = RelevantKey.TemplatizeByNumbers();
+         }
+         else
+         {
+            TemplatedKey = null;
+         }
       }
 
-      public string OriginalKey { get; }
+      public TemplatedString TemplatedKey { get; }
+
+      public string RelevantKey { get; }
 
-      public string DialogueKey { get; }
+      public string OriginalText { get; set; }
 
-      public string ForcedRelevantKey => IsDialogue ? DialogueKey : OriginalKey;
+      public string GetDictionaryLookupKey()
+      {
+         if( TemplatedKey != null )
+         {
+            return TemplatedKey.Template;
+         }
+         return RelevantKey;
+      }
 
-      public string RelevantKey => IsDialogue && Settings.IgnoreWhitespaceInDialogue ? DialogueKey : OriginalKey;
+      public string Untemplate( string text )
+      {
+         if( TemplatedKey != null )
+         {
+            return TemplatedKey.Untemplate( text );
+         }
 
-      public bool IsDialogue => OriginalKey.Length > Settings.MinDialogueChars;
+         return text;
+      }
+
+      public string RepairTemplate( string text )
+      {
+         if( TemplatedKey != null )
+         {
+            return TemplatedKey.RepairTemplate( text );
+         }
+
+         return text;
+      }
    }
 }

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

@@ -43,8 +43,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Utilities
       public static string Decode( string text )
       {
          // Remove these in newer version
-         text = text.Replace( "0D", "\r" ).Replace( "\\r", "\r" );
-         text = text.Replace( "0A", "\n" ).Replace( "\\n", "\n" );
+         text = text.Replace( "\\r", "\r" );
+         text = text.Replace( "\\n", "\n" );
          return text;
       }
 

+ 0 - 139
src/XUnity.AutoTranslator.Plugin.Core/Web/AutoTranslateClient.cs

@@ -1,139 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Reflection;
-using System.Text;
-using System.Threading;
-using Harmony;
-using UnityEngine.Networking;
-using XUnity.AutoTranslator.Plugin.Core.Configuration;
-using XUnity.AutoTranslator.Plugin.Core.Constants;
-
-namespace XUnity.AutoTranslator.Plugin.Core.Web
-{
-   public static class AutoTranslateClient
-   {
-      private static readonly ConstructorInfo WwwConstructor = Types.WWW.GetConstructor( new[] { typeof( string ), typeof( byte[] ), typeof( Dictionary<string, string> ) } );
-
-      private static KnownEndpoint _endpoint;
-      private static int _runningTranslations = 0;
-      private static int _translationCount;
-      private static int _maxConcurrency;
-
-      public static void Configure()
-      {
-         _endpoint = KnownEndpoints.FindEndpoint( Settings.ServiceEndpoint );
-         _maxConcurrency = _endpoint.GetMaxConcurrency();
-
-         if( _endpoint != null )
-         {
-            _endpoint.ConfigureServicePointManager();
-         }
-      }
-
-      public static bool IsConfigured
-      {
-         get
-         {
-            return _endpoint != null;
-         }
-      }
-
-      public static bool HasAvailableClients => _runningTranslations < _maxConcurrency;
-
-      public static bool Fallback()
-      {
-         return _endpoint.Fallback();
-      }
-
-      public static IEnumerator TranslateByWWW( string untranslated, string from, string to, Action<string> success, Action failure )
-      {
-         var url = _endpoint.GetServiceUrl( untranslated, from, to );
-         var headers = new Dictionary<string, string>();
-         _endpoint.ApplyHeaders( headers );
-
-         object www = WwwConstructor.Invoke( new object[] { url, null, headers } );
-         try
-         {
-            _runningTranslations++;
-            yield return www;
-            _runningTranslations--;
-
-            _translationCount++;
-            if( !Settings.IsSlowdown )
-            {
-               if( _translationCount > Settings.MaxTranslationsBeforeSlowdown )
-               {
-                  Settings.IsSlowdown = true;
-                  _maxConcurrency = 1;
-
-                  Console.WriteLine( "[XUnity.AutoTranslator][WARN]: Maximum translations per session reached. Entering slowdown mode." );
-               }
-            }
-
-            if( !Settings.IsShutdown )
-            {
-               if( _translationCount > Settings.MaxTranslationsBeforeShutdown )
-               {
-                  Settings.IsShutdown = true;
-                  Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: Maximum translations per session reached. Shutting plugin down." );
-               }
-            }
-
-
-            string error = null;
-            try
-            {
-               error = (string)AccessTools.Property( Types.WWW, "error" ).GetValue( www, null );
-            }
-            catch( Exception e )
-            {
-               error = e.ToString();
-            }
-
-            if( error != null )
-            {
-               failure();
-            }
-            else
-            {
-               var text = (string)AccessTools.Property( Types.WWW, "text" ).GetValue( www, null ); ;
-               if( text != null )
-               {
-                  try
-                  {
-                     if( _endpoint.TryExtractTranslated( text, out var translatedText ) )
-                     {
-                        translatedText = translatedText ?? string.Empty;
-                        success( translatedText );
-                     }
-                     else
-                     {
-                        failure();
-                     }
-                  }
-                  catch
-                  {
-                     failure();
-                  }
-               }
-               else
-               {
-                  failure();
-               }
-            }
-         }
-         finally
-         {
-            var disposable = www as IDisposable;
-            if( disposable != null )
-            {
-               disposable.Dispose();
-            }
-         }
-      }
-   }
-}

+ 4 - 25
src/XUnity.AutoTranslator.Plugin.Core/Web/BaiduTranslateEndpoint.cs

@@ -12,18 +12,15 @@ using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Web
 {
-   public class BaiduTranslateEndpoint : KnownEndpoint
+   public class BaiduTranslateEndpoint : KnownHttpEndpoint
    {
-      private static ServicePoint ServicePoint;
       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 string HttpsServicePointTemplateUrl = "https://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();
 
       public BaiduTranslateEndpoint()
-         : base( KnownEndpointNames.GoogleTranslate )
       {
-
+         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "api.fanyi.baidu.com" );
       }
 
       private static string CreateMD5( string input )
@@ -39,30 +36,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          return sb.ToString().ToLower();
       }
 
-      public override void ApplyHeaders( Dictionary<string, string> headers )
-      {
-         headers[ "User-Agent" ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36";
-         headers[ "Accept-Charset" ] = "UTF-8";
-      }
-
       public override void ApplyHeaders( WebHeaderCollection headers )
       {
-         headers[ HttpRequestHeader.UserAgent ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36";
+         headers[ HttpRequestHeader.UserAgent ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36";
          headers[ HttpRequestHeader.AcceptCharset ] = "UTF-8";
       }
 
-      public override void ConfigureServicePointManager()
-      {
-         try
-         {
-            ServicePoint = ServicePointManager.FindServicePoint( new Uri( "http://api.fanyi.baidu.com" ) );
-            ServicePoint.ConnectionLimit = GetMaxConcurrency();
-         }
-         catch
-         {
-         }
-      }
-
       public override bool TryExtractTranslated( string result, out string translated )
       {
          try
@@ -102,7 +81,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          string salt = DateTime.UtcNow.Millisecond.ToString();
          var md5 = CreateMD5( Settings.BaiduAppId + untranslatedText + salt + Settings.BaiduAppSecret );
 
-         return string.Format( Settings.EnableSSL ? HttpsServicePointTemplateUrl : HttpServicePointTemplateUrl, WWW.EscapeURL( untranslatedText ), from, to, Settings.BaiduAppId, salt, md5 );
+         return string.Format( HttpServicePointTemplateUrl, WWW.EscapeURL( untranslatedText ), from, to, Settings.BaiduAppId, salt, md5 );
       }
    }
 }

+ 5 - 25
src/XUnity.AutoTranslator.Plugin.Core/Web/DefaultEndpoint.cs

@@ -6,36 +6,21 @@ using XUnity.AutoTranslator.Plugin.Core.Configuration;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Web
 {
-   public class DefaultEndpoint : KnownEndpoint
+   public class DefaultEndpoint : KnownHttpEndpoint
    {
-      private static ServicePoint ServicePoint;
       private static readonly string ServicePointTemplateUrl = "{0}?from={1}&to={2}&text={3}";
+      private string _endpoint;
 
       public DefaultEndpoint( string endpoint )
-         : base( endpoint )
-      {
-      }
-
-      public override void ApplyHeaders( Dictionary<string, string> headers )
       {
+         _endpoint = endpoint;
+         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( new Uri( _endpoint ).Host );
       }
 
       public override void ApplyHeaders( WebHeaderCollection headers )
       {
       }
 
-      public override void ConfigureServicePointManager()
-      {
-         try
-         {
-            ServicePoint = ServicePointManager.FindServicePoint( new Uri( Identifier ) );
-            ServicePoint.ConnectionLimit = GetMaxConcurrency();
-         }
-         catch
-         {
-         }
-      }
-
       public override bool TryExtractTranslated( string result, out string translated )
       {
          translated = result;
@@ -44,12 +29,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       public override string GetServiceUrl( string untranslatedText, string from, string to )
       {
-         return string.Format( ServicePointTemplateUrl, Identifier, from, to, WWW.EscapeURL( untranslatedText ) );
-      }
-
-      public override int GetMaxConcurrency()
-      {
-         return 10;
+         return string.Format( ServicePointTemplateUrl, _endpoint, from, to, WWW.EscapeURL( untranslatedText ) );
       }
    }
 }

+ 69 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/ExciteTranslateEndpoint.cs

@@ -0,0 +1,69 @@
+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 ExciteTranslateEndpoint : KnownHttpEndpoint
+    {
+        private static readonly string HttpsServicePointTemplateUrl = "https://www.excite.co.jp/world/?wb_lp={0}{1}&before={2}";
+
+        // Author: Johnny Cee (https://stackoverflow.com/questions/10709821/find-text-in-string-with-c-sharp)
+        private static string getBetween(string strSource, string strStart, string strEnd)
+        {
+            const int kNotFound = -1;
+
+            var startIdx = strSource.IndexOf(strStart);
+            if (startIdx != kNotFound)
+            {
+                startIdx += strStart.Length;
+                var endIdx = strSource.IndexOf(strEnd, startIdx);
+                if (endIdx > startIdx)
+                {
+                    return strSource.Substring(startIdx, endIdx - startIdx);
+                }
+            }
+            return String.Empty;
+        }
+
+        public ExciteTranslateEndpoint()
+        {
+            ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "www.excite.co.jp", "excite.co.jp" );
+        }
+
+      public override void ApplyHeaders( WebHeaderCollection headers )
+      {
+         headers[ HttpRequestHeader.UserAgent ] = "Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53";
+         headers[ HttpRequestHeader.Accept ] = "text/html";
+         headers[ HttpRequestHeader.AcceptCharset ] = "UTF-8";
+         headers[ "DNT" ] = "1";
+      }
+
+        public override bool TryExtractTranslated(string result, out string translated)
+        {
+            try
+            {                
+                String extracted = getBetween(result, "class=\"inputText\">", "</p>");
+                translated = RestSharp.Contrib.HttpUtility.HtmlDecode( extracted ?? string.Empty );
+                return true;
+            }
+            catch
+            {
+                translated = null;
+                return false;
+            }
+        }
+
+        public override string GetServiceUrl(string untranslatedText, string from, string to)
+        {
+            return string.Format(HttpsServicePointTemplateUrl, from.ToUpper(), to.ToUpper(), WWW.EscapeURL(untranslatedText));
+        }
+    }
+}

+ 123 - 48
src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateEndpoint.cs

@@ -1,9 +1,13 @@
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Net;
+using System.Reflection;
 using System.Text;
+using Harmony;
+using Jurassic;
 using SimpleJSON;
 using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
@@ -12,36 +16,106 @@ using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Web
 {
-   public class GoogleTranslateEndpoint : KnownEndpoint
+   public class GoogleTranslateEndpoint : KnownHttpEndpoint
    {
-      //private static readonly string CertificateIssuer = "CN=Google Internet Authority G3, O=Google Trust Services, C=US";
-      private static ServicePoint ServicePoint;
-      private static readonly string HttpServicePointTemplateUrl = "http://translate.googleapis.com/translate_a/single?client=t&dt=t&sl={0}&tl={1}&ie=UTF-8&oe=UTF-8&tk={2}&q={3}";
       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}";
-      private static readonly string FallbackHttpServicePointTemplateUrl = "http://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}";
       private static readonly string FallbackHttpsServicePointTemplateUrl = "https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}";
-      private static bool _hasFallenBack = false;
+      private static readonly string HttpsTranslateUserSite = "https://translate.google.com";
+
+      private CookieContainer _cookieContainer;
+      private bool _hasFallenBack = false;
+      private bool _hasSetup = false;
+      private long m = 425635;
+      private long s = 1953544246;
 
       public GoogleTranslateEndpoint()
-         : base( KnownEndpointNames.GoogleTranslate )
       {
+         _cookieContainer = new CookieContainer();
+         ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "translate.google.com", "translate.googleapis.com" );
+      }
 
+      public override void ApplyHeaders( WebHeaderCollection headers )
+      {
+         headers[ HttpRequestHeader.UserAgent ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36";
+         headers[ HttpRequestHeader.Accept ] = "*/*";
       }
 
-      public override void ApplyHeaders( Dictionary<string, string> headers )
+      public override IEnumerator OnBeforeTranslate( int translationCount )
       {
-         headers[ "User-Agent" ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36";
-         headers[ "Accept" ] = "*/*";
+         if( !_hasSetup || translationCount % 100 == 0 )
+         {
+            _hasSetup = true;
+            // Setup TKK and cookies
+
+            return SetupTKK();
+
+         }
+         else
+         {
+            return null;
+         }
       }
 
-      public override void ApplyHeaders( WebHeaderCollection headers )
+      public IEnumerator SetupTKK()
       {
-         headers[ HttpRequestHeader.UserAgent ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36";
-         headers[ HttpRequestHeader.Accept ] = "*/*";
-         headers[ HttpRequestHeader.AcceptCharset ] = "UTF-8";
+         string error = null;
+         DownloadResult downloadResult = null;
+
+         _cookieContainer = new CookieContainer();
+
+         var client = GetClient();
+         try
+         {
+            ApplyHeaders( client.Headers );
+            downloadResult = client.GetDownloadResult( new Uri( HttpsTranslateUserSite ) );
+         }
+         catch( Exception e )
+         {
+            error = e.ToString();
+         }
+
+         if( downloadResult != null )
+         {
+            yield return downloadResult;
+
+            error = downloadResult.Error;
+            if( downloadResult.Succeeded )
+            {
+               try
+               {
+                  var html = downloadResult.Result;
+
+                  const string lookup = "TKK=eval('";
+                  var lookupIndex = html.IndexOf( lookup ) + lookup.Length;
+                  var openClamIndex = html.IndexOf( '{', lookupIndex );
+                  var closeClamIndex = html.IndexOf( '}', openClamIndex );
+                  var functionIndex = html.IndexOf( "function", lookupIndex );
+                  var script = html.Substring( functionIndex, closeClamIndex - functionIndex + 1 );
+                  var decodedScript = script.Replace( "\\x3d", "=" ).Replace( "\\x27", "'" ).Replace( "function", "function FuncName" );
+
+                  // https://github.com/paulbartrum/jurassic/wiki/Safely-executing-user-provided-scripts
+                  ScriptEngine engine = new ScriptEngine();
+                  engine.Evaluate( decodedScript );
+                  var result = engine.CallGlobalFunction<string>( "FuncName" );
+
+                  var parts = result.Split( '.' );
+                  m = long.Parse( parts[ 0 ] );
+                  s = long.Parse( parts[ 1 ] );
+               }
+               catch( Exception e )
+               {
+                  error = e.ToString();
+               }
+            }
+         }
+
+         if( error != null )
+         {
+            Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: An error occurred while setting up GoogleTranslate Cookie/TKK." + Environment.NewLine + error );
+         }
       }
 
-      // TKK Approach stolen from Translation Aggregator r190.
+      // TKK Approach stolen from Translation Aggregator r190, all credits to Sinflower
 
       private long Vi( long r, string o )
       {
@@ -58,8 +132,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       private string Tk( string r )
       {
-         long m = 425586;
-         long s = 2342038670;
          List<long> S = new List<long>();
 
          for( var v = 0 ; v < r.Length ; v++ )
@@ -67,18 +139,22 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
             long A = r[ v ];
             if( 128 > A )
                S.Add( A );
-            else if( 2048 > A )
-               S.Add( A >> 6 | 192 );
-            else if( 55296 == ( 64512 & A ) && v + 1 < r.Length && 56320 == ( 64512 & r[ v + 1 ] ) )
-            {
-               A = 65536 + ( ( 1023 & A ) << 10 ) + ( 1023 & r[ ++v ] );
-               S.Add( A >> 18 | 240 );
-               S.Add( A >> 12 & 63 | 128 );
-            }
             else
             {
-               S.Add( A >> 12 | 224 );
-               S.Add( A >> 6 & 63 | 128 );
+               if( 2048 > A )
+                  S.Add( A >> 6 | 192 );
+               else if( 55296 == ( 64512 & A ) && v + 1 < r.Length && 56320 == ( 64512 & r[ v + 1 ] ) )
+               {
+                  A = 65536 + ( ( 1023 & A ) << 10 ) + ( 1023 & r[ ++v ] );
+                  S.Add( A >> 18 | 240 );
+                  S.Add( A >> 12 & 63 | 128 );
+               }
+               else
+               {
+                  S.Add( A >> 12 | 224 );
+                  S.Add( A >> 6 & 63 | 128 );
+               }
+
                S.Add( 63 & A | 128 );
             }
          }
@@ -103,24 +179,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          return p.ToString( CultureInfo.InvariantCulture ) + "." + ( p ^ m ).ToString( CultureInfo.InvariantCulture );
       }
 
-      public override void ConfigureServicePointManager()
-      {
-         try
-         {
-            //ServicePointManager.ServerCertificateValidationCallback += ( sender, certificate, chain, sslPolicyErrors ) =>
-            //{
-            //   return certificate.Issuer == CertificateIssuer;
-            //};
-
-            ServicePoint = ServicePointManager.FindServicePoint( new Uri( "http://translate.googleapis.com" ) );
-            ServicePoint.ConnectionLimit = GetMaxConcurrency();
-
-         }
-         catch
-         {
-         }
-      }
-
       public override bool TryExtractTranslated( string result, out string translated )
       {
          try
@@ -152,15 +210,15 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       {
          if( _hasFallenBack )
          {
-            return string.Format( Settings.EnableSSL ? FallbackHttpsServicePointTemplateUrl : FallbackHttpServicePointTemplateUrl, from, to, WWW.EscapeURL( untranslatedText ) );
+            return string.Format( FallbackHttpsServicePointTemplateUrl, from, to, WWW.EscapeURL( untranslatedText ) );
          }
          else
          {
-            return string.Format( Settings.EnableSSL ? HttpsServicePointTemplateUrl : HttpServicePointTemplateUrl, from, to, Tk( untranslatedText ), WWW.EscapeURL( untranslatedText ) );
+            return string.Format( HttpsServicePointTemplateUrl, from, to, Tk( untranslatedText ), WWW.EscapeURL( untranslatedText ) );
          }
       }
 
-      public override bool Fallback()
+      public override bool ShouldGetSecondChanceAfterFailure()
       {
          if( !_hasFallenBack )
          {
@@ -170,5 +228,22 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
          return false;
       }
+
+      public override void WriteCookies( HttpWebResponse response )
+      {
+         CookieCollection cookies = response.Cookies;
+         foreach( Cookie cookie in cookies )
+         {
+            // redirect cookie to correct domain
+            cookie.Domain = ".googleapis.com";
+         }
+
+         _cookieContainer.Add( cookies );
+      }
+
+      public override CookieContainer ReadCookies()
+      {
+         return _cookieContainer;
+      }
    }
 }

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

@@ -0,0 +1,69 @@
+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}";
+
+        // Author: Johnny Cee (https://stackoverflow.com/questions/10709821/find-text-in-string-with-c-sharp)
+        private static string getBetween(string strSource, string strStart, string strEnd)
+        {
+            const int kNotFound = -1;
+
+            var startIdx = strSource.IndexOf(strStart);
+            if (startIdx != kNotFound)
+            {
+                startIdx += strStart.Length;
+                var endIdx = strSource.IndexOf(strEnd, startIdx);
+                if (endIdx > startIdx)
+                {
+                    return strSource.Substring(startIdx, endIdx - startIdx);
+                }
+            }
+            return String.Empty;
+        }
+
+        public GoogleTranslateHackEndpoint()
+        {
+            ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "translate.google.com" );
+        }
+
+      public override void ApplyHeaders( WebHeaderCollection headers )
+      {
+         headers[ HttpRequestHeader.UserAgent ] = "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 = getBetween(result, "class=\"t0\">", "</div>");
+                translated = RestSharp.Contrib.HttpUtility.HtmlDecode( extracted ?? string.Empty );
+                return true;
+            }
+            catch
+            {
+                translated = null;
+                return false;
+            }
+        }
+
+        public override string GetServiceUrl(string untranslatedText, string from, string to)
+        {
+            return string.Format(HttpsServicePointTemplateUrl, from, to, WWW.EscapeURL(untranslatedText));
+        }
+    }
+}

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

@@ -0,0 +1,34 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Web
+{
+   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>
+      /// Called before plugin shutdown and can return true to prevent plugin shutdown, if the plugin
+      /// can provide a secondary strategy for translation.
+      /// </summary>
+      /// <returns></returns>
+      bool ShouldGetSecondChanceAfterFailure();
+
+      /// <summary>
+      /// "Update" game loop method.
+      /// </summary>
+      void OnUpdate();
+   }
+}

+ 0 - 38
src/XUnity.AutoTranslator.Plugin.Core/Web/KnownEndpoint.cs

@@ -1,38 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Text;
-
-namespace XUnity.AutoTranslator.Plugin.Core.Web
-{
-   public abstract class KnownEndpoint
-   {
-      public KnownEndpoint( string identifier )
-      {
-         Identifier = identifier;
-      }
-
-      public string Identifier { get; }
-
-      public abstract void ConfigureServicePointManager();
-
-      public abstract string GetServiceUrl( string untranslatedText, string from, string to );
-
-      public abstract void ApplyHeaders( Dictionary<string, string> headers );
-
-      public abstract void ApplyHeaders( WebHeaderCollection headers );
-
-      public abstract bool TryExtractTranslated( string result, out string translated );
-
-      public virtual bool Fallback()
-      {
-         return false;
-      }
-
-      public virtual int GetMaxConcurrency()
-      {
-         return 1;
-      }
-   }
-}

+ 11 - 6
src/XUnity.AutoTranslator.Plugin.Core/Web/KnownEndpoints.cs

@@ -8,19 +8,24 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 {
    public static class KnownEndpoints
    {
-      public static readonly KnownEndpoint GoogleTranslate = new GoogleTranslateEndpoint();
-      public static readonly KnownEndpoint BaiduTranslate = new BaiduTranslateEndpoint();
-
-      public static KnownEndpoint FindEndpoint( string identifier )
+      public static KnownHttpEndpoint FindEndpoint( string identifier )
       {
          if( string.IsNullOrEmpty( identifier ) ) return null;
 
          switch( identifier )
          {
             case KnownEndpointNames.GoogleTranslate:
-               return GoogleTranslate;
+               return new GoogleTranslateEndpoint();
+            case KnownEndpointNames.GoogleTranslateHack:
+               return new GoogleTranslateHackEndpoint();
             case KnownEndpointNames.BaiduTranslate:
-               return BaiduTranslate;
+               return new BaiduTranslateEndpoint();
+            case KnownEndpointNames.YandexTranslate:
+               return new YandexTranslateEndpoint();
+            case KnownEndpointNames.WatsonTranslate:
+               return new WatsonTranslateEndpoint();
+            case KnownEndpointNames.ExciteTranslate:
+               return new ExciteTranslateEndpoint();
             default:
                return new DefaultEndpoint( identifier );
          }

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

@@ -0,0 +1,127 @@
+using System;
+using System.Collections;
+using System.Net;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Web
+{
+   public abstract class KnownHttpEndpoint : IKnownEndpoint
+   {
+      private static readonly TimeSpan MaxUnusedLifespan = TimeSpan.FromSeconds( 20 );
+
+      private static int _runningTranslations = 0;
+      private static int _maxConcurrency = 1;
+      private static bool _isSettingUp = false;
+      private UnityWebClient _client;
+      private DateTime _clientLastUse = DateTime.UtcNow;
+
+      public KnownHttpEndpoint()
+      {
+      }
+
+      public bool IsBusy => _isSettingUp || _runningTranslations >= _maxConcurrency;
+
+      public IEnumerator Translate( string untranslatedText, string from, string to, Action<string> success, Action failure )
+      {
+         _clientLastUse = DateTime.UtcNow;
+
+         try
+         {
+            _isSettingUp = true;
+
+            var setup = OnBeforeTranslate( Settings.TranslationCount );
+            if( setup != null )
+            {
+               while( setup.MoveNext() )
+               {
+                  yield return setup.Current;
+               }
+            }
+         }
+         finally
+         {
+            _isSettingUp = false;
+         }
+
+         var client = GetClient();
+         var url = GetServiceUrl( untranslatedText, from, to );
+         ApplyHeaders( client.Headers );
+         var result = client.GetDownloadResult( new Uri( url ) );
+         try
+         {
+            _runningTranslations++;
+            yield return result;
+            _runningTranslations--;
+
+            if( result.Succeeded )
+            {
+               if( TryExtractTranslated( result.Result, out var translatedText ) )
+               {
+                  translatedText = translatedText ?? string.Empty;
+                  success( translatedText );
+               }
+               else
+               {
+                  Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: Error occurred while extracting translation." );
+                  failure();
+               }
+            }
+            else
+            {
+               Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: Error occurred while retrieving translation." + Environment.NewLine + result.Error );
+               failure();
+            }
+         }
+         finally
+         {
+            _clientLastUse = DateTime.UtcNow;
+         }
+      }
+
+      public virtual void OnUpdate()
+      {
+         var client = _client;
+         if( client != null && DateTime.UtcNow - _clientLastUse > MaxUnusedLifespan )
+         {
+            _client = null;
+            client.Dispose();
+         }
+      }
+
+      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 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;
+      }
+   }
+}

+ 28 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/Security.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Web
+{
+   public static class Security
+   {
+      public static RemoteCertificateValidationCallback AlwaysAllowByHosts( params string[] hosts )
+      {
+         var lookup = new HashSet<string>( hosts, StringComparer.OrdinalIgnoreCase );
+
+         return ( sender, certificate, chain, sslPolicyErrors ) =>
+         {
+            var request = sender as HttpWebRequest;
+            if( request != null )
+            {
+               return lookup.Contains( request.Address.Host );
+            }
+            return false;
+         };
+      }
+   }
+}

+ 115 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/UnityWebClient.cs

@@ -0,0 +1,115 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Text;
+using Harmony;
+using UnityEngine;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Web
+{
+   public class UnityWebClient : WebClient
+   {
+      private KnownHttpEndpoint _httpEndpoint;
+
+      public UnityWebClient( KnownHttpEndpoint endpoint )
+      {
+         _httpEndpoint = endpoint;
+         Encoding = Encoding.UTF8;
+         DownloadStringCompleted += UnityWebClient_DownloadStringCompleted;
+      }
+
+      private void UnityWebClient_DownloadStringCompleted( object sender, DownloadStringCompletedEventArgs ev )
+      {
+         var handle = ev.UserState as DownloadResult;
+
+         // obtain result, error, etc.
+         string text = null;
+         string error = null;
+
+         try
+         {
+            if( ev.Error == null )
+            {
+               text = ev.Result ?? string.Empty;
+            }
+            else
+            {
+               error = ev.Error.ToString();
+            }
+         }
+         catch( Exception e )
+         {
+            error = e.ToString();
+         }
+
+         handle.SetCompleted( text, error );
+      }
+
+      protected override WebRequest GetWebRequest( Uri address )
+      {
+         var request = base.GetWebRequest( address );
+         var httpRequest = request as HttpWebRequest;
+         if( httpRequest != null )
+         {
+            var cookies = _httpEndpoint.ReadCookies();
+            httpRequest.CookieContainer = cookies;
+         }
+         return request;
+      }
+
+      protected override WebResponse GetWebResponse( WebRequest request, IAsyncResult result )
+      {
+         WebResponse response = base.GetWebResponse( request, result );
+         WriteCookies( response );
+         return response;
+      }
+
+      protected override WebResponse GetWebResponse( WebRequest request )
+      {
+         WebResponse response = base.GetWebResponse( request );
+         WriteCookies( response );
+         return response;
+      }
+
+      private void WriteCookies( WebResponse r )
+      {
+         var response = r as HttpWebResponse;
+         if( response != null )
+         {
+            _httpEndpoint.WriteCookies( response );
+         }
+      }
+
+      public DownloadResult GetDownloadResult( Uri address )
+      {
+         var handle = new DownloadResult();
+         DownloadStringAsync( address, handle );
+         return handle;
+      }
+   }
+
+   public class DownloadResult : CustomYieldInstruction
+   {
+      private bool _isCompleted = false;
+
+      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 Succeeded => Error == null;
+   }
+}

+ 61 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/WatsonTranslateEndpoint.cs

@@ -0,0 +1,61 @@
+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 WatsonTranslateEndpoint : KnownHttpEndpoint
+    {
+        private static readonly string HttpsServicePointTemplateUrl = Settings.WatsonAPIUrl.TrimEnd('/')+ "/v2/translate?model_id={0}-{1}&text={2}";
+
+        public WatsonTranslateEndpoint()
+        {
+            ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( new Uri( Settings.WatsonAPIUrl ).Host );
+        }
+
+      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( Settings.WatsonAPIUsername + ":" + Settings.WatsonAPIPassword ) );
+      }
+
+        public override bool TryExtractTranslated(string result, out string translated)
+        {
+            try
+            {
+                var obj = JSON.Parse(result);
+                var lineBuilder = new StringBuilder(result.Length);
+                
+                foreach (JSONNode entry in obj.AsObject["translations"].AsArray) {
+                    var token = entry.AsObject["translation"].ToString();
+                    token = token.Substring(1, token.Length - 2).UnescapeJson();
+
+                    if (!lineBuilder.EndsWithWhitespaceOrNewline()) lineBuilder.Append("\n");
+
+                    lineBuilder.Append(token);
+                }
+                translated = lineBuilder.ToString();               
+                return true;
+            }
+            catch (Exception)
+            {
+                translated = null;
+                return false;
+            }
+        }
+
+        public override string GetServiceUrl(string untranslatedText, string from, string to)
+        {            
+            return string.Format(HttpsServicePointTemplateUrl, from, to, WWW.EscapeURL(untranslatedText));
+        }
+    }
+}

+ 72 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/YandexTranslateEndpoint.cs

@@ -0,0 +1,72 @@
+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 YandexTranslateEndpoint : KnownHttpEndpoint
+    {
+        private static readonly string HttpsServicePointTemplateUrl = "https://translate.yandex.net/api/v1.5/tr.json/translate?key={3}&text={2}&lang={0}-{1}&format=plain";
+        public YandexTranslateEndpoint()
+        {
+            ServicePointManager.ServerCertificateValidationCallback += Security.AlwaysAllowByHosts( "translate.yandex.net" );
+        }
+
+      public override void ApplyHeaders( WebHeaderCollection headers )
+      {
+         headers[ HttpRequestHeader.UserAgent ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.183 Safari/537.36 Vivaldi/1.96.1147.55";
+         headers[ HttpRequestHeader.Accept ] = "*/*";
+         headers[ HttpRequestHeader.AcceptCharset ] = "UTF-8";
+      }
+
+        public override bool TryExtractTranslated(string result, out string translated)
+        {
+            try
+            {
+
+                var obj = JSON.Parse(result);
+                var lineBuilder = new StringBuilder(result.Length);
+
+                var code = obj.AsObject["code"].ToString();
+
+                if (code == "200")
+                {
+                    var token = obj.AsObject["text"].ToString();
+                    token = token.Substring(2, token.Length - 4).UnescapeJson();
+                    if (String.IsNullOrEmpty(token))
+                    {
+                        translated = null;
+                        return false;
+                    }
+
+                    if (!lineBuilder.EndsWithWhitespaceOrNewline()) lineBuilder.Append("\n");
+                    lineBuilder.Append(token);
+
+                    translated = lineBuilder.ToString();
+                    return true;
+                } else
+                {
+                    translated = null;
+                    return false;
+                }                
+            }
+            catch (Exception)
+            {
+                translated = null;
+                return false;
+            }
+        }
+
+        public override string GetServiceUrl(string untranslatedText, string from, string to)
+        {
+            return string.Format(HttpsServicePointTemplateUrl, from, to, WWW.EscapeURL(untranslatedText), Settings.YandexAPIKey);
+        }
+    }
+}

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

@@ -2,9 +2,13 @@
 
   <PropertyGroup>
     <TargetFramework>net35</TargetFramework>
-    <Version>2.6.0</Version>
+     <Version>2.8.0</Version>
   </PropertyGroup>
 
+  <ItemGroup>
+    <PackageReference Include="Jurassic" Version="2.2.2" />
+  </ItemGroup>
+
   <ItemGroup>
     <Reference Include="0Harmony">
       <HintPath>..\..\libs\0Harmony.dll</HintPath>
@@ -20,4 +24,8 @@
     </Reference>
   </ItemGroup>
 
+  <ItemGroup>
+    <Folder Include="MonoHttp\" />
+  </ItemGroup>
+
 </Project>

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

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

+ 17 - 7
src/XUnity.AutoTranslator.Setup/Program.cs

@@ -2,13 +2,13 @@
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
-using IWshRuntimeLibrary;
 using XUnity.AutoTranslator.Setup.Properties;
 
 namespace XUnity.AutoTranslator.Setup
 {
    class Program
    {
+      [STAThread]
       static void Main( string[] args )
       {
          var gamePath = Environment.CurrentDirectory;
@@ -47,6 +47,7 @@ namespace XUnity.AutoTranslator.Setup
             AddFile( Path.Combine( managedDir, "0Harmony.dll" ), Resources._0Harmony );
             AddFile( Path.Combine( managedDir, "ExIni.dll" ), Resources.ExIni );
             AddFile( Path.Combine( managedDir, "ReiPatcher.exe" ), Resources.ReiPatcher ); // needed because file is modified by attribute in ReiPatcher... QQ
+            AddFile( Path.Combine( managedDir, "Jurassic.dll" ), Resources.Jurassic );
             AddFile( Path.Combine( managedDir, "XUnity.AutoTranslator.Plugin.Core.dll" ), Resources.XUnity_AutoTranslator_Plugin_Core, true );
 
             // create an .ini file for each launcher, if it does not already exist
@@ -122,13 +123,22 @@ namespace XUnity.AutoTranslator.Setup
       public static void CreateShortcut( string shortcutName, string shortcutPath, string targetFileLocation )
       {
          string shortcutLocation = Path.Combine( shortcutPath, shortcutName );
-         WshShell shell = new WshShell();
-         IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut( shortcutLocation );
 
-         shortcut.WorkingDirectory = Path.GetDirectoryName( targetFileLocation );
-         shortcut.TargetPath = targetFileLocation;
-         shortcut.Arguments = "-c \"" + Path.GetFileNameWithoutExtension( shortcutName ) + ".ini\"";
-         shortcut.Save();
+         // Create empty .lnk file
+         File.WriteAllBytes( shortcutName, new byte[ 0 ] );
+
+         // Create a ShellLinkObject that references the .lnk file
+         Shell32.Shell shl = new Shell32.Shell();
+         Shell32.Folder dir = shl.NameSpace( Path.GetDirectoryName( shortcutLocation ) );
+         Shell32.FolderItem itm = dir.Items().Item( shortcutName );
+         Shell32.ShellLinkObject lnk = (Shell32.ShellLinkObject)itm.GetLink;
+
+         // Set the .lnk file properties
+         lnk.Path = targetFileLocation;
+         lnk.Arguments = "-c \"" + Path.GetFileNameWithoutExtension( shortcutName ) + ".ini\"";
+         lnk.WorkingDirectory = Path.GetDirectoryName( targetFileLocation );
+
+         lnk.Save( shortcutName );
       }
    }
 }

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

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

+ 4 - 1
src/XUnity.AutoTranslator.Setup/Properties/Resources.resx

@@ -121,6 +121,9 @@
   <data name="ExIni" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\..\..\libs\ExIni.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
+  <data name="Jurassic" type="System.Resources.ResXFileRef, System.Windows.Forms">
+    <value>..\..\XUnity.AutoTranslator.Plugin.Core\bin\Release\net35\Jurassic.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </data>
   <data name="Mono_Cecil" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\..\..\libs\Mono.Cecil.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
@@ -143,7 +146,7 @@
     <value>..\..\XUnity.AutoTranslator.Patcher\bin\Release\net35\XUnity.AutoTranslator.Patcher.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   <data name="XUnity_AutoTranslator_Plugin_Core" type="System.Resources.ResXFileRef, System.Windows.Forms">
-    <value>..\..\XUnity.AutoTranslator.Patcher\bin\Release\net35\XUnity.AutoTranslator.Plugin.Core.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>..\..\XUnity.AutoTranslator.Plugin.Core\bin\Release\net35\XUnity.AutoTranslator.Plugin.Core.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </data>
   <data name="_0Harmony" type="System.Resources.ResXFileRef, System.Windows.Forms">
     <value>..\..\..\libs\0Harmony.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>

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

@@ -4,19 +4,19 @@
     <OutputType>Exe</OutputType>
     <TargetFramework>net40</TargetFramework>
     <AssemblyName>SetupReiPatcherAndAutoTranslator</AssemblyName>
-    <Version>2.6.0</Version>
+     <Version>2.8.0</Version>
   </PropertyGroup>
 
   <ItemGroup>
-    <COMReference Include="IWshRuntimeLibrary.dll">
-      <Guid>f935dc20-1cf0-11d0-adb9-00c04fd58a0b</Guid>
+    <COMReference Include="Shell32.dll">
+      <Guid>50a7e9b0-70ef-11d1-b75a-00a0c90564fe</Guid>
       <VersionMajor>1</VersionMajor>
       <VersionMinor>0</VersionMinor>
       <WrapperTool>tlbimp</WrapperTool>
       <Lcid>0</Lcid>
       <Isolated>false</Isolated>
-      <Private>false</Private>
       <EmbedInteropTypes>true</EmbedInteropTypes>
+      <Private>false</Private>
     </COMReference>
   </ItemGroup>