15 Коммиты 9b1595dfde ... e0d11341e6

Автор SHA1 Сообщение Дата
  randoman e0d11341e6 * FEATURE - Support image dumping and loading (not automatic!). Disabled by default 6 лет назад
  randoman 2007a9ccd7 2.16.0 6 лет назад
  randoman aef81a1bb1 fix some hooks, fix toggle translations 6 лет назад
  randoman 0ffae1ec72 moved hash generation to single place 6 лет назад
  randoman a218a4a070 better TKK error message 6 лет назад
  randoman 73f7bce916 better error handling, fixed user agents 6 лет назад
  randoman fe74b2c259 error handling for override hooks, removed jurassic from build scripts 6 лет назад
  randoman bc24349660 removed twice overriden hooks 6 лет назад
  randoman f5c3ca2d35 removed DeleteUnmodifiedTextures config 6 лет назад
  randoman 8837942335 removed jurassic, version to 2.16.0 6 лет назад
  randoman 50ec39e11f readme + version bump 6 лет назад
  randoman dffaa8069d minor change to hasing 6 лет назад
  randoman 4ea81b2d99 prefixed cursor, fixed manual image hook 6 лет назад
  randoman e1cd815673 improved texture replacement implementation 6 лет назад
  randoman b507f469ff initial texture support implementation 6 лет назад
40 измененных файлов с 1925 добавлено и 294 удалено
  1. 7 1
      CHANGELOG.md
  2. 85 3
      README.md
  3. 1 1
      src/XUnity.AutoTranslator.Patcher/Patcher.cs
  4. 2 1
      src/XUnity.AutoTranslator.Plugin.BepIn/AutoTranslatorPlugin.cs
  5. 2 2
      src/XUnity.AutoTranslator.Plugin.BepIn/XUnity.AutoTranslator.Plugin.BepIn.csproj
  6. 588 65
      src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs
  7. 21 2
      src/XUnity.AutoTranslator.Plugin.Core/Configuration/Settings.cs
  8. 20 14
      src/XUnity.AutoTranslator.Plugin.Core/Constants/ClrTypes.cs
  9. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Constants/PluginData.cs
  10. 50 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/ComponentExtensions.cs
  11. 12 5
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/HarmonyInstanceExtensions.cs
  12. 85 17
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/ObjectExtensions.cs
  13. 17 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/StringExtensions.cs
  14. 56 0
      src/XUnity.AutoTranslator.Plugin.Core/Extensions/TextureExtensions.cs
  15. 2 2
      src/XUnity.AutoTranslator.Plugin.Core/Features.cs
  16. 136 47
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/HooksSetup.cs
  17. 63 31
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/IMGUIHooks.cs
  18. 4 4
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/NGUIHooks.cs
  19. 297 0
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/NGUIImageHooks.cs
  20. 18 18
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/TextMeshProHooks.cs
  21. 4 4
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/UGUIHooks.cs
  22. 179 0
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/UGUIImageHooks.cs
  23. 4 4
      src/XUnity.AutoTranslator.Plugin.Core/Hooks/UtageHooks.cs
  24. 7 0
      src/XUnity.AutoTranslator.Plugin.Core/ImageTranslationInfo.cs
  25. 8 7
      src/XUnity.AutoTranslator.Plugin.Core/TextTranslationInfo.cs
  26. 9 0
      src/XUnity.AutoTranslator.Plugin.Core/TextureHashGenerationStrategy.cs
  27. 107 0
      src/XUnity.AutoTranslator.Plugin.Core/TextureTranslationInfo.cs
  28. 4 4
      src/XUnity.AutoTranslator.Plugin.Core/UtageSupport/UtageHelpers.cs
  29. 46 0
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/HashHelper.cs
  30. 66 0
      src/XUnity.AutoTranslator.Plugin.Core/Utilities/TextureHelper.cs
  31. 1 1
      src/XUnity.AutoTranslator.Plugin.Core/Web/BaiduTranslateEndpoint.cs
  32. 14 42
      src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateEndpoint.cs
  33. 0 1
      src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateLegitimateEndpoint.cs
  34. 3 3
      src/XUnity.AutoTranslator.Plugin.Core/Web/KnownWwwEndpoint.cs
  35. 1 5
      src/XUnity.AutoTranslator.Plugin.Core/XUnity.AutoTranslator.Plugin.Core.csproj
  36. 2 2
      src/XUnity.AutoTranslator.Plugin.IPA/XUnity.AutoTranslator.Plugin.IPA.csproj
  37. 2 2
      src/XUnity.AutoTranslator.Plugin.UnityInjector/XUnity.AutoTranslator.Plugin.UnityInjector.csproj
  38. 0 1
      src/XUnity.AutoTranslator.Setup/Program.cs
  39. 0 3
      src/XUnity.AutoTranslator.Setup/Properties/Resources.resx
  40. 1 1
      src/XUnity.AutoTranslator.Setup/XUnity.AutoTranslator.Setup.csproj

+ 7 - 1
CHANGELOG.md

@@ -1,4 +1,10 @@
-### 2.15.4
+### 2.16.0
+ * FEATURE - Support image dumping and loading (not automatic!). Disabled by default
+ * BUG FIX - Fixed toggle translation which was broken in 2.15.4
+ * BUG FIX - Updated TKK retrieval logic
+ * MISC - Removed Jurassic dependency as it is no longer required
+
+### 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
 

+ 85 - 3
README.md

@@ -1,5 +1,23 @@
 # XUnity Auto Translator
 
+## Index
+ * [Notice](#notice)
+ * [Text Frameworks](#text-frameworks)
+ * [Plugin Frameworks](#plugin-frameworks)
+ * [Configuration](#configuration)
+ * [Key Mapping](#key-mapping)
+ * [Installation](#installation)
+ * [Translating Mods](#translating-mods)
+ * [Texture Translation](#texture-translation)
+ * [Integrating with Auto Translator](#integrating-with-auto-translator)
+
+## Notice
+The latest version (2.16.0+) now also supports basic image loading/dumping. These are not automatically translated and the feature is disabled by default.
+
+This feature is primarily meant for games with little to no mod support to enable full translations without needing to modify resource files.
+
+If you are going to make use of this feature, please make sure you read and understand the [Texture Translation](#texture-translation) section!
+
 ## Text Frameworks
 This is an auto translation mod that hooks into the unity game engine and attempts to provide translations for the following text frameworks for Unity:
  * UGUI
@@ -8,7 +26,7 @@ This is an auto translation mod that hooks into the unity game engine and attemp
  * TextMeshPro
  * Utage (VN Game Engine)
 
-It does go to the internet, in order to provide the translation, so if you are not comfortable with that, dont use it.
+It does go to the internet, in order to provide the translation, so if you are not comfortable with that, don't use it.
  
 ## Plugin Frameworks
 The mod can be installed into the following Plugin Managers:
@@ -38,7 +56,7 @@ OutputFile=Translation\_AutoGeneratedTranslations.{lang}.txt   ;File to insert g
 [TextFrameworks]
 EnableUGUI=True                  ;Enable or disable UGUI translation
 EnableNGUI=True                  ;Enable or disable NGUI translation
-EnableTextMeshPro=True           ;Enable or disable TextMeshProp translation
+EnableTextMeshPro=True           ;Enable or disable TextMeshPro translation
 EnableIMGUI=False                ;Enable of disable IMGUI translation
 AllowPluginHookOverride=True     ;Allow other text translation plugins to override this plugin's hooks
 
@@ -60,6 +78,15 @@ WhitespaceRemovalStrategy=TrimPerNewline ;Indicates how whitespace/newline remov
 ResizeUILineSpacingScale=        ;A decimal value that the default line spacing should be scaled by during UI resizing, for example: 0.80. NOTE: Only works for UGUI
 ForceUIResizing=True             ;Indicates whether the UI resize behavior should be applied to all UI components regardless of them being translated.
 
+[Texture]
+TextureDirectory=Translation\Texture ;Directory to dump textures to, and root of directories to load images from
+EnableTextureTranslation=False   ;Indicates whether the plugin will attempt to replace in-game images with those from the TextureDirectory directory
+EnableTextureDumping=False       ;Indicates whether the plugin will dump texture it is capable of replacing to the TextureDirectory. Has significant performance impact
+EnableTextureToggling=False      ;Indicates whether or not toggling the translation with the ALT+T hotkey will also affect textures. Not guaranteed to work for all textures. Has significant performance impact
+EnableTextureScanOnSceneLoad=True ;Indicates whether or not the plugin should scan for textures on scene load. This enables the plugin to find and (possibly) replace more texture
+LoadUnmodifiedTextures=False     ;Indicates whether or not unmodified textures should be loaded. Modifications are determined based on the hash in the file name. Only enable this for debugging purposes as it is likely to cause oddities
+TextureHashGenerationStrategy=FromImageName ;Indicates how the mod identifies pictures through hashes. Can be ["FromImageName", "FromImageData", "FromImageNameThenData"]
+
 [Http]
 UserAgent=                       ;Override the user agent used by APIs requiring a user agent
 
@@ -92,7 +119,7 @@ Tag=2.9.0                        ;Tag representing the last version this plugin
 The following key inputs are mapped:
  * ALT + T: Alternate between translated and untranslated versions of all texts provided by this plugin.
  * ALT + D: Dump untranslated texts (if no endpoint is configured)
- * ALT + R: Reload translation files. Useful if you change the text files on the fly.
+ * ALT + R: Reload translation files. Useful if you change the text and texture files on the fly. Not guaranteed to work for all textures.
  * ALT + U: Manual hooking. The default hooks wont always pick up texts. This will attempt to make lookups manually.
  * ALT + F: If OverrideFont is configured, will toggle between overridden and default font.
 
@@ -172,6 +199,61 @@ The file structure should like like this
 ## Translating Mods
 Often other mods UI are implemented through IMGUI. As you can see above, this is disabled by default. By changing the "EnableIMGUI" value to "True", it will start translating IMGUI as well, which likely means that other mods UI will be translated.
 
+## Texture Translation
+From version 2.16.0+ this mod provides basic capabilities to replace images. It is a feature that is disabled by default. There is no automatic translation of these images though.
+
+This feature is primarily meant for games with little to no mod support to enable full translations without needing to modify resource files.
+
+It is controlled by the following configuration:
+
+```ini
+[Texture]
+TextureDirectory=Translation\Texture
+EnableTextureTranslation=False
+EnableTextureDumping=False
+EnableTextureToggling=False
+EnableTextureScanOnSceneLoad=False
+LoadUnmodifiedTextures=False
+TextureHashGenerationStrategy=FromImageName
+```
+
+`TextureDirectory` specifies the directory where textures are dumped to and loaded from. Loading will happen from all subdirectories of the specified directory as well, so you can move dumped images to whatever folder structure you desire.
+
+`EnableTextureTranslation` enables texture translation. This basically means that textures will be loaded from the `TextureDirectory` and it's subsdirectories. These images will replace the in-game images used by the game.
+
+`EnableTextureDumping` enables texture dumping. This means that the mod will dump any images it has not already dumped to the `TextureDirectory`. When dumping textures, it may also be worth enabling `EnableTextureScanOnSceneLoad` to more quickly find all textures that require translating. **NEVER REDISTRIBUTE THIS MOD WITH THIS ENABLED.**
+
+`EnableTextureScanOnSceneLoad` allows the plugin to scan for texture objects on the sceneLoad event. This enables the plugin to find more texture at a tiny performance cost during scene load (which is often during loading screens, etc.). However, because of the way Unity works not all of these are guaranteed to be replacable. If you find an image that is dumped but cannot be translated, please report it. However, please recognize this mod is primarily intended for replacing UI textures, not textures for 3D meshes.
+
+`LoadUnmodifiedTextures` enables whether or not the plugin should load textures that has not been modified. This is only useful for debugging, and likely to cause various visual glitches, especially if `EnableTextureScanOnSceneLoad` is also enabled. **NEVER REDISTRIBUTE THIS MOD WITH THIS ENABLED.**
+
+`EnableTextureToggling` enables whether the ALT+T hotkey will also toggle textures. This is by no means guaranteed to work, especially if `EnableTextureScanOnSceneLoad` is also enabled. **NEVER REDISTRIBUTE THIS MOD WITH THIS ENABLED.**
+
+`TextureHashGenerationStrategy` specifies how images are identified. When images are stored, the game will need some way of associating them with the image that it has to replace.
+This is done through a hash-value that is stored in square brackets in each image file name, like this: `file_name [0223B639A2-6E698E9272].png`. This configuration specifies how these hash-values are generated:
+ * `FromImageName` means that the hash is generated from the internal resource name that the game uses for the image, which may not exist for all images or even be unique. However, it is generally fairly reliable. If an image has no resource name, it will not be dumped.
+ * `FromImageData` means that the hash is generated from the data stored in the image, which is guaranteed to exist for all images. However, generating the hash comes at a performance cost, that will also be incurred by the end-users.
+ * `FromImageNameThenData` means that it should use the name, if available, otherwise use the data.
+
+There's an important catch you need to be aware when dealing with these options and that is if ANY of these options exists: `EnableTextureDumping=True`, `EnableTextureToggling=True`, `TextureHashGenerationStrategy=FromImageData|FromImageNameThenData`, then the game will need to read the raw data from all images it finds in game in order to replace the image and this is an expensive operation.
+
+It is therefore recommended to use `TextureHashGenerationStrategy=FromImageName`. Most likely, images without a resource name won't be interesting to translate anyway.
+
+If you redistribute this mod with translated images, it is recommended you delete all images you either have no intention of translating or are not translated at all.
+
+You can also change the file name to whatever you desire, as long as you keep the hash appended to the end of the file name.
+
+If you take anything away from this section, it should be these two points:
+ * **NEVER REDISTRIBUTE THIS MOD WITH `EnableTextureDumping=True`, `EnableTextureToggling=True` OR `LoadUnmodifiedTextures=True`**
+ * **ONLY REDISTRIBUTE THIS MOD WITH `TextureHashGenerationStrategy=FromImageData|FromImageNameThenData` ENABLED IF ABSOLUTELY REQUIRED BY THE GAME.**
+
+### Technical details about Hash Generation in file names
+There are actually two hashes in the generated file name, separated by a dash (-):
+ * The first hash is a SHA1 (only first 5 bytes) based on the `TextureHashGenerationStrategy` used. If `FromImageName` is specified, then it is based on the UTF8 (without BOM) representation.
+ * The second hash is a SHA1 (only first 5 bytes) based on the data in the image. This is used to determine whether or not the image has been modified, so images that has not been edited are not loaded. Unless `LoadUnmodifiedTextures` is specified.
+
+If `TextureHashGenerationStrategy=FromImageData` is specified, only a single hash will appear in each file name, as that single hash can be used both to identify the image and to determine whether or not it has been edited.
+
 ## Integrating with Auto Translator
 
 ### Implementing a dedicated translation component

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

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

+ 2 - 1
src/XUnity.AutoTranslator.Plugin.BepIn/AutoTranslatorPlugin.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Text;
+using BepInEx;
 using ExIni;
 using XUnity.AutoTranslator.Plugin.Core;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
@@ -21,7 +22,7 @@ namespace XUnity.AutoTranslator.Plugin.BepIn
       {
          _dataFolder = "BepInEx";
          _configPath = Path.Combine( _dataFolder, "AutoTranslatorConfig.ini" );
-         Logger.Current = new BepInLogger();
+         Core.Logger.Current = new BepInLogger();
       }
 
       public IniFile Preferences

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

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

+ 588 - 65
src/XUnity.AutoTranslator.Plugin.Core/AutoTranslationPlugin.cs

@@ -89,6 +89,9 @@ 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 HashSet<string> _untranslatedImages = new HashSet<string>();
+
       private object _advEngine;
       private float? _nextAdvUpdate;
 
@@ -99,7 +102,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;
@@ -144,7 +148,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
          if( Settings.EnableConsole ) DebugConsole.Enable();
 
-         HooksSetup.InstallHooks();
+         HooksSetup.InstallTextHooks();
+         HooksSetup.InstallImageHooks();
 
          try
          {
@@ -181,6 +186,18 @@ namespace XUnity.AutoTranslator.Plugin.Core
             _overrideFont = _hasOverrideFont;
          }
 
+         if( Settings.EnableTextureScanOnSceneLoad && ( Settings.EnableTextureDumping || Settings.EnableTextureTranslation ) )
+         {
+            try
+            {
+               EnableSceneLoadScan();
+            }
+            catch( Exception e )
+            {
+               Logger.Current.Error( e, "An error occurred while settings up texture scene-load scans." );
+            }
+         }
+
          LoadTranslations();
          LoadStaticTranslations();
 
@@ -201,6 +218,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 )
@@ -255,6 +278,23 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
+      public void EnableSceneLoadScan()
+      {
+         // specified in own method, because of chance that this has changed through Unity lifetime
+         SceneManager.sceneLoaded += SceneManager_SceneLoaded;
+      }
+
+      private void SceneManager_SceneLoaded( Scene scene, LoadSceneMode arg1 )
+      {
+         Logger.Current.Info( "SceneLoading..." );
+         var startTime = Time.realtimeSinceStartup;
+
+         ManualHookForTextures();
+
+         var endTime = Time.realtimeSinceStartup;
+         Logger.Current.Info( $"SceneLoaded (took {Math.Round( endTime - startTime, 2 )} seconds)" );
+      }
+
       /// <summary>
       /// Loads the translations found in Translation.{lang}.txt
       /// </summary>
@@ -274,6 +314,17 @@ namespace XUnity.AutoTranslator.Plugin.Core
                   LoadTranslationsInFile( fullFileName );
                }
             }
+
+            if( Settings.EnableTextureTranslation || Settings.EnableTextureDumping )
+            {
+               _translatedImages.Clear();
+               _untranslatedImages.Clear();
+               Directory.CreateDirectory( Path.Combine( Config.Current.DataPath, Settings.TextureDirectory ) );
+               foreach( var fullFileName in GetTextureFiles() )
+               {
+                  RegisterImageFromFile( fullFileName );
+               }
+            }
          }
          catch( Exception e )
          {
@@ -281,11 +332,117 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
+      private void RegisterImageFromFile( string fullFileName )
+      {
+         var fileName = Path.GetFileNameWithoutExtension( fullFileName );
+         var startHash = fileName.LastIndexOf( "[" );
+         var endHash = fileName.LastIndexOf( "]" );
+
+         if( endHash > -1 && startHash > -1 && endHash > startHash )
+         {
+            var takeFrom = startHash + 1;
+
+            // load based on whether or not the key is image hashed
+            var parts = fileName.Substring( takeFrom, endHash - takeFrom ).Split( '-' );
+            string key;
+            string originalHash;
+            if( parts.Length == 1 )
+            {
+               key = parts[ 0 ];
+               originalHash = parts[ 0 ];
+            }
+            else if( parts.Length == 2 )
+            {
+               key = parts[ 0 ];
+               originalHash = parts[ 1 ];
+            }
+            else
+            {
+               Logger.Current.Warn( $"Image not loaded (unknown hash): {fullFileName}." );
+               return;
+            }
+
+            var data = File.ReadAllBytes( fullFileName );
+            var currentHash = HashHelper.Compute( data );
+            var isModified = StringComparer.InvariantCultureIgnoreCase.Compare( originalHash, currentHash ) != 0;
+            
+            // only load images that someone has modified!
+            if( Settings.LoadUnmodifiedTextures || isModified )
+            {
+               RegisterTranslatedImage( key, data );
+               Logger.Current.Debug( $"Image loaded: {fullFileName}." );
+            }
+            else
+            {
+               RegisterUntranslatedImage( key );
+               Logger.Current.Warn( $"Image not loaded (unmodified): {fullFileName}." );
+            }
+
+            //if( Settings.DeleteUnmodifiedTextures && !isModified )
+            //{
+            //   try
+            //   {
+            //      File.Delete( fullFileName );
+            //      Logger.Current.Warn( $"Image deleted (unmodified): {fullFileName}." );
+            //   }
+            //   catch( Exception e )
+            //   {
+            //      Logger.Current.Warn( e, $"An error occurred while trying to delete unmodified image: {fullFileName}." );
+            //   }
+            //}
+         }
+         else
+         {
+            Logger.Current.Warn( $"Image not loaded (no hash): {fullFileName}." );
+         }
+      }
+
+      private void RegisterImageFromData( string textureName, string key, byte[] data )
+      {
+         var name = textureName.SanitizeForFileSystem();
+         var root = Path.Combine( Config.Current.DataPath, Settings.TextureDirectory );
+         var originalHash = HashHelper.Compute( data );
+
+         // allow hash and key to be the same; only store one of them then!
+         string fileName;
+         if( key == originalHash )
+         {
+            fileName = name + " [" + key + "].png";
+         }
+         else
+         {
+            fileName = name + " [" + key + "-" + originalHash + "].png";
+         }
+
+         var fullName = Path.Combine( root, fileName );
+         File.WriteAllBytes( fullName, data );
+         Logger.Current.Info( "Dumped texture file: " + fileName );
+
+         if( Settings.LoadUnmodifiedTextures )
+         {
+            RegisterTranslatedImage( key, data );
+         }
+         else
+         {
+            RegisterUntranslatedImage( key );
+         }
+      }
+
+      private void RegisterTranslatedImage( string key, byte[] data )
+      {
+         _translatedImages[ key ] = data;
+      }
+
+      private void RegisterUntranslatedImage( string key )
+      {
+         _untranslatedImages.Add( key );
+      }
+
       private void LoadTranslationsInFile( string fullFileName )
       {
          if( File.Exists( fullFileName ) )
          {
-            Logger.Current.Debug( $"Loading translations from {fullFileName}." );
+            Logger.Current.Debug( $"Loading texts: {fullFileName}." );
 
             string[] translations = File.ReadAllLines( fullFileName, Encoding.UTF8 );
             foreach( string translation in translations )
@@ -584,6 +741,16 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
+      private bool IsImageRegistered( string key )
+      {
+         return _translatedImages.ContainsKey( key ) || _untranslatedImages.Contains( key );
+      }
+
+      private bool TryGetTranslatedImage( string key, out byte[] data )
+      {
+         return _translatedImages.TryGetValue( key, out data );
+      }
+
       private void AddTranslation( string key, string value )
       {
          _translations[ key ] = value;
@@ -663,9 +830,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 +841,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 +852,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, Texture2D texture = null, bool isPrefixHooked = false )
+      {
+         if( !_imageHooksEnabled ) return;
+         if( !source.IsKnownImageType() ) return;
+
+         HandleImage( source, texture, isPrefixHooked );
+      }
+
+      public void Hook_ImageChanged( Texture2D texture, bool isPrefixHooked = false )
+      {
+         if( !_imageHooksEnabled ) return;
+         if( texture == null ) return;
+
+         HandleImage( null, texture, isPrefixHooked );
+      }
+
+      private void SetTranslatedText( object ui, string translatedText, TextTranslationInfo info )
       {
          info?.SetTranslatedText( translatedText );
 
@@ -705,7 +888,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
       {
          if( _hasOverrideFont )
          {
-            var info = ui.GetTranslationInfo();
+            var info = ui.GetTextTranslationInfo();
             if( _overrideFont )
             {
                info?.ChangeFont( ui );
@@ -718,12 +901,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 +917,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 +961,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
             }
             finally
             {
-               _hooksEnabled = true;
+               _textHooksEnabled = true;
 
                if( info != null )
                {
@@ -801,7 +984,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 +1029,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 +1040,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 +1064,248 @@ 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, bool isPrefixHooked )
+      {
+         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, isPrefixHooked, false );
+            }
+            catch( Exception e )
+            {
+               Logger.Current.Error( e, "An error occurred while translating texture." );
+            }
+         }
+      }
+
+      private void TranslateTexture( object ui, bool forceReload )
+      {
+         if( ui is Texture2D texture2d )
+         {
+            TranslateTexture( null, texture2d, false, forceReload );
+         }
+         else
+         {
+            TranslateTexture( ui, null, false, forceReload );
+         }
+      }
+
+      private void TranslateTexture( object source, Texture2D texture, bool isPrefixHooked, 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
+                        {
+                           if( !isPrefixHooked )
+                           {
+                              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
+                        {
+                           if( !isPrefixHooked )
+                           {
+                              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
+                        {
+                           if( !isPrefixHooked )
+                           {
+                              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
+            {
+               if( ShouldTranslate( texture ) )
+               {
+                  var key = info.GetKey( texture );
+                  if( string.IsNullOrEmpty( key ) ) return;
+
+                  if( !IsImageRegistered( key ) )
+                  {
+                     var name = texture.GetTextureName();
+                     //var format = "[" + texture.format.ToString() + "] ";
+
+                     var originalData = info.GetOrCreateOriginalData( texture );
+                     RegisterImageFromData( name, key, originalData );
+                  }
+               }
+            }
+            finally
+            {
+               info.HasDumpedAlternativeTexture = true;
+            }
+         }
+         finally
+         {
+            _imageHooksEnabled = true;
+         }
+      }
+
+      private bool ShouldTranslate( Texture2D texture )
+      {
+         // convert to int so engine versions that does not have specific enums still work
+         var format = (int)texture.format;
+
+         // 1 = Alpha8
+         // 9 = R16
+         // 63 = R8
+         return format != 1
+            && format != 9
+            && format != 63;
+      }
+
+      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,22 +1330,23 @@ 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
             text = text.TrimIfConfigured();
          }
 
-         //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 ) )
          {
+            //Logger.Current.Debug( "START: " + ui.GetType().Name + ": " + text );
+
             info?.Reset( originalText );
             var isSpammer = ui.IsSpammingComponent();
             var textKey = new TranslationKey( ui, text, isSpammer, context != null );
@@ -986,11 +1422,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 );
@@ -1158,6 +1594,8 @@ namespace XUnity.AutoTranslator.Plugin.Core
             yield return new WaitForSeconds( delay );
             var afterText = ui.GetText();
 
+            //Logger.Current.Debug( "WAITING: " + ui.GetType().Name + ": " + afterText );
+
             if( beforeText == afterText )
             {
                onTextStabilized( afterText );
@@ -1228,7 +1666,7 @@ namespace XUnity.AutoTranslator.Plugin.Core
          onContinue();
       }
 
-      public void Start()
+      public void Awake()
       {
          if( !_initialized )
          {
@@ -1246,6 +1684,18 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
+      public void Start()
+      {
+         try
+         {
+            HooksSetup.InstallOverrideTextHooks();
+         }
+         catch( Exception e )
+         {
+            Logger.Current.Error( e, "An unexpected error occurred during plugin start." );
+         }
+      }
+
       public void Update()
       {
          try
@@ -1529,7 +1979,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 +2015,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 +2030,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 +2043,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 ] );
          }
       }
 
@@ -1608,15 +2058,35 @@ namespace XUnity.AutoTranslator.Plugin.Core
 
          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 is Component component )
                {
-                  SetTranslatedText( kvp.Key, translatedText, info ); // no need to untemplatize the translated text
+                  if( component.gameObject?.activeSelf ?? false )
+                  {
+                     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, true );
                }
             }
+            catch( Exception )
+            {
+               // not super pretty, no...
+               ObjectExtensions.Remove( ui );
+            }
          }
       }
 
@@ -1666,19 +2136,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 +2160,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 +2186,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
@@ -1721,15 +2196,22 @@ namespace XUnity.AutoTranslator.Plugin.Core
                var ui = kvp.Key;
                try
                {
-                  if( ( ui as Component )?.gameObject?.activeSelf ?? false )
+                  if( ui is Component component )
                   {
-                     var info = (TranslationInfo)kvp.Value;
-
-                     if( info != null && info.IsTranslated )
+                     if( component.gameObject?.activeSelf ?? false )
                      {
-                        SetText( ui, info.TranslatedText, true, info );
+                        var tti = kvp.Value as TextTranslationInfo;
+                        if( tti != null && tti.IsTranslated )
+                        {
+                           SetText( ui, tti.TranslatedText, true, tti );
+                        }
                      }
                   }
+
+                  if( Settings.EnableTextureTranslation && Settings.EnableTextureToggling )
+                  {
+                     TranslateTexture( ui, false );
+                  }
                }
                catch( Exception )
                {
@@ -1746,15 +2228,22 @@ namespace XUnity.AutoTranslator.Plugin.Core
                var ui = kvp.Key;
                try
                {
-                  if( ( ui as Component )?.gameObject?.activeSelf ?? false )
+                  if( ui is Component component )
                   {
-                     var info = (TranslationInfo)kvp.Value;
-
-                     if( info != null && info.IsTranslated )
+                     if( component.gameObject?.activeSelf ?? false )
                      {
-                        SetText( ui, info.OriginalText, true, info );
+                        var tti = kvp.Value as TextTranslationInfo;
+                        if( tti != null && tti.IsTranslated )
+                        {
+                           SetText( ui, tti.OriginalText, true, tti );
+                        }
                      }
                   }
+
+                  if( Settings.EnableTextureTranslation && Settings.EnableTextureToggling )
+                  {
+                     TranslateTexture( ui, false );
+                  }
                }
                catch( Exception )
                {
@@ -1815,6 +2304,12 @@ namespace XUnity.AutoTranslator.Plugin.Core
       }
 
       private void ManualHook()
+      {
+         ManualHookForComponents();
+         ManualHookForTextures();
+      }
+
+      private void ManualHookForComponents()
       {
          foreach( var root in GetAllRoots() )
          {
@@ -1822,6 +2317,26 @@ namespace XUnity.AutoTranslator.Plugin.Core
          }
       }
 
+      private void ManualHookForTextures()
+      {
+         if( Settings.EnableTextureScanOnSceneLoad && ( Settings.EnableTextureTranslation || Settings.EnableTextureDumping ) )
+         {
+            // scan all textures and update
+            var textures = Resources.FindObjectsOfTypeAll<Texture2D>();
+            foreach( var texture in textures )
+            {
+               Hook_ImageChanged( texture );
+            }
+
+            //// scan all components and set dirty
+            //var components = GameObject.FindObjectsOfType<Component>();
+            //foreach( var component in components )
+            //{
+            //   component.SetAllDirtyEx();
+            //}
+         }
+      }
+
       private IEnumerable<GameObject> GetAllRoots()
       {
          var objects = GameObject.FindObjectsOfType<GameObject>();
@@ -1864,10 +2379,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 || Settings.EnableTextureDumping )
+               {
+                  if( component.IsKnownImageType() )
+                  {
+                     Hook_ImageChangedOnComponent( component );
+                  }
+               }
             }
 
             if( obj.transform != null )

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

@@ -79,6 +79,15 @@ 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 bool EnableTextureScanOnSceneLoad;
+      public static bool LoadUnmodifiedTextures;
+      //public static bool DeleteUnmodifiedTextures;
+      public static TextureHashGenerationStrategy TextureHashGenerationStrategy;
+
       public static bool CopyToClipboard;
       public static int MaxClipboardCopyCharacters;
 
@@ -117,7 +126,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 +134,22 @@ 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 );
+         EnableTextureScanOnSceneLoad = Config.Current.Preferences[ "Texture" ][ "EnableTextureScanOnSceneLoad" ].GetOrDefault( false );
+         LoadUnmodifiedTextures = Config.Current.Preferences[ "Texture" ][ "LoadUnmodifiedTextures" ].GetOrDefault( false );
+         //DeleteUnmodifiedTextures = Config.Current.Preferences[ "Texture" ][ "DeleteUnmodifiedTextures" ].GetOrDefault( false );
+         TextureHashGenerationStrategy = Config.Current.Preferences[ "Texture" ][ "TextureHashGenerationStrategy" ].GetOrDefault( TextureHashGenerationStrategy.FromImageName );
+
          // 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 )

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

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

+ 50 - 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,52 @@ 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 == null ) return null;
+
+         if( ui is Image image )
+         {
+            return image.mainTexture as Texture2D;
+         }
+         else if( ui is RawImage rawImage )
+         {
+            return rawImage.mainTexture as Texture2D;
+         }
+         else
+         {
+            // 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

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

@@ -18,12 +18,19 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
 
       public static void PatchType( this HarmonyInstance instance, Type type )
       {
-         var parentMethodInfos = type.GetHarmonyMethods();
-         if( parentMethodInfos != null && parentMethodInfos.Count() > 0 )
+         try
          {
-            var info = HarmonyMethod.Merge( parentMethodInfos );
-            var processor = new PatchProcessor( instance, type, info );
-            processor.Patch();
+            var parentMethodInfos = type.GetHarmonyMethods();
+            if( parentMethodInfos != null && parentMethodInfos.Count() > 0 )
+            {
+               var info = HarmonyMethod.Merge( parentMethodInfos );
+               var processor = new PatchProcessor( instance, type, info );
+               processor.Patch();
+            }
+         }
+         catch( Exception e )
+         {
+            Logger.Current.Warn( e, "An error occurred while patching a property/method on a class." );
          }
       }
    }

+ 85 - 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,29 @@ 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();
+
+         return ( ui is Material || ui is Image || ui is RawImage )
+            || ( 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 +50,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 +61,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 +79,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 +88,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 +169,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Extensions
       {
          lock( Sync )
          {
-            return DynamicFields.ToList();
+            return IterateAllPairs().ToList();
          }
       }
 
@@ -130,5 +180,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 )
          {

+ 136 - 47
src/XUnity.AutoTranslator.Plugin.Core/Hooks/HooksSetup.cs

@@ -19,17 +19,110 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
 
    public static class HooksSetup
    {
-      public static void InstallHooks()
+      private static HarmonyInstance _harmony;
+
+      static HooksSetup()
+      {
+         _harmony = HarmonyInstance.Create( "gravydevsupreme.xunity.autotranslator" );
+      }
+
+      public static void InstallOverrideTextHooks()
+      {
+         if( Settings.EnableUGUI || Settings.EnableUtage )
+         {
+            UGUIHooks.HooksOverriden = SetupHook( KnownEvents.OnUnableToTranslateUGUI, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
+         }
+         if( Settings.EnableTextMeshPro )
+         {
+            TextMeshProHooks.HooksOverriden = SetupHook( KnownEvents.OnUnableToTranslateTextMeshPro, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
+         }
+         if( Settings.EnableNGUI )
+         {
+            NGUIHooks.HooksOverriden = SetupHook( KnownEvents.OnUnableToTranslateNGUI, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
+         }
+         if( Settings.EnableIMGUI )
+         {
+            IMGUIHooks.HooksOverriden = SetupHook( KnownEvents.OnUnableToTranslateIMGUI, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
+         }
+      }
+
+      public static void InstallImageHooks()
+      {
+         try
+         {
+            if( Settings.EnableTextureTranslation || Settings.EnableTextureDumping )
+            {
+               _harmony.PatchAll( UGUIImageHooks.All );
+            }
+         }
+         catch( Exception e )
+         {
+            Logger.Current.Error( e, "An error occurred while setting up image hooks for UnityEngine." );
+         }
+
+         try
+         {
+            if( Settings.EnableTextureTranslation || Settings.EnableTextureDumping )
+            {
+               _harmony.PatchAll( NGUIImageHooks.All );
+            }
+         }
+         catch( Exception e )
+         {
+            Logger.Current.Error( e, "An error occurred while setting up image hooks for NGUI." );
+         }
+
+         //var knownTypes = new HashSet<Type> { typeof( Texture ), typeof( Texture2D ), typeof( Sprite ), typeof( Material ) };
+         //foreach( var assembly in AppDomain.CurrentDomain.GetAssemblies() )
+         //{
+         //   try
+         //   {
+         //      var types = assembly.GetTypes();
+         //      foreach( var type in types )
+         //      {
+         //         try
+         //         {
+         //            var properties = type.GetProperties( BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public );
+         //            foreach( var property in properties )
+         //            {
+         //               if( property.CanWrite && knownTypes.Contains( property.PropertyType ) )
+         //               {
+         //                  try
+         //                  {
+         //                     var original = property.GetSetMethod();
+         //                     var prefix = typeof( GenericPrefix_Hook ).GetMethod( "Prefix" );
+         //                     _harmony.Patch( original, new HarmonyMethod( prefix ), null, null );
+         //                     Logger.Current.Warn( "Patched: " + type.Name + "." + property.Name );
+         //                  }
+         //                  catch( Exception e )
+         //                  {
+         //                     Logger.Current.Error( "Failed patching: " + type.Name + "." + property.Name );
+         //                  }
+         //               }
+         //            }
+         //         }
+         //         catch( Exception e )
+         //         {
+         //            Logger.Current.Error( e, "Failed getting properties of type: " + type.Name );
+         //         }
+         //      }
+         //   }
+         //   catch( Exception )
+         //   {
+         //      Logger.Current.Error( "Failed getting types of assembly: " + assembly.FullName );
+         //   }
+         //}
+
+      }
+
+      public static void InstallTextHooks()
       {
-         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( UGUIHooks.All );
             }
          }
          catch( Exception e )
@@ -41,11 +134,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          {
             if( Settings.EnableTextMeshPro )
             {
-               success = SetupHook( KnownEvents.OnUnableToTranslateTextMeshPro, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
-               if( !success )
-               {
-                  harmony.PatchAll( TextMeshProHooks.All );
-               }
+               _harmony.PatchAll( TextMeshProHooks.All );
             }
          }
          catch( Exception e )
@@ -57,11 +146,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          {
             if( Settings.EnableNGUI )
             {
-               success = SetupHook( KnownEvents.OnUnableToTranslateNGUI, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
-               if( !success )
-               {
-                  harmony.PatchAll( NGUIHooks.All );
-               }
+               _harmony.PatchAll( NGUIHooks.All );
             }
          }
          catch( Exception e )
@@ -73,18 +158,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          {
             if( Settings.EnableIMGUI )
             {
-               success = SetupHook( KnownEvents.OnUnableToTranslateNGUI, AutoTranslationPlugin.Current.ExternalHook_TextChanged_WithResult );
-               if( !success )
-               {
-                  harmony.PatchAll( IMGUIHooks.All );
+               _harmony.PatchAll( IMGUIHooks.All );
 
-                  // This wont work in "newer" unity versions!
-                  try
-                  {
-                     harmony.PatchType( typeof( DoButtonGridHook ) );
-                  }
-                  catch { }
+               // This wont work in "newer" unity versions!
+               try
+               {
+                  _harmony.PatchType( typeof( DoButtonGridHook ) );
                }
+               catch { }
             }
          }
          catch( Exception e )
@@ -96,56 +177,64 @@ namespace XUnity.AutoTranslator.Plugin.Core.Hooks
          {
             if( Settings.EnableUtage )
             {
-               harmony.PatchAll( UtageHooks.All );
+               _harmony.PatchAll( UtageHooks.All );
             }
          }
          catch( Exception e )
          {
             Logger.Current.Error( e, "An error occurred while setting up hooks for Utage." );
          }
+
       }
 
       public static bool SetupHook( string eventName, Func<object, string, string> callback )
       {
          if( !Settings.AllowPluginHookOverride ) return false;
 
-         var objects = GameObject.FindObjectsOfType<GameObject>();
-         foreach( var gameObject in objects )
+         try
          {
-            if( gameObject != null )
+            var objects = GameObject.FindObjectsOfType<GameObject>();
+            foreach( var gameObject in objects )
             {
-               var components = gameObject.GetComponents<Component>();
-               foreach( var component in components )
+               if( gameObject != null )
                {
-                  if( component != null )
+                  var components = gameObject.GetComponents<Component>();
+                  foreach( var component in components )
                   {
-                     var e = component.GetType().GetEvent( eventName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
-                     if( e != null )
+                     if( component != null )
                      {
-                        var addMethod = e.GetAddMethod();
-                        if( addMethod != null )
+                        var e = component.GetType().GetEvent( eventName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
+                        if( e != null )
                         {
-                           try
+                           var addMethod = e.GetAddMethod();
+                           if( addMethod != null )
                            {
-                              if( addMethod.IsStatic )
+                              try
                               {
-                                 addMethod.Invoke( null, new object[] { callback } );
-                              }
-                              else
-                              {
-                                 addMethod.Invoke( component, new object[] { callback } );
-                              }
+                                 if( addMethod.IsStatic )
+                                 {
+                                    addMethod.Invoke( null, new object[] { callback } );
+                                 }
+                                 else
+                                 {
+                                    addMethod.Invoke( component, new object[] { callback } );
+                                 }
 
-                              Logger.Current.Info( eventName + " was hooked by external plugin." );
-                              return true;
+                                 Logger.Current.Info( eventName + " was hooked by external plugin." );
+                                 return true;
+                              }
+                              catch { }
                            }
-                           catch { }
                         }
                      }
                   }
                }
             }
          }
+         catch( Exception e )
+         {
+            Logger.Current.Error( e, $"An error occurred while setting up override hooks for '{eventName}'." );
+         }
 
          return false;
       }

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

@@ -11,6 +11,8 @@ namespace XUnity.AutoTranslator.Plugin.Core.IMGUI
 {
    public static class IMGUIHooks
    {
+      public static bool HooksOverriden = false;
+
       public static readonly Type[] All = new[] {
          typeof( BeginGroupHook ),
          typeof( BoxHook ),
@@ -29,17 +31,20 @@ 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 )
       {
-         AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         if( !IMGUIHooks.HooksOverriden )
+         {
+            AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         }
       }
    }
 
@@ -48,17 +53,20 @@ 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 )
       {
-         AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         if( !IMGUIHooks.HooksOverriden )
+         {
+            AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         }
       }
 
    }
@@ -68,17 +76,20 @@ 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 )
       {
-         AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         if( !IMGUIHooks.HooksOverriden )
+         {
+            AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         }
       }
    }
 
@@ -87,17 +98,20 @@ 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 )
       {
-         AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         if( !IMGUIHooks.HooksOverriden )
+         {
+            AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         }
       }
    }
 
@@ -106,17 +120,20 @@ 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 )
       {
-         AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         if( !IMGUIHooks.HooksOverriden )
+         {
+            AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         }
       }
    }
 
@@ -125,17 +142,20 @@ 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 )
       {
-         AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         if( !IMGUIHooks.HooksOverriden )
+         {
+            AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         }
       }
    }
 
@@ -144,17 +164,20 @@ 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 )
       {
-         AutoTranslationPlugin.Current.Hook_TextChanged( title );
+         if( !IMGUIHooks.HooksOverriden )
+         {
+            AutoTranslationPlugin.Current.Hook_TextChanged( title );
+         }
       }
    }
 
@@ -163,19 +186,22 @@ 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 )
       {
-         foreach( var content in contents )
+         if( !IMGUIHooks.HooksOverriden )
          {
-            AutoTranslationPlugin.Current.Hook_TextChanged( content );
+            foreach( var content in contents )
+            {
+               AutoTranslationPlugin.Current.Hook_TextChanged( content );
+            }
          }
       }
    }
@@ -185,17 +211,20 @@ 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 )
       {
-         AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         if( !IMGUIHooks.HooksOverriden )
+         {
+            AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         }
       }
    }
 
@@ -204,17 +233,20 @@ 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 )
       {
-         AutoTranslationPlugin.Current.Hook_TextChanged( content );
+         if( !IMGUIHooks.HooksOverriden )
+         {
+            AutoTranslationPlugin.Current.Hook_TextChanged( 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 )

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

@@ -0,0 +1,297 @@
+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_spriteMaterial_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_spriteMaterial_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, null, false );
+      }
+   }
+   
+   [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, null, false );
+      }
+   }
+
+   [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, null, false );
+      }
+   }
+   
+   [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, null, false );
+      }
+   }
+
+   [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, null, false );
+      }
+   }
+
+   [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, null, false );
+      }
+   }
+
+   [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, null, false );
+      }
+   }
+
+   [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 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 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;
       }
 

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

@@ -0,0 +1,179 @@
+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( Image_material_Hook ),
+         typeof( RawImage_texture_Hook ),
+         typeof( Cursor_SetCursor_Hook ),
+
+         // fallback hooks on material (Prefix hooks)
+         typeof( Material_mainTexture_Hook ),
+      };
+   }
+
+   public static class GenericPrefix_Hook
+   {
+      public static void Prefix( object __instance, object value )
+      {
+         if( value is Texture2D texture2d )
+         {
+            AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance, texture2d, true );
+         }
+      }
+   }
+
+   [Harmony]
+   public static class Material_mainTexture_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return true;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( typeof( Material ), "mainTexture" ).GetSetMethod();
+      }
+
+      public static void Prefix( object __instance, Texture value )
+      {
+         if( value is Texture2D texture2d )
+         {
+            AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance, texture2d, true );
+         }
+      }
+   }
+
+   [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, null, false );
+         }
+      }
+   }
+
+   [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, null, false );
+      }
+   }
+
+   [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, null, false );
+      }
+   }
+
+   [Harmony]
+   public static class Image_material_Hook
+   {
+      static bool Prepare( HarmonyInstance instance )
+      {
+         return true;
+      }
+
+      static MethodBase TargetMethod( HarmonyInstance instance )
+      {
+         return AccessTools.Property( typeof( Image ), "material" ).GetSetMethod();
+      }
+
+      public static void Postfix( object __instance )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance, null, false );
+      }
+   }
+
+   [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 Prefix( object __instance, Texture value )
+      {
+         if( value is Texture2D texture2d )
+         {
+            AutoTranslationPlugin.Current.Hook_ImageChangedOnComponent( __instance, texture2d, true );
+         }
+      }
+   }
+
+   [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 Prefix( Texture2D texture )
+      {
+         AutoTranslationPlugin.Current.Hook_ImageChanged( texture, true );
+      }
+   }
+}

+ 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
+   }
+}

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

@@ -0,0 +1,107 @@
+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 static readonly Encoding UTF8 = new UTF8Encoding( false );
+
+      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 );
+            }
+            else if( Settings.TextureHashGenerationStrategy == TextureHashGenerationStrategy.FromImageName )
+            {
+               var name = texture.name; // name may be duplicate, WILL be duplicate!
+               if( string.IsNullOrEmpty( name ) ) return;
+
+               _key = HashHelper.Compute( UTF8.GetBytes( name ) );
+
+               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 );
+               }
+               else
+               {
+                  _key = HashHelper.Compute( UTF8.GetBytes( name ) );
+
+                  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 hex = ByteArrayToHexViaLookup32( hash );
+         return hex.Substring( 0, 10 );
+      }
+
+      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; }
+   }
+}

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

@@ -38,7 +38,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
       public override void ApplyHeaders( WebHeaderCollection headers )
       {
-         headers[ HttpRequestHeader.UserAgent ] = Settings.GetUserAgent( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36" );
+         headers[ HttpRequestHeader.UserAgent ] = Settings.GetUserAgent( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36" );
          headers[ HttpRequestHeader.AcceptCharset ] = "UTF-8";
       }
 

+ 14 - 42
src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateEndpoint.cs

@@ -7,7 +7,6 @@ using System.Net;
 using System.Reflection;
 using System.Text;
 using Harmony;
-using Jurassic;
 using SimpleJSON;
 using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;
@@ -21,7 +20,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
       //protected static readonly ConstructorInfo WwwConstructor = Constants.Types.WWW?.GetConstructor( new[] { typeof( string ), typeof( byte[] ), typeof( Dictionary<string, string> ) } );
       private static readonly string HttpsServicePointTemplateUrl = "https://translate.googleapis.com/translate_a/single?client=t&dt=t&sl={0}&tl={1}&ie=UTF-8&oe=UTF-8&tk={2}&q={3}";
       private static readonly string HttpsTranslateUserSite = "https://translate.google.com";
-      private static readonly string DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36";
+      private static readonly string DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36";
       //private static readonly string UserAgentRepository = "https://techblog.willshouse.com/2012/01/03/most-common-user-agents/";
       private static readonly System.Random RandomNumbers = new System.Random();
 
@@ -199,40 +198,14 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
                {
                   var html = downloadResult.Result;
 
-                  const string lookup = "TKK=eval('";
-                  var index = html.IndexOf( lookup );
-                  if( index > -1 ) // jurassic approach
+                  bool found = false;
+                  string[] lookups = new[] { "tkk:'", "TKK='" };
+                  foreach( var lookup in lookups )
                   {
-                     var lookupIndex = index + lookup.Length;
-                     var openClamIndex = html.IndexOf( '{', lookupIndex );
-                     var closeClamIndex = html.IndexOf( '}', openClamIndex );
-                     var functionIndex = html.IndexOf( "function", lookupIndex );
-                     var script = html.Substring( functionIndex, closeClamIndex - functionIndex + 1 );
-                     var decodedScript = script.Replace( "\\x3d", "=" ).Replace( "\\x27", "'" ).Replace( "function", "function FuncName" );
-
-                     // https://github.com/paulbartrum/jurassic/wiki/Safely-executing-user-provided-scripts
-                     ScriptEngine engine = new ScriptEngine();
-                     engine.Evaluate( decodedScript );
-                     var result = engine.CallGlobalFunction<string>( "FuncName" );
-
-                     var parts = result.Split( '.' );
-                     if( parts.Length == 2 )
-                     {
-                        m = long.Parse( parts[ 0 ] );
-                        s = long.Parse( parts[ 1 ] );
-                     }
-                     else
-                     {
-                        Logger.Current.Warn( "An error occurred while setting up GoogleTranslate Cookie/TKK. Could not locate TKK value. Using fallback TKK values instead." );
-                     }
-                  }
-                  else
-                  {
-                     const string lookup2 = "TKK='";
-                     index = html.IndexOf( lookup2 );
+                     var index = html.IndexOf( lookup );
                      if( index > -1 ) // simple string approach
                      {
-                        var startIndex = index + lookup2.Length;
+                        var startIndex = index + lookup.Length;
                         var endIndex = html.IndexOf( "'", startIndex );
                         var result = html.Substring( startIndex, endIndex - startIndex );
 
@@ -241,17 +214,16 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
                         {
                            m = long.Parse( parts[ 0 ] );
                            s = long.Parse( parts[ 1 ] );
+                           found = true;
+                           break;
                         }
-                        else
-                        {
-                           Logger.Current.Warn( "An error occurred while setting up GoogleTranslate Cookie/TKK. Could not locate TKK value. Using fallback TKK values instead." );
-                        }
-                     }
-                     else
-                     {
-                        Logger.Current.Warn( "An error occurred while setting up GoogleTranslate Cookie/TKK. Could not locate TKK value. Using fallback TKK values instead." );
                      }
                   }
+
+                  if( !found )
+                  {
+                     Logger.Current.Warn( "An error occurred while setting up GoogleTranslate TKK. Could not locate TKK value. Using fallback TKK values instead." );
+                  }
                }
                catch( Exception e )
                {
@@ -262,7 +234,7 @@ namespace XUnity.AutoTranslator.Plugin.Core.Web
 
          if( error != null )
          {
-            Logger.Current.Warn( "An error occurred while setting up GoogleTranslate Cookie/TKK. Using fallback TKK values instead." + Environment.NewLine + error );
+            Logger.Current.Warn( "An error occurred while setting up GoogleTranslate TKK. Using fallback TKK values instead." + Environment.NewLine + error );
          }
       }
 

+ 0 - 1
src/XUnity.AutoTranslator.Plugin.Core/Web/GoogleTranslateLegitimateEndpoint.cs

@@ -7,7 +7,6 @@ using System.Net;
 using System.Reflection;
 using System.Text;
 using Harmony;
-using Jurassic;
 using SimpleJSON;
 using UnityEngine;
 using XUnity.AutoTranslator.Plugin.Core.Configuration;

+ 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 )
                      {

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

@@ -2,13 +2,9 @@
 
    <PropertyGroup>
       <TargetFramework>net35</TargetFramework>
-      <Version>2.15.4</Version>
+      <Version>2.16.0</Version>
    </PropertyGroup>
 
-   <ItemGroup>
-      <PackageReference Include="Jurassic" Version="2.2.2" />
-   </ItemGroup>
-
    <ItemGroup>
       <Reference Include="0Harmony">
          <HintPath>..\..\libs\0Harmony.dll</HintPath>

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

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

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

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

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

@@ -47,7 +47,6 @@ namespace XUnity.AutoTranslator.Setup
             AddFile( Path.Combine( managedDir, "0Harmony.dll" ), Resources._0Harmony );
             AddFile( Path.Combine( managedDir, "ExIni.dll" ), Resources.ExIni );
             AddFile( Path.Combine( managedDir, "ReiPatcher.exe" ), Resources.ReiPatcher ); // needed because file is modified by attribute in ReiPatcher... QQ
-            AddFile( Path.Combine( managedDir, "Jurassic.dll" ), Resources.Jurassic );
             AddFile( Path.Combine( managedDir, "XUnity.AutoTranslator.Plugin.Core.dll" ), Resources.XUnity_AutoTranslator_Plugin_Core, true );
 
             // create an .ini file for each launcher, if it does not already exist

+ 0 - 3
src/XUnity.AutoTranslator.Setup/Properties/Resources.resx

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

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

@@ -4,7 +4,7 @@
       <OutputType>Exe</OutputType>
       <TargetFramework>net40</TargetFramework>
       <AssemblyName>SetupReiPatcherAndAutoTranslator</AssemblyName>
-      <Version>2.15.4</Version>
+      <Version>2.16.0</Version>
    </PropertyGroup>
 
    <ItemGroup>