Преглед изворни кода

initial texture support implementation

randoman пре 6 година
родитељ
комит
b507f469ff
25 измењених фајлова са 1368 додато и 168 уклоњено
  1. 6 1
      CHANGELOG.md
  2. 391 56
      src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs
  3. 15 2
      src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs
  4. 20 14
      src/XUnity.AutoTranslator.Plugin.Core/Constants/ClrTypes.cs
  5. 48 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/ComponentExtensions.cs
  6. 89 17
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/ObjectExtensions.cs
  7. 17 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/StringExtensions.cs
  8. 56 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/TextureExtensions.cs
  9. 2 2
      src/XUnity.AutoTranslator.Plugin.Core/Features.cs
  10. 8 12
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/HooksSetup.cs
  11. 20 20
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/IMGUIHooks.cs
  12. 4 4
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/NGUIHooks.cs
  13. 298 0
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/NGUIImageHooks.cs
  14. 18 18
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/TextMeshProHooks.cs
  15. 4 4
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/UGUIHooks.cs
  16. 120 0
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/UGUIImageHooks.cs
  17. 4 4
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/UtageHooks.cs
  18. 7 0
      src/XUnity.AutoTranslator.Plugin.Core/ImageTranslationInfo.cs
  19. 8 7
      src/XUnity.AutoTranslator.Plugin.Core/TextTranslationInfo.cs
  20. 9 0
      src/XUnity.AutoTranslator.Plugin.Core/TextureHashGenerationStrategy.cs
  21. 105 0
      src/XUnity.AutoTranslator.Plugin.Core/TextureTranslationInfo.cs
  22. 4 4
      src/XUnity.AutoTranslator.Plugin.Core/UtageSupport/UtageHelpers.cs
  23. 46 0
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/HashHelper.cs
  24. 66 0
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/TextureHelper.cs
  25. 3 3
      src/XUnity.AutoTranslator.Plugin.Core/Web/KnownWwwEndpoint.cs

+ 6 - 1
CHANGELOG.md

@@ -1,4 +1,9 @@
-### 2.15.4
+### 3.0.0
+ * FEATURE - Support image dumping and loading (not automatic!). Disabled by default
+ * BUG FIX - Fixed toggle translation which was broken in 2.15.4
+ * MISC - Cleaned up configuration parameter names. Automatically migrated
+
+### 2.15.4
  * MISC - Added configuration option to apply 'UI resize behaviour' to all components regardless of them being translated: ForceUIResizing
  * MISC - Added configuration option to change line spacing for UGUI within the 'UI resize behaviour': ResizeUILineSpacingScale
 

+ 391 - 56
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs

@@ -89,6 +89,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// </summary>
       private HashSet<string> _immediatelyTranslating = new HashSet<string>();
 
+      private Dictionary<string, byte[]> _translatedImages = new Dictionary<string, byte[]>( StringComparer.InvariantCultureIgnoreCase );
+
       private object _advEngine;
       private float? _nextAdvUpdate;
 
@@ -99,7 +101,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private float _translationsQueuedPerSecond;
 
       private bool _isInTranslatedMode = true;
-      private bool _hooksEnabled = true;
+      private bool _textHooksEnabled = true;
+      private bool _imageHooksEnabled = true;
       private bool _batchLogicHasFailed = false;
 
       private int _availableBatchOperations = Settings.MaxAvailableBatchOperations;
@@ -201,6 +204,12 @@ namespace XUnity.AutoTranslator.Plugin.Core
             .Select( x => x.Replace( "/", "\\" ) );
       }
 
+      private IEnumerable<string> GetTextureFiles()
+      {
+         return Directory.GetFiles( Path.Combine( Config.Current.DataPath, Settings.TextureDirectory ), $"*.png", SearchOption.AllDirectories )
+            .Select( x => x.Replace( "/", "\\" ) );
+      }
+
       private void MaintenanceLoop( object state )
       {
          while( true )
@@ -274,6 +283,16 @@ namespace XUnity.AutoTranslator.Plugin.Core
                   LoadTranslationsInFile( fullFileName );
                }
             }
+
+            if( Settings.EnableTextureTranslation || Settings.EnableTextureDumping )
+            {
+               _translatedImages.Clear();
+               Directory.CreateDirectory( Path.Combine( Config.Current.DataPath, Settings.TextureDirectory ) );
+               foreach( var fullFileName in GetTextureFiles() )
+               {
+                  RegisterImageDataAndHash( fullFileName, null, null );
+               }
+            }
          }
          catch( Exception e )
          {
@@ -281,6 +300,33 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
+      private void RegisterImageDataAndHash( string fullFileName, string key, byte[] data )
+      {
+         if( key == null )
+         {
+            var fileName = Path.GetFileNameWithoutExtension( fullFileName );
+            var startHash = fileName.LastIndexOf( "[" );
+            var endHash = fileName.LastIndexOf( "]" );
+
+            if( endHash > -1 && startHash > -1 && endHash > startHash )
+            {
+               var takeFrom = startHash + 1;
+               key = fileName.Substring( takeFrom, endHash - takeFrom );
+            }
+         }
+
+         if( data == null )
+         {
+            data = File.ReadAllBytes( fullFileName );
+         }
+
+
+         if( key != null )
+         {
+            _translatedImages[ key ] = data;
+         }
+      }
+
       private void LoadTranslationsInFile( string fullFileName )
       {
          if( File.Exists( fullFileName ) )
@@ -584,6 +630,16 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
+      private bool IsImageTranslated( string hash )
+      {
+         return _translatedImages.ContainsKey( hash );
+      }
+
+      private bool TryGetTranslatedImage( string hash, out byte[] data )
+      {
+         return _translatedImages.TryGetValue( hash, out data );
+      }
+
       private void AddTranslation( string key, string value )
       {
          _translations[ key ] = value;
@@ -663,9 +719,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       public string Hook_TextChanged_WithResult( object ui, string text )
       {
-         if( !ui.IsKnownType() ) return null;
+         if( !ui.IsKnownTextType() ) return null;
 
-         if( _hooksEnabled && !_temporarilyDisabled )
+         if( _textHooksEnabled && !_temporarilyDisabled )
          {
             return TranslateOrQueueWebJob( ui, text, false );
          }
@@ -674,9 +730,9 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       public string ExternalHook_TextChanged_WithResult( object ui, string text )
       {
-         if( !ui.IsKnownType() ) return null;
+         if( !ui.IsKnownTextType() ) return null;
 
-         if( _hooksEnabled && !_temporarilyDisabled )
+         if( _textHooksEnabled && !_temporarilyDisabled )
          {
             return TranslateOrQueueWebJob( ui, text, true );
          }
@@ -685,13 +741,29 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       public void Hook_TextChanged( object ui )
       {
-         if( _hooksEnabled && !_temporarilyDisabled )
+         if( _textHooksEnabled && !_temporarilyDisabled )
          {
             TranslateOrQueueWebJob( ui, null, false );
          }
       }
 
-      private void SetTranslatedText( object ui, string translatedText, TranslationInfo info )
+      public void Hook_ImageChangedOnComponent( object source )
+      {
+         if( !_imageHooksEnabled ) return;
+         if( !source.IsKnownImageType() ) return;
+
+         HandleImage( source, null );
+      }
+
+      public void Hook_ImageChanged( Texture2D texture )
+      {
+         if( !_imageHooksEnabled ) return;
+         if( texture == null ) return;
+
+         HandleImage( null, texture );
+      }
+
+      private void SetTranslatedText( object ui, string translatedText, TextTranslationInfo info )
       {
          info?.SetTranslatedText( translatedText );
 
@@ -705,7 +777,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          if( _hasOverrideFont )
          {
-            var info = ui.GetTranslationInfo();
+            var info = ui.GetTextTranslationInfo();
             if( _overrideFont )
             {
                info?.ChangeFont( ui );
@@ -718,12 +790,12 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
          if( Settings.ForceUIResizing )
          {
-            var info = ui.GetTranslationInfo();
+            var info = ui.GetTextTranslationInfo();
             if( info?.IsCurrentlySettingText == false )
             {
                // force UI resizing is highly problematic for NGUI because text should somehow
                // be set after changing "resize" properties... brilliant stuff
-               if( ui.GetType() != Constants.Types.UILabel )
+               if( ui.GetType() != ClrTypes.UILabel )
                {
                   info?.ResizeUI( ui );
                }
@@ -734,13 +806,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
       /// <summary>
       /// Sets the text of a UI  text, while ensuring this will not fire a text changed event.
       /// </summary>
-      private void SetText( object ui, string text, bool isTranslated, TranslationInfo info )
+      private void SetText( object ui, string text, bool isTranslated, TextTranslationInfo info )
       {
          if( !info?.IsCurrentlySettingText ?? true )
          {
             try
             {
-               _hooksEnabled = false;
+               _textHooksEnabled = false;
 
                if( info != null )
                {
@@ -778,7 +850,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             }
             finally
             {
-               _hooksEnabled = true;
+               _textHooksEnabled = true;
 
                if( info != null )
                {
@@ -801,7 +873,30 @@ namespace XUnity.AutoTranslator.Plugin.Core
          return str.Length <= ( Settings.MaxCharactersPerTranslation / 2 );
       }
 
-      public bool ShouldTranslate( object ui, bool ignoreComponentState )
+      public bool ShouldTranslateImageComponent( object ui )
+      {
+         var component = ui as Component;
+         if( component != null )
+         {
+            // dummy check
+            var go = component.gameObject;
+            var ignore = go.HasIgnoredName();
+            if( ignore )
+            {
+               return false;
+            }
+
+            var behaviour = component as Behaviour;
+            if( behaviour?.isActiveAndEnabled == false )
+            {
+               return false;
+            }
+         }
+
+         return true;
+      }
+
+      public bool ShouldTranslateTextComponent( object ui, bool ignoreComponentState )
       {
          var component = ui as Component;
          if( component != null )
@@ -823,8 +918,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
                }
             }
 
-            var inputField = component.gameObject.GetFirstComponentInSelfOrAncestor( Constants.Types.InputField )
-               ?? component.gameObject.GetFirstComponentInSelfOrAncestor( Constants.Types.TMP_InputField );
+            var inputField = component.gameObject.GetFirstComponentInSelfOrAncestor( ClrTypes.InputField )
+               ?? component.gameObject.GetFirstComponentInSelfOrAncestor( ClrTypes.TMP_InputField );
 
             return inputField == null;
          }
@@ -834,7 +929,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
       private string TranslateOrQueueWebJob( object ui, string text, bool ignoreComponentState )
       {
-         var info = ui.GetTranslationInfo();
+         var info = ui.GetTextTranslationInfo();
 
          if( _ongoingOperations.Contains( ui ) )
          {
@@ -858,19 +953,218 @@ namespace XUnity.AutoTranslator.Plugin.Core
          return null;
       }
 
-      public static bool IsCurrentlySetting( TranslationInfo info )
+      public static bool IsCurrentlySetting( TextTranslationInfo info )
       {
          if( info == null ) return false;
 
          return info.IsCurrentlySettingText;
       }
 
-      private string TranslateImmediate( object ui, string text, TranslationInfo info, bool ignoreComponentState )
+      private void HandleImage( object source, Texture2D texture )
+      {
+         // Make work with raw textures somehow?????
+         // work with obscure games? Sprite?
+
+         // Test without texture replacement (old! in old games!)
+
+         if( Settings.EnableTextureDumping )
+         {
+            try
+            {
+               DumpTexture( source, texture );
+            }
+            catch( Exception e )
+            {
+               Logger.Current.Error( e, "An error occurred while dumping texture." );
+            }
+         }
+
+         if( Settings.EnableTextureTranslation )
+         {
+            try
+            {
+               TranslateTexture( source, texture, false );
+            }
+            catch( Exception e )
+            {
+               Logger.Current.Error( e, "An error occurred while translating texture." );
+            }
+         }
+      }
+
+      private void TranslateTexture( object source, Texture2D texture, bool forceReload )
+      {
+         try
+         {
+            _imageHooksEnabled = false;
+
+            texture = texture ?? source.GetTexture();
+            if( texture == null ) return;
+
+            var tti = texture.GetTextureTranslationInfo();
+            var iti = source.GetImageTranslationInfo();
+            var key = tti.GetKey( texture );
+            if( string.IsNullOrEmpty( key ) ) return;
+
+            if( TryGetTranslatedImage( key, out var newData ) )
+            {
+               if( _isInTranslatedMode )
+               {
+                  // handle texture
+                  if( !tti.IsTranslated || forceReload )
+                  {
+                     try
+                     {
+                        texture.LoadImageEx( newData, tti.IsNonReadable( texture ) );
+                     }
+                     finally
+                     {
+                        tti.IsTranslated = true;
+                     }
+                  }
+
+                  // handle containing component
+                  if( iti != null )
+                  {
+                     if( !iti.IsTranslated || forceReload )
+                     {
+                        try
+                        {
+                           source.SetAllDirtyEx();
+                        }
+                        finally
+                        {
+                           iti.IsTranslated = true;
+                        }
+                     }
+                  }
+               }
+            }
+            else
+            {
+               // if we cannot find the texture, and the texture is considered translated... hmmm someone has removed a file
+
+               // handle texture
+               var originalData = tti.GetOriginalData( texture );
+               if( originalData != null )
+               {
+                  if( tti.IsTranslated )
+                  {
+                     try
+                     {
+                        texture.LoadImageEx( originalData, tti.IsNonReadable( texture ) );
+                     }
+                     finally
+                     {
+                        tti.IsTranslated = true;
+                     }
+                  }
+
+                  // handle containing component
+                  if( iti != null )
+                  {
+                     if( iti.IsTranslated )
+                     {
+                        try
+                        {
+                           source.SetAllDirtyEx();
+                        }
+                        finally
+                        {
+                           iti.IsTranslated = true;
+                        }
+                     }
+                  }
+               }
+            }
+
+            if( !_isInTranslatedMode )
+            {
+               var originalData = tti.GetOriginalData( texture );
+               if( originalData != null )
+               {
+                  // handle texture
+                  if( tti.IsTranslated )
+                  {
+                     try
+                     {
+                        texture.LoadImageEx( originalData, tti.IsNonReadable( texture ) );
+                     }
+                     finally
+                     {
+                        tti.IsTranslated = false;
+                     }
+                  }
+
+                  // handle containing component
+                  if( iti != null )
+                  {
+                     if( iti.IsTranslated )
+                     {
+                        try
+                        {
+                           source.SetAllDirtyEx();
+                        }
+                        finally
+                        {
+                           iti.IsTranslated = false;
+                        }
+                     }
+                  }
+               }
+            }
+         }
+         finally
+         {
+            _imageHooksEnabled = true;
+         }
+      }
+
+      private void DumpTexture( object source, Texture2D texture )
+      {
+         try
+         {
+            _imageHooksEnabled = false;
+
+            texture = texture ?? source.GetTexture();
+            if( texture == null ) return;
+
+            var info = texture.GetTextureTranslationInfo();
+            if( info.HasDumpedAlternativeTexture ) return;
+
+            try
+            {
+               var key = info.GetKey( texture );
+               if( string.IsNullOrEmpty( key ) ) return;
+
+               if( !IsImageTranslated( key ) )
+               {
+                  var name = texture.GetTextureName().SanitizeForFileSystem();
+                  var root = Path.Combine( Config.Current.DataPath, Settings.TextureDirectory );
+                  var fullName = Path.Combine( root, name + " [" + key + "].png" );
+
+                  var originalData = info.GetOrCreateOriginalData( texture );
+                  File.WriteAllBytes( fullName, originalData );
+                  RegisterImageDataAndHash( fullName, key, originalData );
+               }
+            }
+            finally
+            {
+               info.HasDumpedAlternativeTexture = true;
+            }
+         }
+         finally
+         {
+            _imageHooksEnabled = true;
+         }
+      }
+
+      private string TranslateImmediate( object ui, string text, TextTranslationInfo info, bool ignoreComponentState )
       {
          // Get the trimmed text
          text = ( text ?? ui.GetText() ).TrimIfConfigured();
 
-         if( !string.IsNullOrEmpty( text ) && IsTranslatable( text ) && ShouldTranslate( ui, ignoreComponentState ) && !IsCurrentlySetting( info ) )
+         if( !string.IsNullOrEmpty( text ) && IsTranslatable( text ) && ShouldTranslateTextComponent( ui, ignoreComponentState ) && !IsCurrentlySetting( info ) )
          {
             info?.Reset( text );
 
@@ -895,11 +1189,12 @@ 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, bool supportsStabilization, bool ignoreComponentState, TranslationContext context = null )
+      private string TranslateOrQueueWebJobImmediate( object ui, string text, TextTranslationInfo info, bool supportsStabilization, bool ignoreComponentState, TranslationContext context = null )
       {
+         text = text ?? ui.GetText();
+
          // make sure text exists
          var originalText = text;
-         text = text ?? ui.GetText();
          if( context == null )
          {
             // Get the trimmed text
@@ -909,7 +1204,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          //Logger.Current.Debug( ui.GetType().Name + ": " + text );
 
          // Ensure that we actually want to translate this text and its owning UI element. 
-         if( !string.IsNullOrEmpty( text ) && IsTranslatable( text ) && ShouldTranslate( ui, ignoreComponentState ) && !IsCurrentlySetting( info ) )
+         if( !string.IsNullOrEmpty( text ) && IsTranslatable( text ) && ShouldTranslateTextComponent( ui, ignoreComponentState ) && !IsCurrentlySetting( info ) )
          {
             info?.Reset( originalText );
             var isSpammer = ui.IsSpammingComponent();
@@ -986,11 +1281,11 @@ namespace XUnity.AutoTranslator.Plugin.Core
                            {
                               _ongoingOperations.Remove( ui );
 
+                              originalText = stabilizedText;
+                              stabilizedText = stabilizedText.TrimIfConfigured();
+
                               if( !string.IsNullOrEmpty( stabilizedText ) && IsTranslatable( stabilizedText ) )
                               {
-                                 originalText = stabilizedText;
-                                 stabilizedText = stabilizedText.TrimIfConfigured();
-
                                  var stabilizedTextKey = new TranslationKey( ui, stabilizedText, false );
 
                                  QueueNewUntranslatedForClipboard( stabilizedTextKey );
@@ -1529,7 +1824,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                      var text = component.GetText().TrimIfConfigured();
                      if( text == job.Key.OriginalText )
                      {
-                        var info = component.GetTranslationInfo();
+                        var info = component.GetTextTranslationInfo();
                         SetTranslatedText( component, job.TranslatedText, info );
                      }
                   }
@@ -1565,7 +1860,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
                            {
                               if( translatedText != null )
                               {
-                                 var info = context.Component.GetTranslationInfo();
+                                 var info = context.Component.GetTextTranslationInfo();
                                  SetTranslatedText( context.Component, translatedText, info );
                               }
                            }
@@ -1580,8 +1875,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
 
                // Utage support
-               if( Constants.Types.AdvEngine != null
-                  && job.OriginalSources.Any( x => Constants.Types.AdvCommand.IsAssignableFrom( x.GetType() ) ) )
+               if( ClrTypes.AdvEngine != null
+                  && job.OriginalSources.Any( x => ClrTypes.AdvCommand.IsAssignableFrom( x.GetType() ) ) )
                {
                   _nextAdvUpdate = Time.time + 0.5f;
                }
@@ -1593,12 +1888,12 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          if( _advEngine == null )
          {
-            _advEngine = GameObject.FindObjectOfType( Constants.Types.AdvEngine );
+            _advEngine = GameObject.FindObjectOfType( Constants.ClrTypes.AdvEngine );
          }
 
          if( _advEngine != null )
          {
-            AccessTools.Method( Constants.Types.AdvEngine, "ChangeLanguage" )?.Invoke( _advEngine, new object[ 0 ] );
+            AccessTools.Method( Constants.ClrTypes.AdvEngine, "ChangeLanguage" )?.Invoke( _advEngine, new object[ 0 ] );
          }
       }
 
@@ -1606,17 +1901,36 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          LoadTranslations();
 
+         // FIXME: Translate TEXTURES first??? Problem if texture is not related to a component!
+
          foreach( var kvp in ObjectExtensions.GetAllRegisteredObjects() )
          {
-            var info = kvp.Value as TranslationInfo;
-            if( info != null && !string.IsNullOrEmpty( info.OriginalText ) )
+            var ui = kvp.Key;
+            try
             {
-               var key = new TranslationKey( kvp.Key, info.OriginalText, false );
-               if( TryGetTranslation( key, out string translatedText ) && !string.IsNullOrEmpty( translatedText ) )
+               if( ( ui as Component )?.gameObject?.activeSelf ?? false )
                {
-                  SetTranslatedText( kvp.Key, translatedText, info ); // no need to untemplatize the translated text
+                  var tti = kvp.Value as TextTranslationInfo;
+                  if( tti != null && !string.IsNullOrEmpty( tti.OriginalText ) )
+                  {
+                     var key = new TranslationKey( kvp.Key, tti.OriginalText, false );
+                     if( TryGetTranslation( key, out string translatedText ) && !string.IsNullOrEmpty( translatedText ) )
+                     {
+                        SetTranslatedText( kvp.Key, translatedText, tti ); // no need to untemplatize the translated text
+                     }
+                  }
+
+                  if( Settings.EnableTextureTranslation )
+                  {
+                     TranslateTexture( ui, null, true );
+                  }
                }
             }
+            catch( Exception )
+            {
+               // not super pretty, no...
+               ObjectExtensions.Remove( ui );
+            }
          }
       }
 
@@ -1666,19 +1980,22 @@ namespace XUnity.AutoTranslator.Plugin.Core
                // make sure we use the translated version of all texts
                foreach( var kvp in objects )
                {
-                  var ui = kvp.Key;
-                  try
+                  var tti = kvp.Value as TextTranslationInfo;
+                  if( tti != null )
                   {
-                     if( ( ui as Component )?.gameObject?.activeSelf ?? false )
+                     var ui = kvp.Key;
+                     try
                      {
-                        var info = (TranslationInfo)kvp.Value;
-                        info?.ChangeFont( ui );
+                        if( ( ui as Component )?.gameObject?.activeSelf ?? false )
+                        {
+                           tti?.ChangeFont( ui );
+                        }
+                     }
+                     catch( Exception )
+                     {
+                        // not super pretty, no...
+                        ObjectExtensions.Remove( ui );
                      }
-                  }
-                  catch( Exception )
-                  {
-                     // not super pretty, no...
-                     ObjectExtensions.Remove( ui );
                   }
                }
             }
@@ -1687,13 +2004,13 @@ namespace XUnity.AutoTranslator.Plugin.Core
                // make sure we use the original version of all texts
                foreach( var kvp in objects )
                {
+                  var tti = kvp.Value as TextTranslationInfo;
                   var ui = kvp.Key;
                   try
                   {
                      if( ( ui as Component )?.gameObject?.activeSelf ?? false )
                      {
-                        var info = (TranslationInfo)kvp.Value;
-                        info?.UnchangeFont( ui );
+                        tti?.UnchangeFont( ui );
                      }
                   }
                   catch( Exception )
@@ -1713,6 +2030,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
          Logger.Current.Info( $"Toggling translations of {objects.Count} objects." );
 
+         // FIXME: Translate TEXTURES first??? Problem if texture is not related to a component!
+
          if( _isInTranslatedMode )
          {
             // make sure we use the translated version of all texts
@@ -1723,11 +2042,15 @@ namespace XUnity.AutoTranslator.Plugin.Core
                {
                   if( ( ui as Component )?.gameObject?.activeSelf ?? false )
                   {
-                     var info = (TranslationInfo)kvp.Value;
+                     var tti = kvp.Value as TextTranslationInfo;
+                     if( tti != null && tti.IsTranslated )
+                     {
+                        SetText( ui, tti.TranslatedText, true, tti );
+                     }
 
-                     if( info != null && info.IsTranslated )
+                     if( Settings.EnableTextureTranslation && Settings.EnableTextureToggling )
                      {
-                        SetText( ui, info.TranslatedText, true, info );
+                        TranslateTexture( ui, null, false );
                      }
                   }
                }
@@ -1748,11 +2071,15 @@ namespace XUnity.AutoTranslator.Plugin.Core
                {
                   if( ( ui as Component )?.gameObject?.activeSelf ?? false )
                   {
-                     var info = (TranslationInfo)kvp.Value;
+                     var tti = kvp.Value as TextTranslationInfo;
+                     if( tti != null && tti.IsTranslated )
+                     {
+                        SetText( ui, tti.OriginalText, true, tti );
+                     }
 
-                     if( info != null && info.IsTranslated )
+                     if( Settings.EnableTextureTranslation && Settings.EnableTextureToggling )
                      {
-                        SetText( ui, info.OriginalText, true, info );
+                        TranslateTexture( ui, null, false );
                      }
                   }
                }
@@ -1864,10 +2191,18 @@ namespace XUnity.AutoTranslator.Plugin.Core
             var components = obj.GetComponents<Component>();
             foreach( var component in components )
             {
-               if( component.IsKnownType() )
+               if( component.IsKnownTextType() )
                {
                   Hook_TextChanged( component );
                }
+
+               if( Settings.EnableTextureTranslation )
+               {
+                  if( component.IsKnownImageType() )
+                  {
+                     Hook_ImageChangedOnComponent( component );
+                  }
+               }
             }
 
             if( obj.transform != null )

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

@@ -79,6 +79,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
       public static float? ResizeUILineSpacingScale;
       public static bool ForceUIResizing;
 
+      public static string TextureDirectory;
+      public static bool EnableTextureTranslation;
+      public static bool EnableTextureDumping;
+      public static bool EnableTextureToggling;
+      public static TextureHashGenerationStrategy TextureHashGenerationStrategy;
+
       public static bool CopyToClipboard;
       public static int MaxClipboardCopyCharacters;
 
@@ -117,7 +123,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
 
          Delay = Config.Current.Preferences[ "Behaviour" ][ "Delay" ].GetOrDefault( 0f );
          MaxCharactersPerTranslation = Config.Current.Preferences[ "Behaviour" ][ "MaxCharactersPerTranslation" ].GetOrDefault( 200 );
-         IgnoreWhitespaceInDialogue = Config.Current.Preferences[ "Behaviour" ][ "IgnoreWhitespaceInDialogue" ].GetOrDefault( Types.AdvEngine == null );
+         IgnoreWhitespaceInDialogue = Config.Current.Preferences[ "Behaviour" ][ "IgnoreWhitespaceInDialogue" ].GetOrDefault( ClrTypes.AdvEngine == null );
          IgnoreWhitespaceInNGUI = Config.Current.Preferences[ "Behaviour" ][ "IgnoreWhitespaceInNGUI" ].GetOrDefault( true );
          MinDialogueChars = Config.Current.Preferences[ "Behaviour" ][ "MinDialogueChars" ].GetOrDefault( 20 );
          ForceSplitTextAfterCharacters = Config.Current.Preferences[ "Behaviour" ][ "ForceSplitTextAfterCharacters" ].GetOrDefault( 0 );
@@ -125,12 +131,19 @@ namespace XUnity.AutoTranslator.Plugin.Core.Configuration
          MaxClipboardCopyCharacters = Config.Current.Preferences[ "Behaviour" ][ "MaxClipboardCopyCharacters" ].GetOrDefault( 450 );
          EnableUIResizing = Config.Current.Preferences[ "Behaviour" ][ "EnableUIResizing" ].GetOrDefault( true );
          EnableBatching = Config.Current.Preferences[ "Behaviour" ][ "EnableBatching" ].GetOrDefault( true );
-         TrimAllText = Config.Current.Preferences[ "Behaviour" ][ "TrimAllText" ].GetOrDefault( Types.AdvEngine == null );
+         TrimAllText = Config.Current.Preferences[ "Behaviour" ][ "TrimAllText" ].GetOrDefault( ClrTypes.AdvEngine == null );
          UseStaticTranslations = Config.Current.Preferences[ "Behaviour" ][ "UseStaticTranslations" ].GetOrDefault( true );
          OverrideFont = Config.Current.Preferences[ "Behaviour" ][ "OverrideFont" ].GetOrDefault( string.Empty );
          ResizeUILineSpacingScale = Config.Current.Preferences[ "Behaviour" ][ "ResizeUILineSpacingScale" ].GetOrDefault<float?>( null, true );
          ForceUIResizing = Config.Current.Preferences[ "Behaviour" ][ "ForceUIResizing" ].GetOrDefault( false );
 
+
+         TextureDirectory = Config.Current.Preferences[ "Texture" ][ "TextureDirectory" ].GetOrDefault( @"Translation\Texture" );
+         EnableTextureTranslation = Config.Current.Preferences[ "Texture" ][ "EnableTextureTranslation" ].GetOrDefault( false );
+         EnableTextureDumping = Config.Current.Preferences[ "Texture" ][ "EnableTextureDumping" ].GetOrDefault( false );
+         EnableTextureToggling = Config.Current.Preferences[ "Texture" ][ "EnableTextureToggling" ].GetOrDefault( false );
+         TextureHashGenerationStrategy = Config.Current.Preferences[ "Texture" ][ "TextureHashGenerationStrategy" ].GetOrDefault( TextureHashGenerationStrategy.FromImageNameThenData );
+
          // special handling because of enum parsing
          try
          {

+ 20 - 14
src/XUnity.AutoTranslator.Plugin.Core/Constants/Types.cs → src/XUnity.AutoTranslator.Plugin.Core/Constants/ClrTypes.cs

@@ -4,37 +4,43 @@ using System.Reflection;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Constants
 {
-   public static class Types
+   public static class ClrTypes
    {
-      public static readonly Type TextEditor = FindType( "UnityEngine.TextEditor" );
-      public static readonly Type CustomYieldInstruction = FindType( "UnityEngine.CustomYieldInstruction" );
-
+      // TextMeshPro
       public static readonly Type TMP_InputField = FindType( "TMPro.TMP_InputField" );
       public static readonly Type TMP_Text = FindType( "TMPro.TMP_Text" );
       public static readonly Type TextMeshProUGUI = FindType( "TMPro.TextMeshProUGUI" );
       public static readonly Type TextMeshPro = FindType( "TMPro.TextMeshPro" );
 
+      // NGUI
+      public static readonly Type UILabel = FindType( "UILabel" );
+      public static readonly Type UIWidget = FindType( "UIWidget" );
+      public static readonly Type UIAtlas = FindType( "UIAtlas" );
+      public static readonly Type UISprite = FindType( "UISprite" );
+      public static readonly Type UITexture = FindType( "UITexture" );
+      public static readonly Type UI2DSprite = FindType( "UI2DSprite" );
+      public static readonly Type UIFont = FindType( "UIFont" );
+      public static readonly Type UIPanel = FindType( "UIPanel" );
+      public static readonly Type UIRect = FindType( "UIRect" );
+
+      // Unity
+      public static readonly Type WWW = FindType( "UnityEngine.WWW" );
       public static readonly Type InputField = FindType( "UnityEngine.UI.InputField" );
       public static readonly Type Text = FindType( "UnityEngine.UI.Text" );
-
       public static readonly Type GUI = FindType( "UnityEngine.GUI" );
+      public static readonly Type ImageConversion = FindType( "UnityEngine.ImageConversion" );
 
-      public static readonly Type UILabel = FindType( "UILabel" );
-
-      public static readonly Type WWW = FindType( "UnityEngine.WWW" );
-
+      // Something...
       public static readonly Type Typewriter = FindType( "Typewriter" );
+      public static readonly Type TextEditor = FindType( "UnityEngine.TextEditor" );
+      public static readonly Type CustomYieldInstruction = FindType( "UnityEngine.CustomYieldInstruction" );
 
+      // Utage
       public static readonly Type UguiNovelText = FindType( "Utage.UguiNovelText" );
-
       public static readonly Type AdvCommand = FindType( "Utage.AdvCommand" );
-
       public static readonly Type AdvEngine = FindType( "Utage.AdvEngine" );
-
       public static readonly Type AdvDataManager = FindType( "Utage.AdvDataManager" );
-
       public static readonly Type AdvScenarioData = FindType( "Utage.AdvScenarioData" );
-
       public static readonly Type AdvScenarioLabelData = FindType( "Utage.AdvScenarioLabelData" );
 
       private static Type FindType( string name )

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

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using Harmony;
 using UnityEngine;
 using UnityEngine.UI;
 
@@ -10,6 +11,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
    public static class ComponentExtensions
    {
       private static readonly string TextPropertyName = "text";
+      private static readonly string TexturePropertyName = "texture";
+      private static readonly string MainTexturePropertyName = "mainTexture";
+      private static readonly string MarkAsChangedMethodName = "MarkAsChanged";
 
       public static string GetText( this object ui )
       {
@@ -55,6 +59,50 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
             type.GetProperty( TextPropertyName )?.GetSetMethod()?.Invoke( ui, new[] { text } );
          }
       }
+
+      public static string GetTextureName( this Texture texture )
+      {
+         if( !string.IsNullOrEmpty( texture.name ) ) return texture.name;
+         return "Unnamed";
+      }
+
+      public static Texture2D GetTexture( this object ui )
+      {
+         if( ui is Image image )
+         {
+            return image.mainTexture as Texture2D;
+         }
+         else if( ui is RawImage rawImage )
+         {
+            return rawImage.mainTexture as Texture2D;
+         }
+         else
+         {
+            // lets attempt some reflection for several known types
+            var type = ui.GetType();
+            var texture = type.GetProperty( MainTexturePropertyName )?.GetValue( ui, null )
+               ?? type.GetProperty( TexturePropertyName )?.GetValue( ui, null );
+
+            return texture as Texture2D;
+         }
+      }
+
+      public static void SetAllDirtyEx( this object ui )
+      {
+         if( ui == null ) return;
+
+         if( ui is Graphic graphic )
+         {
+            graphic.SetAllDirty();
+         }
+         else
+         {
+            // lets attempt some reflection for several known types
+            var type = ui.GetType();
+
+            AccessTools.Method( type, MarkAsChangedMethodName )?.Invoke( ui, null );
+         }
+      }
    }
 
    public static class UILabelExtensions

+ 89 - 17
src/XUnity.AutoTranslator.Plugin.Core/Extensions/ObjectExtensions.cs

@@ -7,6 +7,7 @@ using XUnity.AutoTranslator.Plugin.Core.Configuration;
 using UnityEngine.UI;
 using XUnity.AutoTranslator.Plugin.Core.Constants;
 using XUnity.AutoTranslator.Plugin.Core.Utilities;
+using UnityEngine;
 
 namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 {
@@ -17,17 +18,33 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
       private static readonly object Sync = new object();
       private static readonly WeakDictionary<object, object> DynamicFields = new WeakDictionary<object, object>();
 
-      public static bool IsKnownType( this object ui )
+      public static bool IsKnownTextType( this object ui )
       {
          if( ui == null ) return false;
 
          var type = ui.GetType();
 
-         return ui is Text
-            || ui is UnityEngine.GUIContent
-            || ( Types.UILabel != null && Types.UILabel.IsAssignableFrom( type ) )
-            || ( Types.TMP_Text != null && Types.TMP_Text.IsAssignableFrom( type ) )
-            || ( Types.AdvCommand != null && Types.AdvCommand.IsAssignableFrom( type ) );
+         return ( Settings.EnableUGUI && ui is Text )
+            || ( Settings.EnableIMGUI && ui is GUIContent )
+            || ( Settings.EnableNGUI && ClrTypes.UILabel != null && ClrTypes.UILabel.IsAssignableFrom( type ) )
+            || ( Settings.EnableTextMeshPro && ClrTypes.TMP_Text != null && ClrTypes.TMP_Text.IsAssignableFrom( type ) )
+            || ( Settings.EnableUtage && ClrTypes.AdvCommand != null && ClrTypes.AdvCommand.IsAssignableFrom( type ) );
+      }
+
+      public static bool IsKnownImageType( this object ui )
+      {
+         var type = ui.GetType();
+
+         // Simplify this??
+
+         return ( Settings.EnableUGUI && ( ui is Image || ui is RawImage ) )
+            || ( Settings.EnableNGUI
+               && ( ( ClrTypes.UIWidget != null && type != ClrTypes.UILabel && ClrTypes.UIWidget.IsAssignableFrom( type ) )
+               || ( ClrTypes.UIAtlas != null && ClrTypes.UIAtlas.IsAssignableFrom( type ) )
+               || ( ClrTypes.UITexture != null && ClrTypes.UITexture.IsAssignableFrom( type ) )
+               //|| ( ClrTypes.UIFont != null && ClrTypes.UIFont.IsAssignableFrom( type ) )
+               || ( ClrTypes.UIPanel != null && ClrTypes.UIPanel.IsAssignableFrom( type ) ) )
+            );
       }
 
       public static bool SupportsStabilization( this object ui )
@@ -37,8 +54,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          var type = ui.GetType();
 
          return ui is Text
-            || ( Types.UILabel != null && Types.UILabel.IsAssignableFrom( type ) )
-            || ( Types.TMP_Text != null && Types.TMP_Text.IsAssignableFrom( type ) );
+            || ( ClrTypes.UILabel != null && ClrTypes.UILabel.IsAssignableFrom( type ) )
+            || ( ClrTypes.TMP_Text != null && ClrTypes.TMP_Text.IsAssignableFrom( type ) );
       }
 
       public static bool SupportsRichText( this object ui )
@@ -48,9 +65,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          var type = ui.GetType();
 
          return ( ui as Text )?.supportRichText == true
-            || ( Types.TMP_Text != null && Types.TMP_Text.IsAssignableFrom( type ) && Equals( type.GetProperty( RichTextPropertyName )?.GetValue( ui, null ), true ) )
-            || ( Types.AdvCommand != null && Types.AdvCommand.IsAssignableFrom( type ) )
-            || ( Types.UguiNovelText != null && Types.UguiNovelText.IsAssignableFrom( type ) );
+            || ( ClrTypes.TMP_Text != null && ClrTypes.TMP_Text.IsAssignableFrom( type ) && Equals( type.GetProperty( RichTextPropertyName )?.GetValue( ui, null ), true ) )
+            || ( ClrTypes.AdvCommand != null && ClrTypes.AdvCommand.IsAssignableFrom( type ) )
+            || ( ClrTypes.UguiNovelText != null && ClrTypes.UguiNovelText.IsAssignableFrom( type ) );
       }
 
       public static bool IsSpammingComponent( this object ui )
@@ -66,7 +83,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 
          var type = ui.GetType();
 
-         return Types.AdvCommand != null && Types.AdvCommand.IsAssignableFrom( type );
+         return ClrTypes.AdvCommand != null && ClrTypes.AdvCommand.IsAssignableFrom( type );
       }
 
       public static bool IsNGUI( this object ui )
@@ -75,28 +92,65 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 
          var type = ui.GetType();
 
-         return Types.UILabel != null && Types.UILabel.IsAssignableFrom( type );
+         return ClrTypes.UILabel != null && ClrTypes.UILabel.IsAssignableFrom( type );
       }
 
-      public static TranslationInfo GetTranslationInfo( this object obj )
+      public static TextTranslationInfo GetTextTranslationInfo( this object obj )
       {
          if( !Settings.EnableObjectTracking ) return null;
 
          if( !obj.SupportsStabilization() ) return null;
 
-         var info = obj.Get<TranslationInfo>();
+         var info = obj.Get<TextTranslationInfo>();
 
          return info;
       }
 
+      public static ImageTranslationInfo GetImageTranslationInfo( this object obj )
+      {
+         return obj.Get<ImageTranslationInfo>();
+      }
+
+      public static TextureTranslationInfo GetTextureTranslationInfo( this Texture texture )
+      {
+         return texture.Get<TextureTranslationInfo>();
+      }
+
       public static T Get<T>( this object obj )
          where T : new()
       {
+         if( obj == null ) return default( T );
+
          lock( Sync )
          {
             if( DynamicFields.TryGetValue( obj, out object value ) )
             {
-               return (T)value;
+               if( value is Dictionary<Type, object> existingDictionary )
+               {
+                  if( existingDictionary.TryGetValue( typeof( T ), out value ) )
+                  {
+                     return (T)value;
+                  }
+                  else
+                  {
+                     var t = new T();
+                     existingDictionary[ typeof( T ) ] = t;
+                     return t;
+                  }
+               }
+               if( !( value is T ) )
+               {
+                  var newDictionary = new Dictionary<Type, object>();
+                  newDictionary.Add( value.GetType(), value );
+                  var t = new T();
+                  newDictionary[ typeof( T ) ] = t;
+                  DynamicFields[ obj ] = newDictionary;
+                  return t;
+               }
+               else
+               {
+                  return (T)value;
+               }
             }
             else
             {
@@ -119,7 +173,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
       {
          lock( Sync )
          {
-            return DynamicFields.ToList();
+            return IterateAllPairs().ToList();
          }
       }
 
@@ -130,5 +184,23 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
             DynamicFields.Remove( obj );
          }
       }
+
+      private static IEnumerable<KeyValuePair<object, object>> IterateAllPairs()
+      {
+         foreach( var kvp in DynamicFields )
+         {
+            if( kvp.Value is Dictionary<Type, object> dictionary )
+            {
+               foreach( var kvp2 in dictionary )
+               {
+                  yield return new KeyValuePair<object, object>( kvp.Key, kvp2.Value );
+               }
+            }
+            else
+            {
+               yield return kvp;
+            }
+         }
+      }
    }
 }

+ 17 - 0
src/XUnity.AutoTranslator.Plugin.Core/Extensions/StringExtensions.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Globalization;
+using System.IO;
 using System.Linq;
 using System.Text;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
@@ -57,10 +58,26 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
          '9',
          '.'
       };
+      private static readonly HashSet<char> InvalidFileNameChars = new HashSet<char>( Path.GetInvalidFileNameChars() );
 
       private static readonly char[] NewlinesCharacters = new char[] { '\r', '\n' };
       private static readonly char[] WhitespacesAndNewlines = new char[] { '\r', '\n', ' ', ' ' };
 
+
+
+      public static string SanitizeForFileSystem( this string path )
+      {
+         var builder = new StringBuilder( path.Length );
+         foreach( var c in path )
+         {
+            if( !InvalidFileNameChars.Contains( c ) )
+            {
+               builder.Append( c );
+            }
+         }
+         return builder.ToString();
+      }
+
       public static TemplatedString TemplatizeByNumbers( this string str )
       {
          var dict = new Dictionary<string, string>();

+ 56 - 0
src/XUnity.AutoTranslator.Plugin.Core/Extensions/TextureExtensions.cs

@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Harmony;
+using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Constants;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Extensions
+{
+   public static class TextureExtensions
+   {
+      private static readonly MethodInfo LoadImage = AccessTools.Method( ClrTypes.ImageConversion, "LoadImage", new[] { typeof( Texture2D ), typeof( byte[] ), typeof( bool ) } );
+      private static readonly MethodInfo EncodeToPNG = AccessTools.Method( ClrTypes.ImageConversion, "EncodeToPNG", new[] { typeof( Texture2D ) } );
+
+      public static bool IsNonReadable( this Texture2D texture )
+      {
+         return texture.GetRawTextureData().Length == 0;
+      }
+
+      public static void LoadImageEx( this Texture2D texture, byte[] data, bool markNonReadable )
+      {
+         if( LoadImage != null )
+         {
+            LoadImage.Invoke( null, new object[] { texture, data, markNonReadable } );
+         }
+         else
+         {
+            texture.LoadImageSafe( data, markNonReadable );
+         }
+      }
+
+      private static void LoadImageSafe( this Texture2D texture, byte[] data, bool markNonReadable )
+      {
+         texture.LoadImage( data, markNonReadable );
+      }
+
+      public static byte[] EncodeToPNGEx( this Texture2D texture )
+      {
+         if( EncodeToPNG != null )
+         {
+            return (byte[])EncodeToPNG.Invoke( null, new object[] { texture } );
+         }
+         else
+         {
+            return texture.EncodeToPNGSafe();
+         }
+      }
+
+      private static byte[] EncodeToPNGSafe( this Texture2D texture )
+      {
+         return texture.EncodeToPNG();
+      }
+   }
+}

+ 2 - 2
src/XUnity.AutoTranslator.Plugin.Core/Features.cs

@@ -16,7 +16,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          try
          {
-            SupportsClipboard = Types.TextEditor?.GetProperty( "text" )?.GetSetMethod() != null;
+            SupportsClipboard = ClrTypes.TextEditor?.GetProperty( "text" )?.GetSetMethod() != null;
          }
          catch( Exception )
          {
@@ -25,7 +25,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
          try
          {
-            SupportsCustomYieldInstruction = Types.CustomYieldInstruction != null;
+            SupportsCustomYieldInstruction = ClrTypes.CustomYieldInstruction != null;
          }
          catch( Exception )
          {

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

@@ -23,13 +23,13 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
       {
          var harmony = HarmonyInstance.Create( "gravydevsupreme.xunity.autotranslator" );
 
-         bool success = false;
          try
          {
             if( Settings.EnableUGUI || Settings.EnableUtage )
             {
                UGUIHooks.HooksOverriden = SetupHook( KnownEvents.OnUnableToTranslateUGUI, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
                harmony.PatchAll( UGUIHooks.All );
+               harmony.PatchAll( UGUIImageHooks.All );
             }
          }
          catch( Exception e )
@@ -41,11 +41,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          {
             if( Settings.EnableTextMeshPro )
             {
-               success = SetupHook( KnownEvents.OnUnableToTranslateTextMeshPro, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
-               if( !success )
-               {
-                  harmony.PatchAll( TextMeshProHooks.All );
-               }
+               TextMeshProHooks.HooksOverriden = SetupHook( KnownEvents.OnUnableToTranslateTextMeshPro, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
+               harmony.PatchAll( TextMeshProHooks.All );
             }
          }
          catch( Exception e )
@@ -57,11 +54,9 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          {
             if( Settings.EnableNGUI )
             {
-               success = SetupHook( KnownEvents.OnUnableToTranslateNGUI, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
-               if( !success )
-               {
-                  harmony.PatchAll( NGUIHooks.All );
-               }
+               NGUIHooks.HooksOverriden = SetupHook( KnownEvents.OnUnableToTranslateNGUI, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
+               harmony.PatchAll( NGUIHooks.All );
+               harmony.PatchAll( NGUIImageHooks.All );
             }
          }
          catch( Exception e )
@@ -73,7 +68,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          {
             if( Settings.EnableIMGUI )
             {
-               success = SetupHook( KnownEvents.OnUnableToTranslateNGUI, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
+               var success = SetupHook( KnownEvents.OnUnableToTranslateNGUI, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
                if( !success )
                {
                   harmony.PatchAll( IMGUIHooks.All );
@@ -103,6 +98,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          {
             Logger.Current.Error( e, "An error occurred while setting up hooks for Utage." );
          }
+
       }
 
       public static bool SetupHook( string eventName, Func<object, string, string> callback )

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

@@ -29,12 +29,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.GUI != null;
+         return Constants.ClrTypes.GUI != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.GUI, "BeginGroup", new[] { typeof( Rect ), typeof( GUIContent ), typeof( GUIStyle ) } );
+         return AccessTools.Method( Constants.ClrTypes.GUI, "BeginGroup", new[] { typeof( Rect ), typeof( GUIContent ), typeof( GUIStyle ) } );
       }
 
       static void Prefix( GUIContent content )
@@ -48,12 +48,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.GUI != null;
+         return Constants.ClrTypes.GUI != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.GUI, "Box", new[] { typeof( Rect ), typeof( GUIContent ), typeof( GUIStyle ) } );
+         return AccessTools.Method( Constants.ClrTypes.GUI, "Box", new[] { typeof( Rect ), typeof( GUIContent ), typeof( GUIStyle ) } );
       }
 
       static void Prefix( GUIContent content )
@@ -68,12 +68,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.GUI != null;
+         return Constants.ClrTypes.GUI != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.GUI, "DoRepeatButton", new[] { typeof( Rect ), typeof( GUIContent ), typeof( GUIStyle ), typeof( FocusType ) } );
+         return AccessTools.Method( Constants.ClrTypes.GUI, "DoRepeatButton", new[] { typeof( Rect ), typeof( GUIContent ), typeof( GUIStyle ), typeof( FocusType ) } );
       }
 
       static void Prefix( GUIContent content )
@@ -87,12 +87,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.GUI != null;
+         return Constants.ClrTypes.GUI != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.GUI, "DoLabel", new[] { typeof( Rect ), typeof( GUIContent ), typeof( IntPtr ) } );
+         return AccessTools.Method( Constants.ClrTypes.GUI, "DoLabel", new[] { typeof( Rect ), typeof( GUIContent ), typeof( IntPtr ) } );
       }
 
       static void Prefix( GUIContent content )
@@ -106,12 +106,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.GUI != null;
+         return Constants.ClrTypes.GUI != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.GUI, "DoButton", new[] { typeof( Rect ), typeof( GUIContent ), typeof( IntPtr ) } );
+         return AccessTools.Method( Constants.ClrTypes.GUI, "DoButton", new[] { typeof( Rect ), typeof( GUIContent ), typeof( IntPtr ) } );
       }
 
       static void Prefix( GUIContent content )
@@ -125,12 +125,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.GUI != null;
+         return Constants.ClrTypes.GUI != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.GUI, "DoModalWindow", new[] { typeof( int ), typeof( Rect ), typeof( WindowFunction ), typeof( GUIContent ), typeof( GUIStyle ), typeof( GUISkin ) } );
+         return AccessTools.Method( Constants.ClrTypes.GUI, "DoModalWindow", new[] { typeof( int ), typeof( Rect ), typeof( WindowFunction ), typeof( GUIContent ), typeof( GUIStyle ), typeof( GUISkin ) } );
       }
 
       static void Prefix( GUIContent content )
@@ -144,12 +144,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.GUI != null;
+         return Constants.ClrTypes.GUI != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.GUI, "DoWindow", new[] { typeof( int ), typeof( Rect ), typeof( WindowFunction ), typeof( GUIContent ), typeof( GUIStyle ), typeof( GUISkin ), typeof( bool ) } );
+         return AccessTools.Method( Constants.ClrTypes.GUI, "DoWindow", new[] { typeof( int ), typeof( Rect ), typeof( WindowFunction ), typeof( GUIContent ), typeof( GUIStyle ), typeof( GUISkin ), typeof( bool ) } );
       }
 
       static void Prefix( GUIContent title )
@@ -163,12 +163,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.GUI != null;
+         return Constants.ClrTypes.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 ) } );
+         return AccessTools.Method( Constants.ClrTypes.GUI, "DoButtonGrid", new[] { typeof( Rect ), typeof( int ), typeof( GUIContent[] ), typeof( int ), typeof( GUIStyle ), typeof( GUIStyle ), typeof( GUIStyle ), typeof( GUIStyle ) } );
       }
 
       static void Prefix( GUIContent[] contents )
@@ -185,12 +185,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.GUI != null;
+         return Constants.ClrTypes.GUI != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.GUI, "DoTextField", new[] { typeof( Rect ), typeof( int ), typeof( GUIContent ), typeof( bool ), typeof( int ), typeof( GUIStyle ), typeof( string ) } );
+         return AccessTools.Method( Constants.ClrTypes.GUI, "DoTextField", new[] { typeof( Rect ), typeof( int ), typeof( GUIContent ), typeof( bool ), typeof( int ), typeof( GUIStyle ), typeof( string ) } );
       }
 
       static void Prefix( GUIContent content )
@@ -204,12 +204,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.GUI != null;
+         return Constants.ClrTypes.GUI != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.GUI, "DoToggle", new[] { typeof( Rect ), typeof( int ), typeof( bool ), typeof( GUIContent ), typeof( IntPtr ) } );
+         return AccessTools.Method( Constants.ClrTypes.GUI, "DoToggle", new[] { typeof( Rect ), typeof( int ), typeof( bool ), typeof( GUIContent ), typeof( IntPtr ) } );
       }
 
       static void Prefix( GUIContent content )

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

@@ -25,12 +25,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.UILabel != null;
+         return Constants.ClrTypes.UILabel != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( Constants.Types.UILabel, "text" ).GetSetMethod();
+         return AccessTools.Property( Constants.ClrTypes.UILabel, "text" ).GetSetMethod();
       }
 
       public static void Postfix( object __instance )
@@ -48,12 +48,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.NGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.UILabel != null;
+         return Constants.ClrTypes.UILabel != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.UILabel, "OnEnable" );
+         return AccessTools.Method( Constants.ClrTypes.UILabel, "OnEnable" );
       }
 
       public static void Postfix( object __instance )

+ 298 - 0
src/XUnity.AutoTranslator.Plugin.Core/Hooks/NGUIImageHooks.cs

@@ -0,0 +1,298 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Harmony;
+using XUnity.AutoTranslator.Plugin.Core.Constants;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Hooks
+{
+   public static class NGUIImageHooks
+   {
+      public static readonly Type[] All = new Type[] {
+         typeof( UIAtlas_texture_Hook ),
+         typeof( UISprite_OnInit_Hook ),
+         typeof( UISprite_material_Hook ),
+         typeof( UISprite_atlas_Hook ),
+         typeof( UI2DSprite_sprite2D_Hook ),
+         typeof( UI2DSprite_material_Hook ),
+         typeof( UITexture_mainTexture_Hook ),
+         typeof( UITexture_material_Hook ),
+         typeof( UIPanel_clipTexture_Hook ),
+         typeof( UIRect_OnInit_Hook ),
+         //typeof( UIFont_dynamicFont_Hook ),
+         //typeof( UIFont_material_Hook ),
+         //typeof( UILabel_bitmapFont_Hook ),
+         //typeof( UILabel_trueTypeFont_Hook ),
+      };
+   }
+
+
+   [Harmony]
+   public static class UIAtlas_texture_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UIAtlas != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UIAtlas, "spriteMaterial" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+   
+   [Harmony]
+   public static class UISprite_OnInit_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UISprite != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Method( Constants.ClrTypes.UISprite, "OnInit" );
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class UISprite_material_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UISprite != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UISprite, "material" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class UISprite_atlas_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UISprite != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UISprite, "atlas" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+   
+   [Harmony]
+   public static class UITexture_mainTexture_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UITexture != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UITexture, "mainTexture" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class UITexture_material_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UITexture != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UITexture, "material" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class UIRect_OnInit_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UIRect != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Method( ClrTypes.UIRect, "OnInit" );
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class UI2DSprite_sprite2D_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UI2DSprite != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UI2DSprite, "sprite2D" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class UI2DSprite_material_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UI2DSprite != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UI2DSprite, "material" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+
+   [Harmony]
+   public static class UIFont_material_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UIFont != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UIFont, "material" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class UIFont_dynamicFont_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UIFont != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UIFont, "dynamicFont" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class UIPanel_clipTexture_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UIPanel != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UIPanel, "clipTexture" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class UILabel_bitmapFont_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UILabel != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UILabel, "bitmapFont" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class UILabel_trueTypeFont_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return ClrTypes.UILabel != null;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( ClrTypes.UILabel, "trueTypeFont" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+}

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

@@ -30,12 +30,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Types.TextMeshProUGUI != null;
+         return ClrTypes.TextMeshProUGUI != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Types.TextMeshProUGUI, "OnEnable" );
+         return AccessTools.Method( ClrTypes.TextMeshProUGUI, "OnEnable" );
       }
 
       static void Postfix( object __instance )
@@ -53,12 +53,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Types.TextMeshPro != null;
+         return ClrTypes.TextMeshPro != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Types.TextMeshPro, "OnEnable" );
+         return AccessTools.Method( ClrTypes.TextMeshPro, "OnEnable" );
       }
 
       static void Postfix( object __instance )
@@ -76,12 +76,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Types.TMP_Text != null;
+         return ClrTypes.TMP_Text != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Property( Types.TMP_Text, "text" ).GetSetMethod();
+         return AccessTools.Property( ClrTypes.TMP_Text, "text" ).GetSetMethod();
       }
 
       static void Postfix( object __instance )
@@ -99,12 +99,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Types.TMP_Text != null;
+         return ClrTypes.TMP_Text != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Types.TMP_Text, "SetText", new[] { typeof( StringBuilder ) } );
+         return AccessTools.Method( ClrTypes.TMP_Text, "SetText", new[] { typeof( StringBuilder ) } );
       }
 
       static void Postfix( object __instance )
@@ -122,12 +122,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Types.TMP_Text != null;
+         return ClrTypes.TMP_Text != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Types.TMP_Text, "SetText", new[] { typeof( string ), typeof( bool ) } );
+         return AccessTools.Method( ClrTypes.TMP_Text, "SetText", new[] { typeof( string ), typeof( bool ) } );
       }
 
       static void Postfix( object __instance )
@@ -145,12 +145,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Types.TMP_Text != null;
+         return ClrTypes.TMP_Text != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Types.TMP_Text, "SetText", new[] { typeof( string ), typeof( float ), typeof( float ), typeof( float ) } );
+         return AccessTools.Method( ClrTypes.TMP_Text, "SetText", new[] { typeof( string ), typeof( float ), typeof( float ), typeof( float ) } );
       }
 
       static void Postfix( object __instance )
@@ -168,12 +168,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Types.TMP_Text != null;
+         return ClrTypes.TMP_Text != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Types.TMP_Text, "SetCharArray", new[] { typeof( char[] ) } );
+         return AccessTools.Method( ClrTypes.TMP_Text, "SetCharArray", new[] { typeof( char[] ) } );
       }
 
       static void Postfix( object __instance )
@@ -191,12 +191,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Types.TMP_Text != null;
+         return ClrTypes.TMP_Text != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Types.TMP_Text, "SetCharArray", new[] { typeof( char[] ), typeof( int ), typeof( int ) } );
+         return AccessTools.Method( ClrTypes.TMP_Text, "SetCharArray", new[] { typeof( char[] ), typeof( int ), typeof( int ) } );
       }
 
       static void Postfix( object __instance )
@@ -214,12 +214,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.TextMeshPro
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Types.TMP_Text != null;
+         return ClrTypes.TMP_Text != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Types.TMP_Text, "SetCharArray", new[] { typeof( int[] ), typeof( int ), typeof( int ) } );
+         return AccessTools.Method( ClrTypes.TMP_Text, "SetCharArray", new[] { typeof( int[] ), typeof( int ), typeof( int ) } );
       }
 
       static void Postfix( object __instance )

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

@@ -23,12 +23,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Types.Text != null;
+         return ClrTypes.Text != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         var text = AccessTools.Property( Types.Text, "text" );
+         var text = AccessTools.Property( ClrTypes.Text, "text" );
          return text.GetSetMethod();
       }
 
@@ -47,12 +47,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks.UGUI
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Types.Text != null;
+         return ClrTypes.Text != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         var OnEnable = AccessTools.Method( Types.Text, "OnEnable" );
+         var OnEnable = AccessTools.Method( ClrTypes.Text, "OnEnable" );
          return OnEnable;
       }
 

+ 120 - 0
src/XUnity.AutoTranslator.Plugin.Core/Hooks/UGUIImageHooks.cs

@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Harmony;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Hooks
+{
+   public static class UGUIImageHooks
+   {
+      public static readonly Type[] All = new[] {
+         typeof( MaskableGraphic_OnEnable_Hook ),
+         typeof( Image_sprite_Hook ),
+         typeof( Image_overrideSprite_Hook ),
+         typeof( RawImage_texture_Hook ),
+         typeof( Cursor_SetCursor_Hook )
+      };
+   }
+
+   [Harmony]
+   public static class MaskableGraphic_OnEnable_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return true;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Method( typeof( MaskableGraphic ), "OnEnable" );
+      }
+
+      public static void Postfix( object __instance )
+      {
+         if( __instance is Image || __instance is RawImage )
+         {
+            AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+         }
+      }
+   }
+
+   [Harmony]
+   public static class Image_sprite_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return true;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( typeof( Image ), "sprite" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class Image_overrideSprite_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return true;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( typeof( Image ), "overrideSprite" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class RawImage_texture_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return true;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( typeof( RawImage ), "texture" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance );
+      }
+   }
+
+   [Harmony]
+   public static class Cursor_SetCursor_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return true;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Method( typeof( Cursor ), "SetCursor", new[] { typeof( Texture2D ), typeof( Vector2 ), typeof( CursorMode ) } );
+      }
+
+      public static void Postfix( Texture2D texture )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChanged( texture );
+      }
+   }
+}

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

@@ -21,12 +21,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.AdvCommand != null;
+         return Constants.ClrTypes.AdvCommand != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.AdvCommand, "ParseCellLocalizedText", new Type[] { } );
+         return AccessTools.Method( Constants.ClrTypes.AdvCommand, "ParseCellLocalizedText", new Type[] { } );
       }
 
       static void Postfix( object __instance, ref string __result )
@@ -44,12 +44,12 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
    {
       static bool Prepare( HarmonyInstance instance )
       {
-         return Constants.Types.AdvEngine != null;
+         return Constants.ClrTypes.AdvEngine != null;
       }
 
       static MethodBase TargetMethod( HarmonyInstance instance )
       {
-         return AccessTools.Method( Constants.Types.AdvEngine, "JumpScenario", new Type[] { typeof( string ) } );
+         return AccessTools.Method( Constants.ClrTypes.AdvEngine, "JumpScenario", new Type[] { typeof( string ) } );
       }
 
       static void Prefix( ref string label )

+ 7 - 0
src/XUnity.AutoTranslator.Plugin.Core/ImageTranslationInfo.cs

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

+ 8 - 7
src/XUnity.AutoTranslator.Plugin.Core/TranslationInfo.cs → src/XUnity.AutoTranslator.Plugin.Core/TextTranslationInfo.cs

@@ -11,7 +11,8 @@ using XUnity.AutoTranslator.Plugin.Core.Fonts;
 
 namespace XUnity.AutoTranslator.Plugin.Core
 {
-   public class TranslationInfo
+
+   public class TextTranslationInfo
    {
       private static readonly string MultiLinePropertyName = "multiLine";
       private static readonly string OverflowMethodPropertyName = "overflowMethod";
@@ -23,7 +24,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       private MonoBehaviour _typewriter;
       private object _alteredSpacing;
 
-      public TranslationInfo()
+      public TextTranslationInfo()
       {
       }
 
@@ -41,19 +42,19 @@ namespace XUnity.AutoTranslator.Plugin.Core
          {
             _hasCheckedTypeWriter = true;
 
-            if( Constants.Types.Typewriter != null )
+            if( Constants.ClrTypes.Typewriter != null )
             {
                var ui = graphic as Component;
                if( ui != null )
                {
-                  _typewriter = (MonoBehaviour)ui.GetComponent( Constants.Types.Typewriter );
+                  _typewriter = (MonoBehaviour)ui.GetComponent( Constants.ClrTypes.Typewriter );
                }
             }
          }
 
          if( _typewriter != null )
          {
-            AccessTools.Method( Constants.Types.Typewriter, "OnEnable" )?.Invoke( _typewriter, null );
+            AccessTools.Method( Constants.ClrTypes.Typewriter, "OnEnable" )?.Invoke( _typewriter, null );
          }
       }
 
@@ -139,7 +140,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             var type = graphic.GetType();
 
             // special handling for NGUI to better handle textbox sizing
-            if( type == Constants.Types.UILabel )
+            if( type == Constants.ClrTypes.UILabel )
             {
                var originalMultiLine = type.GetProperty( MultiLinePropertyName )?.GetGetMethod()?.Invoke( graphic, null );
                var originalOverflowMethod = type.GetProperty( OverflowMethodPropertyName )?.GetGetMethod()?.Invoke( graphic, null );
@@ -179,7 +180,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          _unresize = null;
       }
 
-      public TranslationInfo Reset( string newText )
+      public TextTranslationInfo Reset( string newText )
       {
          IsTranslated = false;
          TranslatedText = null;

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

@@ -0,0 +1,9 @@
+namespace XUnity.AutoTranslator.Plugin.Core
+{
+   public enum TextureHashGenerationStrategy
+   {
+      FromImageName,
+      FromImageData,
+      FromImageNameThenData
+   }
+}

+ 105 - 0
src/XUnity.AutoTranslator.Plugin.Core/TextureTranslationInfo.cs

@@ -0,0 +1,105 @@
+using System.Text;
+using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Configuration;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
+using XUnity.AutoTranslator.Plugin.Core.Utilities;
+
+namespace XUnity.AutoTranslator.Plugin.Core
+{
+   public class TextureTranslationInfo
+   {
+      private string _key;
+      private byte[] _originalData;
+      private bool? _nonReadable;
+
+      public bool IsTranslated { get; set; }
+
+      public bool HasDumpedAlternativeTexture { get; set; }
+
+      public bool IsNonReadable( Texture2D texture )
+      {
+         if( !_nonReadable.HasValue )
+         {
+            _nonReadable = texture.IsNonReadable();
+         }
+
+         return _nonReadable.Value;
+      }
+
+      public string GetKey( Texture2D texture )
+      {
+         SetupHashAndData( texture );
+         return _key;
+      }
+
+      public byte[] GetOriginalData( Texture2D texture )
+      {
+         SetupHashAndData( texture );
+         return _originalData;
+      }
+
+      public byte[] GetOrCreateOriginalData( Texture2D texture )
+      {
+         // special handling if SetupHashAndData is changed to not support originalData
+         // which frankly, is a memory drain
+
+         SetupHashAndData( texture );
+         if( _originalData != null ) return _originalData;
+         return TextureHelper.GetData( texture ).Data;
+      }
+
+      private void SetupHashAndData( Texture2D texture )
+      {
+         if( _key == null )
+         {
+            if( Settings.TextureHashGenerationStrategy == TextureHashGenerationStrategy.FromImageData )
+            {
+               var result = TextureHelper.GetData( texture );
+
+               _originalData = result.Data;
+               _nonReadable = result.NonReadable;
+               _key = HashHelper.Compute( _originalData ).Substring( 0, 10 );
+            }
+            else if( Settings.TextureHashGenerationStrategy == TextureHashGenerationStrategy.FromImageName )
+            {
+               var name = texture.name; // name may be duplicate, WILL be duplicate!
+               if( string.IsNullOrEmpty( name ) || name.Contains( "(Clone)" ) ) return;
+
+               _key = HashHelper.Compute( Encoding.UTF8.GetBytes( name ) ).Substring( 0, 10 );
+
+               if( Settings.EnableTextureToggling )
+               {
+                  var result = TextureHelper.GetData( texture );
+
+                  _originalData = result.Data;
+                  _nonReadable = result.NonReadable;
+               }
+            }
+            else // if( Settings.TextureHashGenerationStrategy == TextureHashGenerationStrategy.FromImageNameThenData )
+            {
+               var name = texture.name;
+               if( string.IsNullOrEmpty( name ) )
+               {
+                  var result = TextureHelper.GetData( texture );
+
+                  _originalData = result.Data;
+                  _nonReadable = result.NonReadable;
+                  _key = HashHelper.Compute( _originalData ).Substring( 0, 10 );
+               }
+               else
+               {
+                  _key = HashHelper.Compute( Encoding.UTF8.GetBytes( name ) ).Substring( 0, 10 );
+
+                  if( Settings.EnableTextureToggling )
+                  {
+                     var result = TextureHelper.GetData( texture );
+
+                     _originalData = result.Data;
+                     _nonReadable = result.NonReadable;
+                  }
+               }
+            }
+         }
+      }
+   }
+}

+ 4 - 4
src/XUnity.AutoTranslator.Plugin.Core/UtageSupport/UtageHelpers.cs

@@ -19,13 +19,13 @@ namespace XUnity.AutoTranslator.Plugin.Core.UtageSupport
          {
             try
             {
-               AdvManager = GameObject.FindObjectOfType( Constants.Types.AdvDataManager );
-               var ScenarioDataTblProperty = Constants.Types.AdvDataManager.GetProperty( "ScenarioDataTbl" );
+               AdvManager = GameObject.FindObjectOfType( Constants.ClrTypes.AdvDataManager );
+               var ScenarioDataTblProperty = Constants.ClrTypes.AdvDataManager.GetProperty( "ScenarioDataTbl" );
                var ScenarioDataTbl = ScenarioDataTblProperty.GetValue( AdvManager, empty );
                foreach( object labelToAdvScenarioDataKeyValuePair in (IEnumerable)ScenarioDataTbl )
                {
                   var labelToAdvScenarioDataKeyValuePairType = typeof( KeyValuePair<,> )
-                     .MakeGenericType( new Type[] { typeof( string ), Constants.Types.AdvScenarioData } );
+                     .MakeGenericType( new Type[] { typeof( string ), Constants.ClrTypes.AdvScenarioData } );
 
                   var AdvScenarioDataKey = (string)labelToAdvScenarioDataKeyValuePairType.GetProperty( "Key" )
                      .GetValue( labelToAdvScenarioDataKeyValuePair, empty );
@@ -44,7 +44,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.UtageSupport
                      foreach( object labelToAdvScenarioLabelDataKeyValuePair in (IEnumerable)labelToAdvScenarioLabelData )
                      {
                         var labelToAdvScenarioLabelDataKeyValuePairType = typeof( KeyValuePair<,> )
-                           .MakeGenericType( new Type[] { typeof( string ), Constants.Types.AdvScenarioLabelData } );
+                           .MakeGenericType( new Type[] { typeof( string ), Constants.ClrTypes.AdvScenarioLabelData } );
 
                         var AdvScenarioLabelDataKey = (string)labelToAdvScenarioLabelDataKeyValuePairType.GetProperty( "Key" )
                            .GetValue( labelToAdvScenarioLabelDataKeyValuePair, empty );

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

@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Utilities
+{
+   public static class HashHelper
+   {
+      private static readonly SHA1Managed SHA1 = new SHA1Managed();
+      private static readonly uint[] Lookup32 = CreateLookup32();
+
+
+      public static string Compute( byte[] data )
+      {
+         var hash = SHA1.ComputeHash( data );
+         var base64 = ByteArrayToHexViaLookup32( hash );
+         return base64;
+      }
+
+      private static uint[] CreateLookup32()
+      {
+         var result = new uint[ 256 ];
+         for( int i = 0 ; i < 256 ; i++ )
+         {
+            string s = i.ToString( "X2" );
+            result[ i ] = ( (uint)s[ 0 ] ) + ( (uint)s[ 1 ] << 16 );
+         }
+         return result;
+      }
+
+      private static string ByteArrayToHexViaLookup32( byte[] bytes )
+      {
+         var lookup32 = Lookup32;
+         var result = new char[ bytes.Length * 2 ];
+         for( int i = 0 ; i < bytes.Length ; i++ )
+         {
+            var val = lookup32[ bytes[ i ] ];
+            result[ 2 * i ] = (char)val;
+            result[ 2 * i + 1 ] = (char)( val >> 16 );
+         }
+         return new string( result );
+      }
+   }
+}

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

@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+using XUnity.AutoTranslator.Plugin.Core.Extensions;
+
+namespace XUnity.AutoTranslator.Plugin.Core.Utilities
+{
+   public static class TextureHelper
+   {
+      private static readonly Color Transparent = new Color( 0, 0, 0, 0 );
+      
+      public static TextureDataResult GetData( Texture2D texture, RenderTextureFormat rtf = RenderTextureFormat.Default, RenderTextureReadWrite cs = RenderTextureReadWrite.Default )
+      {
+         byte[] data = null;
+         bool nonReadable = texture.IsNonReadable();
+
+         if( !nonReadable )
+         {
+            data = texture.EncodeToPNGEx();
+         }
+
+         if( data == null )
+         {
+            // https://support.unity3d.com/hc/en-us/articles/206486626-How-can-I-get-pixels-from-unreadable-textures-
+            nonReadable = true;
+
+            var tmp = RenderTexture.GetTemporary( texture.width, texture.height, 0, rtf, cs );
+            Graphics.Blit( texture, tmp );
+            var previousRenderTexture = RenderTexture.active;
+            RenderTexture.active = tmp;
+
+            var texture2d = new Texture2D( texture.width, texture.height );
+            texture2d.ReadPixels( new Rect( 0, 0, tmp.width, tmp.height ), 0, 0 );
+            data = texture2d.EncodeToPNGEx();
+            UnityEngine.Object.DestroyImmediate( texture2d );
+
+            //GL.Clear( false, true, Transparent );
+            //Graphics.Blit( tex, tmp );
+            //var texture2d = GetTextureFromRenderTexture( tmp );
+            //var data = texture2d.EncodeToPNG();
+            //UnityEngine.Object.DestroyImmediate( texture2d );
+
+            RenderTexture.active = previousRenderTexture;
+            RenderTexture.ReleaseTemporary( tmp );
+         }
+
+         return new TextureDataResult( data, nonReadable );
+      }
+   }
+
+   public struct TextureDataResult
+   {
+      public TextureDataResult( byte[] data, bool nonReadable )
+      {
+         Data = data;
+         NonReadable = nonReadable;
+      }
+
+      public byte[] Data { get; }
+
+      public bool NonReadable { get; }
+   }
+}

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

@@ -11,7 +11,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 {
    public abstract class KnownWwwEndpoint : IKnownEndpoint
    {
-      protected static readonly ConstructorInfo WwwConstructor = Constants.Types.WWW.GetConstructor( new[] { typeof( string ), typeof( byte[] ), typeof( Dictionary<string, string> ) } );
+      protected static readonly ConstructorInfo WwwConstructor = Constants.ClrTypes.WWW.GetConstructor( new[] { typeof( string ), typeof( byte[] ), typeof( Dictionary<string, string> ) } );
 
       private bool _isBusy = false;
 
@@ -66,7 +66,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
                   string error = null;
                   try
                   {
-                     error = (string)AccessTools.Property( Constants.Types.WWW, "error" ).GetValue( www, null );
+                     error = (string)AccessTools.Property( Constants.ClrTypes.WWW, "error" ).GetValue( www, null );
                   }
                   catch( Exception e )
                   {
@@ -80,7 +80,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
                   }
                   else
                   {
-                     var text = (string)AccessTools.Property( Constants.Types.WWW, "text" ).GetValue( www, null );
+                     var text = (string)AccessTools.Property( Constants.ClrTypes.WWW, "text" ).GetValue( www, null );
 
                      if( text != null )
                      {