فهرست منبع

* 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
* Disabled all concurrency for web requests
* Changed hooking, such that if text framework fails, the others wont also fail

Scrublord1336 6 سال پیش
والد
کامیت
c56f6b641d

+ 7 - 0
CHANGELOG.md

@@ -1,3 +1,10 @@
+### 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
+ * Disabled all concurrency for web requests
+ * Changed hooking, such that if text framework fails, the others wont also fail
+
 ### 2.5.0
  * Various new rate limiting patterns to prevent spam to configured translate endpoint
  * Copy to clipboard feature

+ 16 - 3
src/XUnity.AutoTranslator.Patcher/Patcher.cs

@@ -29,20 +29,27 @@ namespace XUnity.AutoTranslator.Patcher
       {
          get
          {
-            return "2.0.1";
+            return "2.6.0";
          }
       }
 
       public override void PrePatch()
       {
-         RPConfig.RequestAssembly( "UnityEngine.dll" );
+         if( ManagedDllExists( "UnityEngine.dll" ) )
+         {
+            RPConfig.RequestAssembly( "UnityEngine.dll" );
+         }
+         if( ManagedDllExists( "UnityEngine.CoreModule.dll" ) )
+         {
+            RPConfig.RequestAssembly( "UnityEngine.CoreModule.dll" );
+         }
 
          _hookAssembly = LoadAssembly( "XUnity.AutoTranslator.Plugin.Core.dll" );
       }
 
       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 == "UnityEngine" ) && !HasAttribute( this, args.Assembly, "XUnity.AutoTranslator.Plugin.Core" );
       }
 
       public override void Patch( PatcherArguments args )
@@ -66,6 +73,12 @@ namespace XUnity.AutoTranslator.Patcher
          SetPatchedAttribute( args.Assembly, "XUnity.AutoTranslator.Plugin.Core" );
       }
 
+      public bool ManagedDllExists( string name )
+      {
+         string path = Path.Combine( RPConfig.ConfigFile.GetSection( "ReiPatcher" ).GetKey( "AssembliesDir" ).Value, name );
+         return File.Exists( path );
+      }
+
       public static AssemblyDefinition LoadAssembly( string name )
       {
          string path = Path.Combine( RPConfig.ConfigFile.GetSection( "ReiPatcher" ).GetKey( "AssembliesDir" ).Value, name );

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

@@ -2,9 +2,9 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <AssemblyVersion>2.5.0.0</AssemblyVersion>
-      <FileVersion>2.5.0.0</FileVersion>
-      <Version>2.5.0</Version>
+      <AssemblyVersion>2.6.0.0</AssemblyVersion>
+      <FileVersion>2.6.0.0</FileVersion>
+      <Version>2.6.0</Version>
    </PropertyGroup>
 
    <ItemGroup>

+ 5 - 5
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs

@@ -336,7 +336,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       private string Override_TextChanged( object ui, string text )
       {
-         if( _hooksEnabled && !Settings.IsShutdown )
+         if( _hooksEnabled )
          {
             return TranslateOrQueueWebJob( ui, text, true );
          }
@@ -345,7 +345,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       public void Hook_TextChanged( object ui )
       {
-         if( _hooksEnabled && !Settings.IsShutdown )
+         if( _hooksEnabled )
          {
             TranslateOrQueueWebJob( ui, null, false );
          }
@@ -353,7 +353,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       public void Hook_TextInitialized( object ui )
       {
-         if( _hooksEnabled && !Settings.IsShutdown )
+         if( _hooksEnabled )
          {
             TranslateOrQueueWebJob( ui, null, true );
          }
@@ -560,7 +560,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                                     // Lets try not to spam a service that might not be there...
                                     if( AutoTranslateClient.IsConfigured )
                                     {
-                                       if( _consecutiveErrors < Settings.MaxErrors )
+                                       if( _consecutiveErrors < Settings.MaxErrors && !Settings.IsShutdown )
                                        {
                                           var job = GetOrCreateTranslationJobFor( stabilizedTextKey );
                                           job.Components.Add( ui );
@@ -591,7 +591,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                      // Lets try not to spam a service that might not be there...
                      if( AutoTranslateClient.IsConfigured )
                      {
-                        if( _consecutiveErrors < Settings.MaxErrors )
+                        if( _consecutiveErrors < Settings.MaxErrors && !Settings.IsShutdown )
                         {
                            GetOrCreateTranslationJobFor( textKey );
                         }

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

@@ -13,10 +13,10 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static readonly int MaxErrors = 5;
       public static readonly float ClipboardDebounceTime = 1f;
       public static readonly int MaxTranslationsBeforeSlowdown = 1000;
-      public static readonly int MaxTranslationsBeforeShutdown = 6000;
+      public static readonly int MaxTranslationsBeforeShutdown = 10000;
       public static readonly int MaxUnstartedJobs = 3500;
 
-      public static int DefaultMaxConcurrentTranslations = 2;
+      public static int DefaultMaxConcurrentTranslations = 1;
       public static int MaxConcurrentTranslations = DefaultMaxConcurrentTranslations;
       public static bool IsShutdown = false;
 

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

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

@@ -18,6 +18,7 @@ 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 )
       {

+ 25 - 4
src/XUnity.AutoTranslator.Plugin.Core/Hooks/HooksSetup.cs

@@ -21,11 +21,11 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    {
       public static void InstallHooks( Func<object, string, string> defaultHook )
       {
+         var harmony = HarmonyInstance.Create( "gravydevsupreme.xunity.autotranslator" );
+
+         bool success = false;
          try
          {
-            var harmony = HarmonyInstance.Create( "gravydevsupreme.xunity.autotranslator" );
-
-            bool success = false;
             if( Settings.EnableUGUI )
             {
                success = SetupHook( KnownEvents.OnUnableToTranslateUGUI, defaultHook );
@@ -34,7 +34,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
                   harmony.PatchAll( UGUIHooks.All );
                }
             }
+         }
+         catch( Exception e )
+         {
+            Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: An error occurred while setting up hooks for UGUI. " + Environment.NewLine + e );
+         }
 
+         try
+         {
             if( Settings.EnableTextMeshPro )
             {
                success = SetupHook( KnownEvents.OnUnableToTranslateTextMeshPro, defaultHook );
@@ -43,7 +50,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
                   harmony.PatchAll( TextMeshProHooks.All );
                }
             }
+         }
+         catch( Exception e )
+         {
+            Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: An error occurred while setting up hooks for TextMeshPro. " + Environment.NewLine + e );
+         }
 
+         try
+         {
             if( Settings.EnableNGUI )
             {
                success = SetupHook( KnownEvents.OnUnableToTranslateNGUI, defaultHook );
@@ -52,7 +66,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
                   harmony.PatchAll( NGUIHooks.All );
                }
             }
+         }
+         catch( Exception e )
+         {
+            Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: An error occurred while setting up hooks for NGUI. " + Environment.NewLine + e );
+         }
 
+         try
+         {
             if( Settings.EnableIMGUI )
             {
                harmony.PatchAll( IMGUIHooks.All );
@@ -60,7 +81,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          }
          catch( Exception e )
          {
-            Console.WriteLine( "ERROR WHILE INITIALIZING AUTO TRANSLATOR: " + Environment.NewLine + e );
+            Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: An error occurred while setting up hooks for IMGUI. " + Environment.NewLine + e );
          }
       }
 

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

@@ -19,7 +19,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
          typeof( DoButtonHook ),
          typeof( DoModalWindowHook ),
          typeof( DoWindowHook ),
-         typeof( DoButtonGridHook ),
+         //typeof( DoButtonGridHook ),
          typeof( DoTextFieldHook ),
          typeof( DoToggleHook ),
       };
@@ -159,27 +159,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

+ 11 - 11
src/XUnity.AutoTranslator.Plugin.Core/PluginLoader.cs

@@ -46,19 +46,19 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          Load();
       }
+   }
 
-      class Bootstrapper : MonoBehaviour
-      {
-         public event Action Destroyed = delegate { };
+   class Bootstrapper : MonoBehaviour
+   {
+      public event Action Destroyed = delegate { };
 
-         void Start()
-         {
-            Destroy( gameObject );
-         }
-         void OnDestroy()
-         {
-            Destroyed?.Invoke();
-         }
+      void Start()
+      {
+         Destroy( gameObject );
+      }
+      void OnDestroy()
+      {
+         Destroyed?.Invoke();
       }
    }
 }

+ 82 - 48
src/XUnity.AutoTranslator.Plugin.Core/Web/AutoTranslateClient.cs

@@ -4,16 +4,20 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Net;
+using System.Reflection;
 using System.Text;
 using System.Threading;
-using UnityEngine;
+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;
@@ -40,77 +44,107 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       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 );
-
-         using( var www = new WWW( url, null, headers ) )
+         while( true )
          {
-            _runningTranslations++;
-            yield return www;
-            _runningTranslations--;
+            var url = _endpoint.GetServiceUrl( untranslated, from, to );
+            var headers = new Dictionary<string, string>();
+            _endpoint.ApplyHeaders( headers );
 
-            _translationCount++;
-            if( Settings.MaxConcurrentTranslations == Settings.DefaultMaxConcurrentTranslations )
+            var failed = false;
+            object www = WwwConstructor.Invoke( new object[] { url, null, headers } );
+            try
             {
-               if( _translationCount > Settings.MaxTranslationsBeforeSlowdown )
-               {
-                  Settings.MaxConcurrentTranslations = 1;
-                  Console.WriteLine( "[XUnity.AutoTranslator][WARN]: Maximum translations per session reached. Entering slowdown mode." );
-               }
-            }
+               _runningTranslations++;
+               yield return www;
+               _runningTranslations--;
 
-            if( !Settings.IsShutdown )
-            {
-               if( _translationCount > Settings.MaxTranslationsBeforeShutdown )
+               _translationCount++;
+               //if( Settings.MaxConcurrentTranslations == Settings.DefaultMaxConcurrentTranslations )
+               //{
+               //   if( _translationCount > Settings.MaxTranslationsBeforeSlowdown )
+               //   {
+               //      Settings.MaxConcurrentTranslations = 1;
+               //      Console.WriteLine( "[XUnity.AutoTranslator][WARN]: Maximum translations per session reached. Entering slowdown mode." );
+               //   }
+               //}
+
+               if( !Settings.IsShutdown )
                {
-                  Settings.IsShutdown = true;
-                  Console.WriteLine( "[XUnity.AutoTranslator][ERROR]: Maximum translations per session reached. Shutting plugin down." );
+                  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 = www.error;
-            }
-            catch( Exception e )
-            {
-               error = e.ToString();
-            }
+               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 = www.text;
-               if( text != null )
+               if( error != null )
+               {
+                  failed = true;
+               }
+               else
                {
-                  try
+                  var text = (string)AccessTools.Property( Types.WWW, "text" ).GetValue( www, null ); ;
+                  if( text != null )
                   {
-                     if( _endpoint.TryExtractTranslated( text, out var translatedText ) )
+                     try
                      {
-                        translatedText = translatedText ?? string.Empty;
-                        success( translatedText );
+                        if( _endpoint.TryExtractTranslated( text, out var translatedText ) )
+                        {
+                           translatedText = translatedText ?? string.Empty;
+                           success( translatedText );
+                        }
+                        else
+                        {
+                           failed = true;
+                        }
                      }
-                     else
+                     catch
                      {
-                        failure();
+                        failed = true;
                      }
                   }
-                  catch
+                  else
                   {
-                     failure();
+                     failed = true;
                   }
                }
+            }
+            finally
+            {
+               var disposable = www as IDisposable;
+               if( disposable != null )
+               {
+                  disposable.Dispose();
+               }
+            }
+
+            if( failed )
+            {
+               if( ( _endpoint as ISupportFallback )?.Fallback() == true )
+               {
+                  // we can attempt with fallback strategy, simply retry
+               }
                else
                {
                   failure();
+                  break;
                }
             }
+            else
+            {
+               break;
+            }
          }
       }
    }

+ 85 - 17
src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateEndpoint.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.Net;
 using System.Text;
@@ -11,12 +12,14 @@ using XUnity.AutoTranslator.Plugin.Core.Extensions;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Web
 {
-   public class GoogleTranslateEndpoint : KnownEndpoint
+   public class GoogleTranslateEndpoint : KnownEndpoint, ISupportFallback
    {
       //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=gtx&sl={0}&tl={1}&dt=t&q={2}";
-      private static readonly string HttpsServicePointTemplateUrl = "https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}";
+      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;
 
       public GoogleTranslateEndpoint()
          : base( KnownEndpointNames.GoogleTranslate )
@@ -28,7 +31,6 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       {
          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" ] = "*/*";
-         headers[ "Accept-Charset" ] = "UTF-8";
       }
 
       public override void ApplyHeaders( WebHeaderCollection headers )
@@ -38,22 +40,70 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
          headers[ HttpRequestHeader.AcceptCharset ] = "UTF-8";
       }
 
-      public override void ConfigureServicePointManager()
+      // TKK Approach stolen from Translation Aggregator r190.
+
+      private long Vi( long r, string o )
       {
-         try
+         for( var t = 0 ; t < o.Length ; t += 3 )
          {
-            //ServicePointManager.ServerCertificateValidationCallback += ( sender, certificate, chain, sslPolicyErrors ) =>
-            //{
-            //   return certificate.Issuer == CertificateIssuer;
-            //};
-
-            ServicePoint = ServicePointManager.FindServicePoint( new Uri( "http://translate.googleapis.com" ) );
-            ServicePoint.ConnectionLimit = Settings.MaxConcurrentTranslations;
-            
+            long a = o[ t + 2 ];
+            a = a >= 'a' ? a - 87 : a - '0';
+            a = '+' == o[ t + 1 ] ? r >> (int)a : r << (int)a;
+            r = '+' == o[ t ] ? r + a & 4294967295 : r ^ a;
          }
-         catch
+
+         return r;
+      }
+
+      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++ )
          {
+            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 );
+               S.Add( 63 & A | 128 );
+            }
          }
+
+         const string F = "+-a^+6";
+         const string D = "+-3^+b+-f";
+         long p = m;
+
+         for( var b = 0 ; b < S.Count ; b++ )
+         {
+            p += S[ b ];
+            p = Vi( p, F );
+         }
+
+         p = Vi( p, D );
+         p ^= s;
+         if( 0 > p )
+            p = ( 2147483647 & p ) + 2147483648;
+
+         p %= (long)1e6;
+
+         return p.ToString( CultureInfo.InvariantCulture ) + "." + ( p ^ m ).ToString( CultureInfo.InvariantCulture );
+      }
+
+      public override void ConfigureServicePointManager()
+      {
       }
 
       public override bool TryExtractTranslated( string result, out string translated )
@@ -85,7 +135,25 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       public override string GetServiceUrl( string untranslatedText, string from, string to )
       {
-         return string.Format( Settings.EnableSSL ? HttpsServicePointTemplateUrl : HttpServicePointTemplateUrl, from, to, WWW.EscapeURL( untranslatedText ) );
+         if( _hasFallenBack )
+         {
+            return string.Format( Settings.EnableSSL ? FallbackHttpsServicePointTemplateUrl : FallbackHttpServicePointTemplateUrl, from, to, WWW.EscapeURL( untranslatedText ) );
+         }
+         else
+         {
+            return string.Format( Settings.EnableSSL ? HttpsServicePointTemplateUrl : HttpServicePointTemplateUrl, from, to, Tk( untranslatedText ), WWW.EscapeURL( untranslatedText ) );
+         }
+      }
+
+      public bool Fallback()
+      {
+         if( !_hasFallenBack )
+         {
+            _hasFallenBack = true;
+            return true;
+         }
+
+         return false;
       }
    }
 }

+ 12 - 0
src/XUnity.AutoTranslator.Plugin.Core/Web/ISupportFallback.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Web
+{
+   public interface ISupportFallback
+   {
+      bool Fallback();
+   }
+}

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

@@ -1,10 +1,10 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFramework>net35</TargetFramework>
-    <AssemblyVersion>2.5.0.0</AssemblyVersion>
-    <FileVersion>2.5.0.0</FileVersion>
-    <Version>2.5.0</Version>
+    <AssemblyVersion>2.6.0.0</AssemblyVersion>
+    <FileVersion>2.6.0.0</FileVersion>
+    <Version>2.6.0</Version>
   </PropertyGroup>
 
   <ItemGroup>

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

@@ -2,9 +2,9 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <AssemblyVersion>2.5.0.0</AssemblyVersion>
-      <FileVersion>2.5.0.0</FileVersion>
-      <Version>2.5.0</Version>
+      <AssemblyVersion>2.6.0.0</AssemblyVersion>
+      <FileVersion>2.6.0.0</FileVersion>
+      <Version>2.6.0</Version>
    </PropertyGroup>
 
    <ItemGroup>