Skip to content

Commit

Permalink
Version 4.9.0
Browse files Browse the repository at this point in the history
 * FEATURE - API to support easier manual translations of TextAsset resources
 * FEATURE - Support for not loading every translated image into memory permanently, but instead loading them on demand (new configuration option)
 * FEATURE - Better support for variations of Chinese as output language. 'zh-CN', 'zh-TW', 'zh-Hans', 'zh-Hant', 'zh' can now all be used by all translators if supported
 * FEATURE - Better support for variations of Chinese as input language. Plugin wont complain if 'zh-Hans', 'zh-Hant' or 'zh' is used
 * MISC - Support for older versions of UTAGE
 * MISC - New option that enables ignoring rules for calling virtual methods when setting the text of a text component: 'IgnoreVirtualTextSetterCallingRules'
 * MISC - Updated version of MonoMod distributed with the plugin
 * BUG FIX - Potential errors caused by using weak references incorrectly causing a race condition
 * BUG FIX - Potential error during initialization if Resource Redirector is not present
  • Loading branch information
randoman committed Feb 28, 2020
1 parent 812a41f commit e52a507
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 7 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
### 4.9.0
* FEATURE - API to support easier translations of TextAsset resources
* FEATURE - API to support easier manual translations of TextAsset resources
* FEATURE - Support for not loading every translated image into memory permanently, but instead loading them on demand (new configuration option)
* FEATURE - Better support for variations of Chinese as output language. 'zh-CN', 'zh-TW', 'zh-Hans', 'zh-Hant', 'zh' can now all be used by all translators if supported
* FEATURE - Better support for variations of Chinese as input language. Plugin wont complain if 'zh-Hans', 'zh-Hant' or 'zh' is used
* MISC - Support for older versions of UTAGE
* MISC - New option that enables ignoring rules for calling virtual methods when setting the text of a text component: 'IgnoreVirtualTextSetterCallingRules'
* MISC - Updated version of MonoMod distributed with the plugin
* BUG FIX - Potential errors caused by using weak references incorrectly causing a race condition
* BUG FIX - Potential error during initialization if Resource Redirector is not present

Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ EnableTranslationScoping=False ;Indicates the plugin should parse 'TARC' direc
EnableSilentMode=False ;Indicates the plugin should not print out success messages in relation to translations
BlacklistedIMGUIPlugins= ;If an IMGUI window assembly/class/method name contains any of the strings in this list (case insensitive) that UI will not be translated. Requires MonoMod hooks. This is a list seperated by ';'
OutputUntranslatableText=False ;Indicates if texts that are considered by the plugin to be untranslatable should be output to the specified OutputFile
IgnoreVirtualTextSetterCallingRules; Indicates that rules for virtual method calls should be ignored when trying to set the text of a text component. May in some cases help setting the text of stubborn components

[Texture]
TextureDirectory=Translation\{Lang}\Texture ;Directory to dump textures to, and root of directories to load images from. Can use placeholder: {GameExeName}, {Lang}
Expand All @@ -304,6 +305,7 @@ TextureHashGenerationStrategy=FromImageName ;Indicates how the mod identifies pi
DuplicateTextureNames= ;Indicates specific texture names that are duplicated in the game. List is separated by ';'.
DetectDuplicateTextureNames=False;Indicates if the plugin should detect duplicate texture names.
EnableLegacyTextureLoading=False ;Indicates the plugin should use a different strategy to load images, that may be relevant if the game engine is old
CacheTexturesInMemory=True ;Indicates that all textures loaded should be kept in memory for optimal performance. Disable to decrease memory usage

[ResourceRedirector]
PreferredStoragePath=Translation\{Lang}\RedirectedResources ;Indicates the preferred storage for redirected resources in relation to the Auto Translator. Can use placeholder: {GameExeName}, {Lang}
Expand Down Expand Up @@ -436,6 +438,7 @@ If MonoMod hooks are not forced they are only used if available and a given meth
* `EnableSilentMode`: Indicates the plugin should not print out success messages in relation to translations.
* `BlacklistedIMGUIPlugins`: If an IMGUI window assembly/class/method name contains any of the strings in this list (case insensitive) that UI will not be translated. Requires MonoMod hooks. This is a list seperated by ';'.
* `OutputUntranslatableText`: Indicates if texts that are considered by the plugin to be untranslatable should be output to the specified OutputFile. Enabling this may also output a lot of garbage to the `OutputFile` that should be deleted before potential redistribution. **Never redistribute the mod with this enabled.**
* `IgnoreVirtualTextSetterCallingRules`: Indicates that rules for virtual method calls should be ignored when trying to set the text of a text component. May in some cases help setting the text of stubborn components.

## Frequently Asked Questions
> **Q: Why doesn't this plugin work in game X?**
Expand Down Expand Up @@ -696,6 +699,7 @@ TextureHashGenerationStrategy=FromImageName
DuplicateTextureNames=
DetectDuplicateTextureNames=False
EnableLegacyTextureLoading=False
CacheTexturesInMemory=True
```

`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.
Expand All @@ -716,7 +720,9 @@ EnableLegacyTextureLoading=False

`DetectDuplicateTextureNames` specifies that the plugin should identify which image names are duplicated and update the configuration with these names automatically. **Never redistribute the mod with this enabled.**

`EnableLegacyTextureLoading` specifies that the plugin should use attempt to load images differently, which may be relevant if the unity engine is old (verified with versions less than 5.3). This should be used unless the images that are loaded are not the ones that you expected.
`EnableLegacyTextureLoading` specifies that the plugin should use attempt to load images differently, which may be relevant if the unity engine is old (verified with versions less than 5.3). This should not be used unless the images that are loaded are not the ones that you expected.

`CacheTexturesInMemory` specifies that all translation textures should be kept in memory to optimize performance. Can be disabled to improve performance

`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:
Expand Down
Binary file modified libs/MonoMod/MonoMod.RuntimeDetour.dll
Binary file not shown.
Binary file modified libs/MonoMod/MonoMod.Utils.dll
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ string TranslateCell( string cellText )

protected override bool DumpAsset( string calculatedModificationPath, TextAsset asset, IAssetOrResourceLoadedContext context )
{
if( !!textAssetHelper.IsTable( asset ) )
if( !textAssetHelper.IsTable( asset ) )
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ internal static class Settings
public static HashSet<string> BlacklistedIMGUIPlugins;
public static bool EnableTextPathLogging;
public static bool OutputUntranslatableText;
public static bool IgnoreVirtualTextSetterCallingRules;

public static string TextureDirectory;
public static bool EnableTextureTranslation;
Expand Down Expand Up @@ -206,7 +207,9 @@ public static void Configure()
.ToHashSet( StringComparer.OrdinalIgnoreCase ) ?? new HashSet<string>( StringComparer.OrdinalIgnoreCase );
EnableTextPathLogging = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "EnableTextPathLogging", false );
OutputUntranslatableText = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "OutputUntranslatableText", false );
IgnoreVirtualTextSetterCallingRules = PluginEnvironment.Current.Preferences.GetOrDefault( "Behaviour", "IgnoreVirtualTextSetterCallingRules", false );


TextureDirectory = PluginEnvironment.Current.Preferences.GetOrDefault( "Texture", "TextureDirectory", @"Translation\{Lang}\Texture" );
TexturesPath = Path.Combine( PluginEnvironment.Current.TranslationPath, Settings.TextureDirectory ).Parameterize();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public static bool DoesTextMeshProSupportRichText( object ui, Type type )
{
return ClrTypes.TMP_Text.IsAssignableFrom( type ) && Equals( type.CachedProperty( RichTextPropertyName )?.Get( ui ), true );
}
else
else
{
return ( ClrTypes.TextMeshPro?.IsAssignableFrom( type ) == true && Equals( type.CachedProperty( RichTextPropertyName )?.Get( ui ), true ) )
|| ( ClrTypes.TextMeshProUGUI?.IsAssignableFrom( type ) == true && Equals( type.CachedProperty( RichTextPropertyName )?.Get( ui ), true ) );
Expand All @@ -98,7 +98,7 @@ public static bool SupportsStabilization( this object ui )
//}
//return true;
}

return true;
}

Expand Down Expand Up @@ -329,7 +329,32 @@ public static void SetText( this object ui, string text )
else
{
// fallback to reflective approach
type.CachedProperty( TextPropertyName )?.Set( ui, text );
var textProperty = type.CachedProperty( TextPropertyName );
if( textProperty != null )
{
textProperty.Set( ui, text );

if( Settings.IgnoreVirtualTextSetterCallingRules )
{
var newText = (string)textProperty.Get( ui );
Type currentType = type;
while( text != newText && currentType != null )
{
var typeAndMethod = GetTextPropertySetterInParent( currentType );
if( typeAndMethod != null )
{
currentType = typeAndMethod.Type;

typeAndMethod.SetterInvoker( ui, text );
newText = (string)textProperty.Get( ui );
}
else
{
currentType = null;
}
}
}
}

// TMPro
var maxVisibleCharactersProperty = type.CachedProperty( "maxVisibleCharacters" );
Expand All @@ -352,5 +377,55 @@ public static void SetText( this object ui, string text )
}
}
}

private static Dictionary<Type, TypeAndMethod> _textSetters = new Dictionary<Type, TypeAndMethod>();

private static TypeAndMethod GetTextPropertySetterInParent( Type type )
{
var current = type.BaseType;
while( current != null )
{
if( _textSetters.TryGetValue( type, out var typeAndMethod ) )
{
return typeAndMethod;
}
else
{
var property = current.GetProperty( "text", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic );

if( property != null && property.CanWrite )
{
var tam = new TypeAndMethod( current, property.GetSetMethod() );
_textSetters[ current ] = tam;
return tam;
}

current = current.BaseType;
}
}

return null;
}

private class TypeAndMethod
{
Action<object, object> _setterInvoker;

public TypeAndMethod( Type type, MethodBase method )
{
Type = type;
SetterMethod = method;
}

public Type Type { get; }
public MethodBase SetterMethod { get; }
public Action<object, object> SetterInvoker
{
get
{
return _setterInvoker ?? (_setterInvoker = (Action<object, object>)VirtcallRuleBreaker.GenerateDelegate( SetterMethod, false ) );
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static class LanguageHelper

private static readonly HashSet<string> LanguagesNotUsingWhitespaceBetweenWords = new HashSet<string>
{
"ja", "zh", "zh-Hans", "zh-Hant", "zh-CN", "zh-TW", "zh-Hans", "zh-Hant"
"ja", "zh", "zh-CN", "zh-TW", "zh-Hans", "zh-Hant"
};

internal static bool IsFromLanguageSupported( string code )
Expand Down
138 changes: 138 additions & 0 deletions src/XUnity.AutoTranslator.Plugin.Core/Utilities/VirtcallRuleBreaker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using MonoMod.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;

namespace XUnity.AutoTranslator.Plugin.Core.Utilities
{
static class VirtcallRuleBreaker
{
public static Delegate GenerateDelegate( MethodBase method, bool useCallVirt )
{
var parameterTypes = GetActualParameters( method );
var actionParameters = GetActionParameters( method );
var returnType = method is MethodInfo mi ? mi.ReturnType : typeof( void );

var dynamicMethod = new DynamicMethodDefinition( "DynamicMethod_" + method.Name, returnType, actionParameters );

var il = dynamicMethod.GetILProcessor();
if( actionParameters.Length >= 1 )
{
il.Emit( Mono.Cecil.Cil.OpCodes.Ldarg_0 );
il.Emit( Mono.Cecil.Cil.OpCodes.Castclass, parameterTypes[ 0 ] );
if( actionParameters.Length >= 2 )
{
il.Emit( Mono.Cecil.Cil.OpCodes.Ldarg_1 );
il.Emit( Mono.Cecil.Cil.OpCodes.Castclass, parameterTypes[ 1 ] );
if( actionParameters.Length >= 3 )
{
il.Emit( Mono.Cecil.Cil.OpCodes.Ldarg_2 );
il.Emit( Mono.Cecil.Cil.OpCodes.Castclass, parameterTypes[ 2 ] );
if( actionParameters.Length >= 4 )
{
il.Emit( Mono.Cecil.Cil.OpCodes.Ldarg_3 );
il.Emit( Mono.Cecil.Cil.OpCodes.Castclass, parameterTypes[ 3 ] );
}
}
}
}
il.Emit( useCallVirt ? Mono.Cecil.Cil.OpCodes.Callvirt : Mono.Cecil.Cil.OpCodes.Call, method );
il.Emit( Mono.Cecil.Cil.OpCodes.Ret );

var delegateType = returnType == typeof( void ) ? GetGenericActionType( actionParameters ) : GetGenericFuncType( actionParameters, returnType );
var actionOrFunc = dynamicMethod.Generate().CreateDelegate( delegateType );
return actionOrFunc;
}

static Type[] GetActionParameters( MethodBase method )
{
if( method.IsStatic )
{
return method.GetParameters().Select( x => typeof( object ) ).ToArray();
}
else
{
return new Type[] { typeof( object ) }.Concat( method.GetParameters().Select( x => typeof( object ) ) ).ToArray();
}
}

static Type[] GetActualParameters( MethodBase method )
{
if( method.IsStatic )
{
return method.GetParameters().Select( x => x.ParameterType ).ToArray();
}
else
{
return new Type[] { method.DeclaringType }.Concat( method.GetParameters().Select( x => x.ParameterType ) ).ToArray();
}
}

static Type GetGenericActionType( Type[] parameterTypes )
{
return GetActionType( parameterTypes.Length ).MakeGenericType( parameterTypes );
}

static Type GetGenericFuncType( Type[] parameterTypes, Type returnType )
{
return GetFuncType( parameterTypes.Length ).MakeGenericType( parameterTypes.Concat( new[] { returnType } ).ToArray() );
}

static Type GetActionType( int parameterCount )
{
switch( parameterCount )
{
case 0:
return typeof( Action );
case 1:
return typeof( Action<> );
case 2:
return typeof( Action<,> );
case 3:
return typeof( Action<,,> );
case 4:
return typeof( Action<,,,> );
default:
throw new ArgumentException( nameof( parameterCount ) );
}
}

static Type GetFuncType( int parameterCount )
{
switch( parameterCount )
{
case 0:
return typeof( Func<> );
case 1:
return typeof( Func<,> );
case 2:
return typeof( Func<,,> );
case 3:
return typeof( Func<,,,> );
case 4:
return typeof( Func<,,,,> );
default:
throw new ArgumentException( nameof( parameterCount ) );
}
}

//static string GetFuncTypeName( int parameterCount )
//{
// return "System.Func`" + ( parameterCount + 1 );
//}

//static string GetActionTypeName( int parameterCount )
//{
// if( parameterCount == 0 )
// {
// return "System.Action";
// }
// else
// {
// return "System.Action`" + parameterCount;
// }
//}
}
}

0 comments on commit e52a507

Please sign in to comment.