Decompiled source of GameTranslator v2.0.6
BepInEx/core/XUnity.Common.dll
Decompiled 4 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using Mono.Cecil; using Mono.Cecil.Cil; using MonoMod.Utils; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using XUnity.Common.Constants; using XUnity.Common.Extensions; using XUnity.Common.Logging; using XUnity.Common.Utilities; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("XUnity.AutoTranslator.Plugin.Core")] [assembly: AssemblyCompany("gravydevsupreme")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Common dependencies shared between XUnity Auto Translator and Resource Redirector.")] [assembly: AssemblyFileVersion("1.0.3.0")] [assembly: AssemblyInformationalVersion("1.0.3")] [assembly: AssemblyProduct("XUnity.Common")] [assembly: AssemblyTitle("XUnity.Common")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.3.0")] [module: UnverifiableCode] namespace XUnity.Common.Utilities { public static class ArrayHelper { public static T[] Null<T>() { return null; } } public static class CabHelper { private static string CreateRandomCab() { return "CAB-" + Guid.NewGuid().ToString("N"); } public static void RandomizeCab(byte[] assetBundleData) { string @string = Encoding.ASCII.GetString(assetBundleData, 0, Math.Min(1024, assetBundleData.Length - 4)); int num = @string.IndexOf("CAB-", StringComparison.Ordinal); if (num >= 0) { int num2 = @string.Substring(num).IndexOf('\0'); if (num2 >= 0 && num2 <= 36) { string s = CreateRandomCab(); Buffer.BlockCopy(Encoding.ASCII.GetBytes(s), 36 - num2, assetBundleData, num, num2); } } } public static void RandomizeCabWithAnyLength(byte[] assetBundleData) { FindAndReplaceCab("CAB-", 0, assetBundleData, 2048); } private static void FindAndReplaceCab(string ansiStringToStartWith, byte byteToEndWith, byte[] data, int maxIterations = -1) { int num = Math.Min(data.Length, maxIterations); if (num == -1) { num = data.Length; } int num2 = 0; int length = ansiStringToStartWith.Length; string text = Guid.NewGuid().ToString("N"); int num3 = 0; for (int i = 0; i < num; i++) { char c = (char)data[i]; if (num2 == length) { while (data[i] != byteToEndWith && i < num) { if (num3 >= text.Length) { num3 = 0; text = Guid.NewGuid().ToString("N"); } data[i++] = (byte)text[num3++]; } break; } num2 = ((c == ansiStringToStartWith[num2]) ? (num2 + 1) : 0); } } } internal static class CecilFastReflectionHelper { private static readonly Type[] DynamicMethodDelegateArgs = new Type[2] { typeof(object), typeof(object[]) }; public static FastReflectionDelegate CreateFastDelegate(MethodBase method, bool directBoxValueAccess, bool forceNonVirtcall) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Expected O, but got Unknown //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Expected O, but got Unknown //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Expected O, but got Unknown DynamicMethodDefinition val = new DynamicMethodDefinition("FastReflection<" + method.DeclaringType.FullName + "." + method.Name + ">", typeof(object), DynamicMethodDelegateArgs); ILProcessor iLProcessor = val.GetILProcessor(); ParameterInfo[] parameters = method.GetParameters(); bool flag = true; if (!method.IsStatic) { iLProcessor.Emit(OpCodes.Ldarg_0); if (method.DeclaringType.IsValueType) { Extensions.Emit(iLProcessor, OpCodes.Unbox_Any, method.DeclaringType); } } for (int i = 0; i < parameters.Length; i++) { Type type = parameters[i].ParameterType; bool isByRef = type.IsByRef; if (isByRef) { type = type.GetElementType(); } bool isValueType = type.IsValueType; if (isByRef && isValueType && !directBoxValueAccess) { iLProcessor.Emit(OpCodes.Ldarg_1); iLProcessor.Emit(OpCodes.Ldc_I4, i); } iLProcessor.Emit(OpCodes.Ldarg_1); iLProcessor.Emit(OpCodes.Ldc_I4, i); if (isByRef && !isValueType) { Extensions.Emit(iLProcessor, OpCodes.Ldelema, typeof(object)); continue; } iLProcessor.Emit(OpCodes.Ldelem_Ref); if (!isValueType) { continue; } if (!isByRef || !directBoxValueAccess) { Extensions.Emit(iLProcessor, OpCodes.Unbox_Any, type); if (isByRef) { Extensions.Emit(iLProcessor, OpCodes.Box, type); iLProcessor.Emit(OpCodes.Dup); Extensions.Emit(iLProcessor, OpCodes.Unbox, type); if (flag) { flag = false; val.Definition.Body.Variables.Add(new VariableDefinition((TypeReference)new PinnedType((TypeReference)new PointerType(((MemberReference)val.Definition).Module.TypeSystem.Void)))); } iLProcessor.Emit(OpCodes.Stloc_0); iLProcessor.Emit(OpCodes.Stelem_Ref); iLProcessor.Emit(OpCodes.Ldloc_0); } } else { Extensions.Emit(iLProcessor, OpCodes.Unbox, type); } } if (method.IsConstructor) { Extensions.Emit(iLProcessor, OpCodes.Newobj, (MethodBase)(method as ConstructorInfo)); } else if (method.IsFinal || !method.IsVirtual || forceNonVirtcall) { Extensions.Emit(iLProcessor, OpCodes.Call, (MethodBase)(method as MethodInfo)); } else { Extensions.Emit(iLProcessor, OpCodes.Callvirt, (MethodBase)(method as MethodInfo)); } Type type2 = (method.IsConstructor ? method.DeclaringType : (method as MethodInfo).ReturnType); if ((object)type2 != typeof(void)) { if (type2.IsValueType) { Extensions.Emit(iLProcessor, OpCodes.Box, type2); } } else { iLProcessor.Emit(OpCodes.Ldnull); } iLProcessor.Emit(OpCodes.Ret); return (FastReflectionDelegate)Extensions.CreateDelegate((MethodBase)val.Generate(), typeof(FastReflectionDelegate)); } public static Func<T, F> CreateFastFieldGetter<T, F>(FieldInfo fieldInfo) { //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) if ((object)fieldInfo == null) { throw new ArgumentNullException("fieldInfo"); } if (!typeof(F).IsAssignableFrom(fieldInfo.FieldType)) { throw new ArgumentException("FieldInfo type does not match return type."); } if ((object)typeof(T) != typeof(object) && ((object)fieldInfo.DeclaringType == null || !fieldInfo.DeclaringType.IsAssignableFrom(typeof(T)))) { throw new MissingFieldException(typeof(T).Name, fieldInfo.Name); } DynamicMethodDefinition val = new DynamicMethodDefinition("FastReflection<" + typeof(T).FullName + ".Get_" + fieldInfo.Name + ">", typeof(F), new Type[1] { typeof(T) }); ILProcessor iLProcessor = val.GetILProcessor(); if (!fieldInfo.IsStatic) { iLProcessor.Emit(OpCodes.Ldarg_0); Extensions.Emit(iLProcessor, OpCodes.Castclass, fieldInfo.DeclaringType); } Extensions.Emit(iLProcessor, fieldInfo.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld, fieldInfo); if (fieldInfo.FieldType.IsValueType != typeof(F).IsValueType) { Extensions.Emit(iLProcessor, OpCodes.Box, fieldInfo.FieldType); } iLProcessor.Emit(OpCodes.Ret); return (Func<T, F>)Extensions.CreateDelegate((MethodBase)val.Generate(), typeof(Func<T, F>)); } public static Action<T, F> CreateFastFieldSetter<T, F>(FieldInfo fieldInfo) { //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) if ((object)fieldInfo == null) { throw new ArgumentNullException("fieldInfo"); } if (!typeof(F).IsAssignableFrom(fieldInfo.FieldType)) { throw new ArgumentException("FieldInfo type does not match argument type."); } if ((object)typeof(T) != typeof(object) && ((object)fieldInfo.DeclaringType == null || !fieldInfo.DeclaringType.IsAssignableFrom(typeof(T)))) { throw new MissingFieldException(typeof(T).Name, fieldInfo.Name); } DynamicMethodDefinition val = new DynamicMethodDefinition("FastReflection<" + typeof(T).FullName + ".Set_" + fieldInfo.Name + ">", (Type)null, new Type[2] { typeof(T), typeof(F) }); ILProcessor iLProcessor = val.GetILProcessor(); if (!fieldInfo.IsStatic) { iLProcessor.Emit(OpCodes.Ldarg_0); Extensions.Emit(iLProcessor, OpCodes.Castclass, fieldInfo.DeclaringType); } iLProcessor.Emit(OpCodes.Ldarg_1); if ((object)fieldInfo.FieldType != typeof(F)) { if (fieldInfo.FieldType.IsValueType != typeof(F).IsValueType) { if (fieldInfo.FieldType.IsValueType) { Extensions.Emit(iLProcessor, OpCodes.Unbox_Any, fieldInfo.FieldType); } else { Extensions.Emit(iLProcessor, OpCodes.Box, fieldInfo.FieldType); } } else { Extensions.Emit(iLProcessor, OpCodes.Castclass, fieldInfo.FieldType); } } Extensions.Emit(iLProcessor, fieldInfo.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, fieldInfo); iLProcessor.Emit(OpCodes.Ret); return (Action<T, F>)Extensions.CreateDelegate((MethodBase)val.Generate(), typeof(Action<T, F>)); } } public static class CustomFastReflectionHelper { private struct FastReflectionDelegateKey { public MethodBase Method { get; } public bool DirectBoxValueAccess { get; } public bool ForceNonVirtCall { get; } public FastReflectionDelegateKey(MethodBase method, bool directBoxValueAccess, bool forceNonVirtCall) { Method = method; DirectBoxValueAccess = directBoxValueAccess; ForceNonVirtCall = forceNonVirtCall; } public override bool Equals(object obj) { if (obj is FastReflectionDelegateKey fastReflectionDelegateKey && EqualityComparer<MethodBase>.Default.Equals(Method, fastReflectionDelegateKey.Method) && DirectBoxValueAccess == fastReflectionDelegateKey.DirectBoxValueAccess) { return ForceNonVirtCall == fastReflectionDelegateKey.ForceNonVirtCall; } return false; } public override int GetHashCode() { return ((1017116076 * -1521134295 + EqualityComparer<MethodBase>.Default.GetHashCode(Method)) * -1521134295 + DirectBoxValueAccess.GetHashCode()) * -1521134295 + ForceNonVirtCall.GetHashCode(); } } private static readonly Dictionary<FastReflectionDelegateKey, FastReflectionDelegate> MethodCache = new Dictionary<FastReflectionDelegateKey, FastReflectionDelegate>(); public static FastReflectionDelegate CreateFastDelegate(this MethodBase method, bool directBoxValueAccess = true, bool forceNonVirtCall = false) { FastReflectionDelegateKey key = new FastReflectionDelegateKey(method, directBoxValueAccess, forceNonVirtCall); if (MethodCache.TryGetValue(key, out var value)) { return value; } value = (((object)ClrTypes.DynamicMethodDefinition == null) ? GetFastDelegateForSRE(method, directBoxValueAccess, forceNonVirtCall) : GetFastDelegateForCecil(method, directBoxValueAccess, forceNonVirtCall)); MethodCache.Add(key, value); return value; } public static Func<T, F> CreateFastFieldGetter<T, F>(FieldInfo fieldInfo) { if ((object)ClrTypes.DynamicMethodDefinition != null) { return CreateFastFieldGetterForCecil<T, F>(fieldInfo); } return CreateFastFieldGetterForSRE<T, F>(fieldInfo); } public static Action<T, F> CreateFastFieldSetter<T, F>(FieldInfo fieldInfo) { if ((object)ClrTypes.DynamicMethodDefinition != null) { return CreateFastFieldSetterForCecil<T, F>(fieldInfo); } return CreateFastFieldSetterForSRE<T, F>(fieldInfo); } private static FastReflectionDelegate GetFastDelegateForCecil(MethodBase method, bool directBoxValueAccess, bool forceNonVirtCall) { try { return CecilFastReflectionHelper.CreateFastDelegate(method, directBoxValueAccess, forceNonVirtCall); } catch (Exception e) { try { XuaLogger.Common.Warn(e, "Failed creating fast reflection delegate through with cecil. Retrying with reflection emit..."); return ReflectionEmitFastReflectionHelper.CreateFastDelegate(method, directBoxValueAccess, forceNonVirtCall); } catch (Exception e2) { XuaLogger.Common.Warn(e2, "Failed creating fast reflection delegate through with reflection emit. Falling back to standard reflection..."); return (object target, object[] args) => method.Invoke(target, args); } } } private static Func<T, F> CreateFastFieldGetterForCecil<T, F>(FieldInfo fieldInfo) { try { return CecilFastReflectionHelper.CreateFastFieldGetter<T, F>(fieldInfo); } catch (Exception e) { try { XuaLogger.Common.Warn(e, "Failed creating fast reflection delegate through with cecil. Retrying with reflection emit..."); return ReflectionEmitFastReflectionHelper.CreateFastFieldGetter<T, F>(fieldInfo); } catch (Exception e2) { XuaLogger.Common.Warn(e2, "Failed creating fast reflection delegate through with reflection emit. Falling back to standard reflection..."); return (T target) => (F)fieldInfo.GetValue(target); } } } private static Action<T, F> CreateFastFieldSetterForCecil<T, F>(FieldInfo fieldInfo) { try { return CecilFastReflectionHelper.CreateFastFieldSetter<T, F>(fieldInfo); } catch (Exception e) { try { XuaLogger.Common.Warn(e, "Failed creating fast reflection delegate through with cecil. Retrying with reflection emit..."); return ReflectionEmitFastReflectionHelper.CreateFastFieldSetter<T, F>(fieldInfo); } catch (Exception e2) { XuaLogger.Common.Warn(e2, "Failed creating fast reflection delegate through with reflection emit. Falling back to standard reflection..."); return delegate(T target, F value) { fieldInfo.SetValue(target, value); }; } } } private static FastReflectionDelegate GetFastDelegateForSRE(MethodBase method, bool directBoxValueAccess, bool forceNonVirtCall) { try { return ReflectionEmitFastReflectionHelper.CreateFastDelegate(method, directBoxValueAccess, forceNonVirtCall); } catch (Exception e) { XuaLogger.Common.Warn(e, "Failed creating fast reflection delegate through with reflection emit. Falling back to standard reflection..."); return (object target, object[] args) => method.Invoke(target, args); } } private static Func<T, F> CreateFastFieldGetterForSRE<T, F>(FieldInfo fieldInfo) { try { return ReflectionEmitFastReflectionHelper.CreateFastFieldGetter<T, F>(fieldInfo); } catch (Exception e) { XuaLogger.Common.Warn(e, "Failed creating fast reflection delegate through with reflection emit. Falling back to standard reflection..."); return (T target) => (F)fieldInfo.GetValue(target); } } private static Action<T, F> CreateFastFieldSetterForSRE<T, F>(FieldInfo fieldInfo) { try { return ReflectionEmitFastReflectionHelper.CreateFastFieldSetter<T, F>(fieldInfo); } catch (Exception e) { XuaLogger.Common.Warn(e, "Failed creating fast reflection delegate through with reflection emit. Falling back to standard reflection..."); return delegate(T target, F value) { fieldInfo.SetValue(target, value); }; } } } public static class DiacriticHelper { public static string RemoveAllDiacritics(this string input) { return new string((from c in input.SafeNormalize(NormalizationForm.FormD) where CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark select c).ToArray()).SafeNormalize(); } private static string SafeNormalize(this string input, NormalizationForm normalizationForm = NormalizationForm.FormC) { return ReplaceNonCharacters(input, '?').Normalize(normalizationForm); } private static string ReplaceNonCharacters(string input, char replacement) { StringBuilder stringBuilder = new StringBuilder(input.Length); for (int i = 0; i < input.Length; i++) { if (char.IsSurrogatePair(input, i)) { int num = char.ConvertToUtf32(input, i); i++; if (IsValidCodePoint(num)) { stringBuilder.Append(char.ConvertFromUtf32(num)); } else { stringBuilder.Append(replacement); } } else { char c = input[i]; if (IsValidCodePoint(c)) { stringBuilder.Append(c); } else { stringBuilder.Append(replacement); } } } return stringBuilder.ToString(); } private static bool IsValidCodePoint(int point) { if (point >= 64976) { if (point >= 65008 && (point & 0xFFFF) != 65535 && (point & 0xFFFE) != 65534) { return point <= 1114111; } return false; } return true; } } public static class ExpressionHelper { public static Delegate CreateTypedFastInvoke(MethodBase method) { if ((object)method == null) { throw new ArgumentNullException("method"); } return CreateTypedFastInvokeUnchecked(method); } public static Delegate CreateTypedFastInvokeUnchecked(MethodBase method) { if ((object)method == null) { return null; } if (method.IsGenericMethod) { throw new ArgumentException("The provided method must not be generic.", "method"); } if (method is MethodInfo methodInfo) { Expression[] arguments; if (method.IsStatic) { ParameterExpression[] array = (from p in methodInfo.GetParameters() select Expression.Parameter(p.ParameterType, p.Name)).ToArray(); arguments = array; return Expression.Lambda(Expression.Call(null, methodInfo, arguments), array).Compile(); } List<ParameterExpression> list = (from p in methodInfo.GetParameters() select Expression.Parameter(p.ParameterType, p.Name)).ToList(); list.Insert(0, Expression.Parameter(methodInfo.DeclaringType, "instance")); ParameterExpression instance = list[0]; arguments = list.Skip(1).ToArray(); return Expression.Lambda(Expression.Call(instance, methodInfo, arguments), list.ToArray()).Compile(); } if (method is ConstructorInfo constructorInfo) { ParameterExpression[] array2 = (from p in constructorInfo.GetParameters() select Expression.Parameter(p.ParameterType, p.Name)).ToArray(); Expression[] arguments = array2; return Expression.Lambda(Expression.New(constructorInfo, arguments), array2).Compile(); } throw new ArgumentException("method", "This method only supports MethodInfo and ConstructorInfo."); } } public static class ExtensionDataHelper { private static readonly object Sync; private static readonly WeakDictionary<object, object> WeakDynamicFields; public static int WeakReferenceCount { get { lock (Sync) { return WeakDynamicFields.Count; } } } static ExtensionDataHelper() { Sync = new object(); WeakDynamicFields = new WeakDictionary<object, object>(); MaintenanceHelper.AddMaintenanceFunction(Cull, 12); } public static void SetExtensionData<T>(this object obj, T t) { lock (Sync) { if (WeakDynamicFields.TryGetValue(obj, out var value)) { if (value is Dictionary<Type, object> dictionary) { dictionary[typeof(T)] = t; return; } Dictionary<Type, object> dictionary2 = new Dictionary<Type, object>(); dictionary2.Add(value.GetType(), value); dictionary2[typeof(T)] = t; WeakDynamicFields[obj] = dictionary2; } else { WeakDynamicFields[obj] = t; } } } public static T GetOrCreateExtensionData<T>(this object obj) where T : new() { if (obj == null) { return default(T); } lock (Sync) { if (WeakDynamicFields.TryGetValue(obj, out var value)) { if (value is Dictionary<Type, object> dictionary) { if (dictionary.TryGetValue(typeof(T), out value)) { return (T)value; } T val = new T(); dictionary[typeof(T)] = val; return val; } if (!(value is T result)) { Dictionary<Type, object> dictionary2 = new Dictionary<Type, object>(); dictionary2.Add(value.GetType(), value); T val2 = new T(); dictionary2[typeof(T)] = val2; WeakDynamicFields[obj] = dictionary2; return val2; } return result; } T val3 = new T(); WeakDynamicFields[obj] = val3; return val3; } } public static T GetExtensionData<T>(this object obj) { if (obj == null) { return default(T); } lock (Sync) { if (WeakDynamicFields.TryGetValue(obj, out var value)) { if (value is Dictionary<Type, object> dictionary && dictionary.TryGetValue(typeof(T), out value)) { if (!(value is T result)) { return default(T); } return result; } if (!(value is T result2)) { return default(T); } return result2; } } return default(T); } public static void Cull() { lock (Sync) { WeakDynamicFields.RemoveCollectedEntries(); } } public static List<KeyValuePair<object, object>> GetAllRegisteredObjects() { lock (Sync) { return IterateAllPairs().ToList(); } } public static void Remove(object obj) { lock (Sync) { WeakDynamicFields.Remove(obj); } } private static IEnumerable<KeyValuePair<object, object>> IterateAllPairs() { foreach (KeyValuePair<object, object> kvp in WeakDynamicFields) { if (kvp.Value is Dictionary<Type, object> dictionary) { foreach (KeyValuePair<Type, object> item in dictionary) { yield return new KeyValuePair<object, object>(kvp.Key, item.Value); } } else { yield return kvp; } } } } public delegate object FastReflectionDelegate(object target, params object[] args); public static class HookingHelper { private static readonly MethodInfo PatchMethod12; private static readonly MethodInfo PatchMethod20; private static readonly object Harmony; private static bool _loggedHarmonyError; static HookingHelper() { PatchMethod12 = ClrTypes.HarmonyInstance?.GetMethod("Patch", new Type[4] { ClrTypes.MethodBase, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod }); PatchMethod20 = ClrTypes.Harmony?.GetMethod("Patch", new Type[5] { ClrTypes.MethodBase, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod, ClrTypes.HarmonyMethod }); _loggedHarmonyError = false; try { if ((object)ClrTypes.HarmonyInstance != null) { Harmony = ClrTypes.HarmonyInstance.GetMethod("Create", BindingFlags.Static | BindingFlags.Public).Invoke(null, new object[1] { "xunity.common.hookinghelper" }); } else if ((object)ClrTypes.Harmony != null) { Harmony = ClrTypes.Harmony.GetConstructor(new Type[1] { typeof(string) }).Invoke(new object[1] { "xunity.common.hookinghelper" }); } else { XuaLogger.Common.Error("An unexpected exception occurred during harmony initialization, likely caused by unknown Harmony version. Harmony hooks will be unavailable!"); } } catch (Exception e) { XuaLogger.Common.Error(e, "An unexpected exception occurred during harmony initialization. Harmony hooks will be unavailable!"); } } public static void PatchAll(IEnumerable<Type> types, bool forceExternHooks) { foreach (Type type in types) { PatchType(type, forceExternHooks); } } public static void PatchAll(IEnumerable<Type[]> types, bool forceMonoModHooks) { foreach (Type[] type in types) { for (int i = 0; i < type.Length && !PatchType(type[i], forceMonoModHooks); i++) { } } } public static bool PatchType(Type type, bool forceExternHooks) { MethodBase methodBase = null; IntPtr intPtr = IntPtr.Zero; try { if (Harmony == null && !_loggedHarmonyError) { _loggedHarmonyError = true; XuaLogger.Common.Warn("Harmony is not loaded or could not be initialized. Using fallback hooks instead."); } BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; MethodInfo method = type.GetMethod("Prepare", bindingAttr); if ((object)method == null || (bool)method.Invoke(null, new object[1] { Harmony })) { try { methodBase = (MethodBase)(type.GetMethod("TargetMethod", bindingAttr)?.Invoke(null, new object[1] { Harmony })); } catch { } try { intPtr = ((IntPtr?)type.GetMethod("TargetMethodPointer", bindingAttr)?.Invoke(null, null)) ?? IntPtr.Zero; } catch { } if ((object)methodBase == null && intPtr == IntPtr.Zero) { if ((object)methodBase != null) { XuaLogger.Common.Warn("Could not hook '" + methodBase.DeclaringType.FullName + "." + methodBase.Name + "'. Likely due differences between different versions of the engine or text framework."); } else { XuaLogger.Common.Warn("Could not hook '" + type.Name + "'. Likely due differences between different versions of the engine or text framework."); } return false; } MethodInfo method2 = type.GetMethod("Prefix", bindingAttr); MethodInfo method3 = type.GetMethod("Postfix", bindingAttr); MethodInfo method4 = type.GetMethod("Finalizer", bindingAttr); if ((object)methodBase == null || forceExternHooks || Harmony == null || ((object)method2 == null && (object)method3 == null && (object)method4 == null)) { return PatchWithExternHooks(type, methodBase, intPtr, forced: true); } if ((object)methodBase != null) { try { int? priority = type.GetCustomAttributes(typeof(HookingHelperPriorityAttribute), inherit: false).OfType<HookingHelperPriorityAttribute>().FirstOrDefault()?.priority; object obj3 = (((object)method2 != null) ? CreateHarmonyMethod(method2, priority) : null); object obj4 = (((object)method3 != null) ? CreateHarmonyMethod(method3, priority) : null); object obj5 = (((object)method4 != null) ? CreateHarmonyMethod(method4, priority) : null); if ((object)PatchMethod12 != null) { PatchMethod12.Invoke(Harmony, new object[4] { methodBase, obj3, obj4, null }); } else { PatchMethod20.Invoke(Harmony, new object[5] { methodBase, obj3, obj4, null, obj5 }); } XuaLogger.Common.Debug("Hooked " + methodBase.DeclaringType.FullName + "." + methodBase.Name + " through Harmony hooks."); return true; } catch (Exception e) when (((Func<bool>)delegate { // Could not convert BlockContainer to single expression System.Runtime.CompilerServices.Unsafe.SkipInit(out int num); if (e.FirstInnerExceptionOfType<PlatformNotSupportedException>() == null) { ArgumentException ex = e.FirstInnerExceptionOfType<ArgumentException>(); num = ((ex != null && ex.Message?.Contains("no body") == true) ? 1 : 0); } else { num = 1; } return num != 0; }).Invoke()) { return PatchWithExternHooks(type, methodBase, intPtr, forced: false); } } XuaLogger.Common.Warn("Could not hook '" + type.Name + "'. Likely due differences between different versions of the engine or text framework."); } } catch (Exception e2) { if ((object)methodBase != null) { XuaLogger.Common.Warn(e2, "An error occurred while patching property/method '" + methodBase.DeclaringType.FullName + "." + methodBase.Name + "'. Failing hook: '" + type.Name + "'."); } else { XuaLogger.Common.Warn(e2, "An error occurred while patching property/method. Failing hook: '" + type.Name + "'."); } } return false; } private static bool PatchWithExternHooks(Type type, MethodBase original, IntPtr originalPtr, bool forced) { BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; if ((object)ClrTypes.Imports != null) { if (originalPtr == IntPtr.Zero) { XuaLogger.Common.Warn("Could not hook '" + type.Name + "'. Likely due differences between different versions of the engine or text framework."); return false; } IntPtr? intPtr = type.GetMethod("ML_Detour", bindingAttr)?.MethodHandle.GetFunctionPointer(); if (intPtr.HasValue && intPtr.Value != IntPtr.Zero) { ClrTypes.Imports.GetMethod("Hook", bindingAttr).Invoke(null, new object[2] { originalPtr, intPtr.Value }); XuaLogger.Common.Debug("Hooked " + type.Name + " through MelonMod Imports.Hook method."); return true; } XuaLogger.Common.Warn("Could not hook '" + type.Name + "' because no detour method was found."); } else { if ((object)original == null) { XuaLogger.Common.Warn("Cannot hook '" + type.Name + "'. Could not locate the original method. Failing hook: '" + type.Name + "'."); return false; } if ((object)ClrTypes.Hook == null || (object)ClrTypes.NativeDetour == null) { XuaLogger.Common.Warn("Cannot hook '" + original.DeclaringType.FullName + "." + original.Name + "'. MonoMod hooks is not supported in this runtime as MonoMod is not loaded. Failing hook: '" + type.Name + "'."); return false; } object obj = type.GetMethod("Get_MM_Detour", bindingAttr)?.Invoke(null, null) ?? type.GetMethod("MM_Detour", bindingAttr); if (obj != null) { string text = "(managed)"; object obj2; try { obj2 = ClrTypes.Hook.GetConstructor(new Type[2] { typeof(MethodBase), typeof(MethodInfo) }).Invoke(new object[2] { original, obj }); obj2.GetType().GetMethod("Apply").Invoke(obj2, null); } catch (Exception e) when (((Func<bool>)delegate { // Could not convert BlockContainer to single expression System.Runtime.CompilerServices.Unsafe.SkipInit(out int num); if (e.FirstInnerExceptionOfType<NullReferenceException>() == null) { NotSupportedException ex = e.FirstInnerExceptionOfType<NotSupportedException>(); num = ((ex != null && ex.Message?.Contains("Body-less") == true) ? 1 : 0); } else { num = 1; } return num != 0; }).Invoke()) { text = "(native)"; obj2 = ClrTypes.NativeDetour.GetConstructor(new Type[2] { typeof(MethodBase), typeof(MethodBase) }).Invoke(new object[2] { original, obj }); obj2.GetType().GetMethod("Apply").Invoke(obj2, null); } type.GetMethod("MM_Init", bindingAttr)?.Invoke(null, new object[1] { obj2 }); if (forced) { XuaLogger.Common.Debug("Hooked " + original.DeclaringType.FullName + "." + original.Name + " through forced MonoMod hooks. " + text); } else { XuaLogger.Common.Debug("Hooked " + original.DeclaringType.FullName + "." + original.Name + " through MonoMod hooks. " + text); } return true; } if (forced) { XuaLogger.Common.Warn("Cannot hook '" + original.DeclaringType.FullName + "." + original.Name + "'. Harmony is not supported in this runtime and no alternate MonoMod hook has been implemented. Failing hook: '" + type.Name + "'."); } else { XuaLogger.Common.Warn("Cannot hook '" + original.DeclaringType.FullName + "." + original.Name + "'. Harmony is not supported in this runtime and no alternate MonoMod hook has been implemented. Failing hook: '" + type.Name + "'."); } } return false; } private static object CreateHarmonyMethod(MethodInfo method, int? priority) { object obj = ClrTypes.HarmonyMethod.GetConstructor(new Type[1] { typeof(MethodInfo) }).Invoke(new object[1] { method }); if (priority.HasValue) { (ClrTypes.HarmonyMethod.GetField("priority", BindingFlags.Instance | BindingFlags.Public) ?? ClrTypes.HarmonyMethod.GetField("prioritiy", BindingFlags.Instance | BindingFlags.Public)).SetValue(obj, priority.Value); } return obj; } } public class HookingHelperPriorityAttribute : Attribute { public int priority; public HookingHelperPriorityAttribute(int priority) { this.priority = priority; } } public static class HookPriority { public const int Last = 0; public const int VeryLow = 100; public const int Low = 200; public const int LowerThanNormal = 300; public const int Normal = 400; public const int HigherThanNormal = 500; public const int High = 600; public const int VeryHigh = 700; public const int First = 800; } public static class ListExtensions { public static void BinarySearchInsert<T>(this List<T> items, T item) where T : IComparable<T> { int num = items.BinarySearch(item); if (num < 0) { items.Insert(~num, item); } else { items.Insert(num, item); } } } public static class MaintenanceHelper { private class ActionRegistration { public Action Action { get; } public int Filter { get; } public ActionRegistration(Action action, int filter) { Action = action; Filter = filter; } } private static readonly object Sync = new object(); private static readonly List<ActionRegistration> RegisteredActions = new List<ActionRegistration>(); private static bool _initialized; public static void AddMaintenanceFunction(Action action, int filter) { lock (Sync) { if (!_initialized) { _initialized = true; StartMaintenance(); } ActionRegistration item = new ActionRegistration(action, filter); RegisteredActions.Add(item); } } private static void StartMaintenance() { Thread thread = new Thread(MaintenanceLoop); thread.IsBackground = true; thread.Start(); } private static void MaintenanceLoop(object state) { int num = 0; while (true) { lock (Sync) { foreach (ActionRegistration registeredAction in RegisteredActions) { if (num % registeredAction.Filter == 0) { try { registeredAction.Action(); } catch (Exception e) { XuaLogger.Common.Error(e, "An unexpected error occurred during maintenance."); } } } } num++; Thread.Sleep(5000); } } } public static class Paths { private static string _gameRoot; public static string GameRoot { get { return _gameRoot ?? GetAndSetGameRoot(); } set { _gameRoot = value; } } public static void Initialize() { GetAndSetGameRoot(); } private static string GetAndSetGameRoot() { return _gameRoot = new DirectoryInfo(Application.dataPath).Parent.FullName; } } public static class ReflectionCache { private struct MemberLookupKey { public Type Type { get; set; } public string MemberName { get; set; } public MemberLookupKey(Type type, string memberName) { Type = type; MemberName = memberName; } public override bool Equals(object obj) { if (obj is MemberLookupKey memberLookupKey) { if ((object)Type == memberLookupKey.Type) { return MemberName == memberLookupKey.MemberName; } return false; } return false; } public override int GetHashCode() { return Type.GetHashCode() + MemberName.GetHashCode(); } } private static Dictionary<MemberLookupKey, CachedMethod> Methods = new Dictionary<MemberLookupKey, CachedMethod>(); private static Dictionary<MemberLookupKey, CachedProperty> Properties = new Dictionary<MemberLookupKey, CachedProperty>(); private static Dictionary<MemberLookupKey, CachedField> Fields = new Dictionary<MemberLookupKey, CachedField>(); public static CachedMethod CachedMethod(this Type type, string name) { return type.CachedMethod(name, (Type[])null); } public static CachedMethod CachedMethod(this Type type, string name, params Type[] types) { MemberLookupKey key = new MemberLookupKey(type, name); if (!Methods.TryGetValue(key, out var value)) { Type type2 = type; MethodInfo methodInfo = null; while ((object)methodInfo == null && (object)type2 != null) { methodInfo = ((types != null && types.Length != 0) ? type2.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, types, null) : type2.GetMethod(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); type2 = type2.BaseType; } if ((object)methodInfo != null) { value = new CachedMethod(methodInfo); } Methods[key] = value; } return value; } public static CachedProperty CachedProperty(this Type type, string name) { MemberLookupKey key = new MemberLookupKey(type, name); if (!Properties.TryGetValue(key, out var value)) { Type type2 = type; PropertyInfo propertyInfo = null; while ((object)propertyInfo == null && (object)type2 != null) { propertyInfo = type2.GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); type2 = type2.BaseType; } if ((object)propertyInfo != null) { value = new CachedProperty(propertyInfo); } Properties[key] = value; } return value; } public static CachedField CachedField(this Type type, string name) { MemberLookupKey key = new MemberLookupKey(type, name); if (!Fields.TryGetValue(key, out var value)) { Type type2 = type; FieldInfo fieldInfo = null; while ((object)fieldInfo == null && (object)type2 != null) { fieldInfo = type2.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); type2 = type2.BaseType; } if ((object)fieldInfo != null) { value = new CachedField(fieldInfo); } Fields[key] = value; } return value; } public static CachedField CachedFieldByIndex(this Type type, int index, Type fieldType, BindingFlags flags) { FieldInfo[] array = (from x in type.GetFields(flags) where (object)x.FieldType == fieldType select x).ToArray(); if (index < array.Length) { return new CachedField(array[index]); } return null; } } public class CachedMethod { private static readonly object[] Args0 = new object[0]; private static readonly object[] Args1 = new object[1]; private static readonly object[] Args2 = new object[2]; private FastReflectionDelegate _invoke; internal CachedMethod(MethodInfo method) { _invoke = method.CreateFastDelegate(); } public object Invoke(object instance, object[] arguments) { return _invoke(instance, arguments); } public object Invoke(object instance) { return _invoke(instance, Args0); } public object Invoke(object instance, object arg1) { try { Args1[0] = arg1; return _invoke(instance, Args1); } finally { Args1[0] = null; } } public object Invoke(object instance, object arg1, object arg2) { try { Args2[0] = arg1; Args2[1] = arg2; return _invoke(instance, Args2); } finally { Args2[0] = null; Args2[1] = null; } } } public class CachedProperty { private static readonly object[] Args0 = new object[0]; private static readonly object[] Args1 = new object[1]; private FastReflectionDelegate _set; private FastReflectionDelegate _get; public Type PropertyType { get; } internal CachedProperty(PropertyInfo propertyInfo) { if (propertyInfo.CanRead) { _get = propertyInfo.GetGetMethod(nonPublic: true).CreateFastDelegate(); } if (propertyInfo.CanWrite) { _set = propertyInfo.GetSetMethod(nonPublic: true).CreateFastDelegate(); } PropertyType = propertyInfo.PropertyType; } public void Set(object instance, object[] arguments) { if (_set != null) { _set(instance, arguments); } } public void Set(object instance, object arg1) { if (_set == null) { return; } try { Args1[0] = arg1; _set(instance, Args1); } finally { Args1[0] = null; } } public object Get(object instance, object[] arguments) { if (_get == null) { return null; } return _get(instance, arguments); } public object Get(object instance) { if (_get == null) { return null; } return _get(instance, Args0); } } public class CachedField { private Func<object, object> _get; private Action<object, object> _set; public Type FieldType { get; } internal CachedField(FieldInfo fieldInfo) { _get = CustomFastReflectionHelper.CreateFastFieldGetter<object, object>(fieldInfo); _set = CustomFastReflectionHelper.CreateFastFieldSetter<object, object>(fieldInfo); FieldType = fieldInfo.FieldType; } public void Set(object instance, object value) { if (_set != null) { _set(instance, value); } } public object Get(object instance) { if (_get == null) { return null; } return _get(instance); } } internal static class ReflectionEmitFastReflectionHelper { private static readonly Type[] DynamicMethodDelegateArgs = new Type[2] { typeof(object), typeof(object[]) }; public static FastReflectionDelegate CreateFastDelegate(MethodBase method, bool directBoxValueAccess, bool forceNonVirtcall) { DynamicMethod dynamicMethod = new DynamicMethod("FastReflection<" + method.DeclaringType.FullName + "." + method.Name + ">", typeof(object), DynamicMethodDelegateArgs, method.DeclaringType.Module, skipVisibility: true); ILGenerator iLGenerator = dynamicMethod.GetILGenerator(); ParameterInfo[] parameters = method.GetParameters(); bool flag = true; if (!method.IsStatic) { iLGenerator.Emit(OpCodes.Ldarg_0); if (method.DeclaringType.IsValueType) { iLGenerator.Emit(OpCodes.Unbox_Any, method.DeclaringType); } } for (int i = 0; i < parameters.Length; i++) { Type type = parameters[i].ParameterType; bool isByRef = type.IsByRef; if (isByRef) { type = type.GetElementType(); } bool isValueType = type.IsValueType; if (isByRef && isValueType && !directBoxValueAccess) { iLGenerator.Emit(OpCodes.Ldarg_1); iLGenerator.Emit(OpCodes.Ldc_I4, i); } iLGenerator.Emit(OpCodes.Ldarg_1); iLGenerator.Emit(OpCodes.Ldc_I4, i); if (isByRef && !isValueType) { iLGenerator.Emit(OpCodes.Ldelema, typeof(object)); continue; } iLGenerator.Emit(OpCodes.Ldelem_Ref); if (!isValueType) { continue; } if (!isByRef || !directBoxValueAccess) { iLGenerator.Emit(OpCodes.Unbox_Any, type); if (isByRef) { iLGenerator.Emit(OpCodes.Box, type); iLGenerator.Emit(OpCodes.Dup); iLGenerator.Emit(OpCodes.Unbox, type); if (flag) { flag = false; throw new NotImplementedException("No idea how to implement this..."); } iLGenerator.Emit(OpCodes.Stloc_0); iLGenerator.Emit(OpCodes.Stelem_Ref); iLGenerator.Emit(OpCodes.Ldloc_0); } } else { iLGenerator.Emit(OpCodes.Unbox, type); } } if (method.IsConstructor) { iLGenerator.Emit(OpCodes.Newobj, method as ConstructorInfo); } else if (method.IsFinal || !method.IsVirtual || forceNonVirtcall) { iLGenerator.Emit(OpCodes.Call, method as MethodInfo); } else { iLGenerator.Emit(OpCodes.Callvirt, method as MethodInfo); } Type type2 = (method.IsConstructor ? method.DeclaringType : (method as MethodInfo).ReturnType); if ((object)type2 != typeof(void)) { if (type2.IsValueType) { iLGenerator.Emit(OpCodes.Box, type2); } } else { iLGenerator.Emit(OpCodes.Ldnull); } iLGenerator.Emit(OpCodes.Ret); return (FastReflectionDelegate)dynamicMethod.CreateDelegate(typeof(FastReflectionDelegate)); } public static Func<T, F> CreateFastFieldGetter<T, F>(FieldInfo fieldInfo) { if ((object)fieldInfo == null) { throw new ArgumentNullException("fieldInfo"); } if (!typeof(F).IsAssignableFrom(fieldInfo.FieldType)) { throw new ArgumentException("FieldInfo type does not match return type."); } if ((object)typeof(T) != typeof(object) && ((object)fieldInfo.DeclaringType == null || !fieldInfo.DeclaringType.IsAssignableFrom(typeof(T)))) { throw new MissingFieldException(typeof(T).Name, fieldInfo.Name); } DynamicMethod dynamicMethod = new DynamicMethod("FastReflection<" + typeof(T).FullName + ".Get_" + fieldInfo.Name + ">", typeof(F), new Type[1] { typeof(T) }, fieldInfo.DeclaringType.Module, skipVisibility: true); ILGenerator iLGenerator = dynamicMethod.GetILGenerator(); if (!fieldInfo.IsStatic) { iLGenerator.Emit(OpCodes.Ldarg_0); iLGenerator.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); } iLGenerator.Emit(fieldInfo.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld, fieldInfo); if (fieldInfo.FieldType.IsValueType != typeof(F).IsValueType) { iLGenerator.Emit(OpCodes.Box, fieldInfo.FieldType); } iLGenerator.Emit(OpCodes.Ret); return (Func<T, F>)dynamicMethod.CreateDelegate(typeof(Func<T, F>)); } public static Action<T, F> CreateFastFieldSetter<T, F>(FieldInfo fieldInfo) { if ((object)fieldInfo == null) { throw new ArgumentNullException("fieldInfo"); } if (!typeof(F).IsAssignableFrom(fieldInfo.FieldType)) { throw new ArgumentException("FieldInfo type does not match argument type."); } if ((object)typeof(T) != typeof(object) && ((object)fieldInfo.DeclaringType == null || !fieldInfo.DeclaringType.IsAssignableFrom(typeof(T)))) { throw new MissingFieldException(typeof(T).Name, fieldInfo.Name); } DynamicMethod dynamicMethod = new DynamicMethod("FastReflection<" + typeof(T).FullName + ".Set_" + fieldInfo.Name + ">", null, new Type[2] { typeof(T), typeof(F) }, fieldInfo.DeclaringType.Module, skipVisibility: true); ILGenerator iLGenerator = dynamicMethod.GetILGenerator(); if (!fieldInfo.IsStatic) { iLGenerator.Emit(OpCodes.Ldarg_0); iLGenerator.Emit(OpCodes.Castclass, fieldInfo.DeclaringType); } iLGenerator.Emit(OpCodes.Ldarg_1); if ((object)fieldInfo.FieldType != typeof(F)) { if (fieldInfo.FieldType.IsValueType != typeof(F).IsValueType) { if (fieldInfo.FieldType.IsValueType) { iLGenerator.Emit(OpCodes.Unbox, fieldInfo.FieldType); } else { iLGenerator.Emit(OpCodes.Box, fieldInfo.FieldType); } } else { iLGenerator.Emit(OpCodes.Castclass, fieldInfo.FieldType); } } iLGenerator.Emit(fieldInfo.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, fieldInfo); iLGenerator.Emit(OpCodes.Ret); return (Action<T, F>)dynamicMethod.CreateDelegate(typeof(Action<T, F>)); } } public static class TimeHelper { public static float realtimeSinceStartup => Time.realtimeSinceStartup; } public class UnityObjectReferenceComparer : IEqualityComparer<object> { public static readonly UnityObjectReferenceComparer Default = new UnityObjectReferenceComparer(); public new bool Equals(object x, object y) { return x == y; } public int GetHashCode(object obj) { return obj.GetHashCode(); } } public class WeakReference<T> : WeakReference where T : class { public new T Target => (T)base.Target; public static WeakReference<T> Create(T target) { if (target == null) { return WeakNullReference<T>.Singleton; } return new WeakReference<T>(target); } protected WeakReference(T target) : base(target, trackResurrection: false) { } } internal class WeakNullReference<T> : WeakReference<T> where T : class { public static readonly WeakNullReference<T> Singleton = new WeakNullReference<T>(); public override bool IsAlive => true; private WeakNullReference() : base((T)null) { } } internal sealed class WeakKeyReference<T> : WeakReference<T> where T : class { public readonly int HashCode; public WeakKeyReference(T key, WeakKeyComparer<T> comparer) : base(key) { HashCode = comparer.GetHashCode(key); } } internal sealed class WeakKeyComparer<T> : IEqualityComparer<object> where T : class { private IEqualityComparer<T> comparer; internal WeakKeyComparer(IEqualityComparer<T> comparer) { if (comparer == null) { comparer = EqualityComparer<T>.Default; } this.comparer = comparer; } public int GetHashCode(object obj) { if (obj is WeakKeyReference<T> weakKeyReference) { return weakKeyReference.HashCode; } return comparer.GetHashCode((T)obj); } public new bool Equals(object x, object y) { bool isDead; T target = GetTarget(x, out isDead); bool isDead2; T target2 = GetTarget(y, out isDead2); if (isDead) { if (!isDead2) { return false; } return x == y; } if (isDead2) { return false; } return comparer.Equals(target, target2); } private static T GetTarget(object obj, out bool isDead) { T result; if (obj is WeakKeyReference<T> weakKeyReference) { result = weakKeyReference.Target; isDead = !weakKeyReference.IsAlive; } else { result = (T)obj; isDead = false; } return result; } } public sealed class WeakDictionary<TKey, TValue> : BaseDictionary<TKey, TValue> where TKey : class { private Dictionary<object, TValue> dictionary; private WeakKeyComparer<TKey> comparer; public override int Count => dictionary.Count; public WeakDictionary() : this(0, (IEqualityComparer<TKey>)null) { } public WeakDictionary(int capacity) : this(capacity, (IEqualityComparer<TKey>)null) { } public WeakDictionary(IEqualityComparer<TKey> comparer) : this(0, comparer) { } public WeakDictionary(int capacity, IEqualityComparer<TKey> comparer) { this.comparer = new WeakKeyComparer<TKey>(comparer); dictionary = new Dictionary<object, TValue>(capacity, this.comparer); } public override void Add(TKey key, TValue value) { if (key == null) { throw new ArgumentNullException("key"); } WeakReference<TKey> key2 = new WeakKeyReference<TKey>(key, comparer); dictionary.Add(key2, value); } public override bool ContainsKey(TKey key) { return dictionary.ContainsKey(key); } public override bool Remove(TKey key) { return dictionary.Remove(key); } public override bool TryGetValue(TKey key, out TValue value) { if (dictionary.TryGetValue(key, out value)) { return true; } value = default(TValue); return false; } protected override void SetValue(TKey key, TValue value) { WeakReference<TKey> key2 = new WeakKeyReference<TKey>(key, comparer); dictionary[key2] = value; } public override void Clear() { dictionary.Clear(); } public override IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { foreach (KeyValuePair<object, TValue> item in dictionary) { WeakReference<TKey> obj = (WeakReference<TKey>)item.Key; TValue value = item.Value; TKey target = obj.Target; if (obj.IsAlive) { yield return new KeyValuePair<TKey, TValue>(target, value); } } } public void RemoveCollectedEntries() { List<object> list = null; foreach (KeyValuePair<object, TValue> item in dictionary) { WeakReference<TKey> weakReference = (WeakReference<TKey>)item.Key; if (!weakReference.IsAlive) { if (list == null) { list = new List<object>(); } list.Add(weakReference); } } if (list == null) { return; } foreach (object item2 in list) { dictionary.Remove(item2); } } } [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy("System.Collections.Generic.Mscorlib_DictionaryDebugView`2,mscorlib,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089")] public abstract class BaseDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable { private abstract class Collection<T> : ICollection<T>, IEnumerable<T>, IEnumerable { protected readonly IDictionary<TKey, TValue> dictionary; public int Count => dictionary.Count; public bool IsReadOnly => true; protected Collection(IDictionary<TKey, TValue> dictionary) { this.dictionary = dictionary; } public void CopyTo(T[] array, int arrayIndex) { BaseDictionary<TKey, TValue>.Copy((ICollection<T>)this, array, arrayIndex); } public virtual bool Contains(T item) { using (IEnumerator<T> enumerator = GetEnumerator()) { while (enumerator.MoveNext()) { T current = enumerator.Current; if (EqualityComparer<T>.Default.Equals(current, item)) { return true; } } } return false; } public IEnumerator<T> GetEnumerator() { foreach (KeyValuePair<TKey, TValue> item in dictionary) { yield return GetItem(item); } } protected abstract T GetItem(KeyValuePair<TKey, TValue> pair); public bool Remove(T item) { throw new NotSupportedException("Collection is read-only."); } public void Add(T item) { throw new NotSupportedException("Collection is read-only."); } public void Clear() { throw new NotSupportedException("Collection is read-only."); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy("System.Collections.Generic.Mscorlib_DictionaryKeyCollectionDebugView`2,mscorlib,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089")] private class KeyCollection : Collection<TKey> { public KeyCollection(IDictionary<TKey, TValue> dictionary) : base(dictionary) { } protected override TKey GetItem(KeyValuePair<TKey, TValue> pair) { return pair.Key; } public override bool Contains(TKey item) { return dictionary.ContainsKey(item); } } [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy("System.Collections.Generic.Mscorlib_DictionaryValueCollectionDebugView`2,mscorlib,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089")] private class ValueCollection : Collection<TValue> { public ValueCollection(IDictionary<TKey, TValue> dictionary) : base(dictionary) { } protected override TValue GetItem(KeyValuePair<TKey, TValue> pair) { return pair.Value; } } private const string PREFIX = "System.Collections.Generic.Mscorlib_"; private const string SUFFIX = ",mscorlib,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089"; private KeyCollection keys; private ValueCollection values; public abstract int Count { get; } public bool IsReadOnly => false; public ICollection<TKey> Keys { get { if (keys == null) { keys = new KeyCollection(this); } return keys; } } public ICollection<TValue> Values { get { if (values == null) { values = new ValueCollection(this); } return values; } } public TValue this[TKey key] { get { if (!TryGetValue(key, out var value)) { throw new KeyNotFoundException(); } return value; } set { SetValue(key, value); } } public abstract void Clear(); public abstract void Add(TKey key, TValue value); public abstract bool ContainsKey(TKey key); public abstract bool Remove(TKey key); public abstract bool TryGetValue(TKey key, out TValue value); public abstract IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator(); protected abstract void SetValue(TKey key, TValue value); public void Add(KeyValuePair<TKey, TValue> item) { Add(item.Key, item.Value); } public bool Contains(KeyValuePair<TKey, TValue> item) { if (!TryGetValue(item.Key, out var value)) { return false; } return EqualityComparer<TValue>.Default.Equals(value, item.Value); } public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { Copy(this, array, arrayIndex); } public bool Remove(KeyValuePair<TKey, TValue> item) { if (!Contains(item)) { return false; } return Remove(item.Key); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private static void Copy<T>(ICollection<T> source, T[] array, int arrayIndex) { if (array == null) { throw new ArgumentNullException("array"); } if (arrayIndex < 0 || arrayIndex > array.Length) { throw new ArgumentOutOfRangeException("arrayIndex"); } if (array.Length - arrayIndex < source.Count) { throw new ArgumentException("Destination array is not large enough. Check array.Length and arrayIndex."); } foreach (T item in source) { array[arrayIndex++] = item; } } } } namespace XUnity.Common.MonoMod { public static class DetourExtensions { public static T GenerateTrampolineEx<T>(this object detour) { return (T)(from x in detour.GetType().GetMethods() where x.Name == "GenerateTrampoline" && x.IsGenericMethod select x).FirstOrDefault().MakeGenericMethod(typeof(T)).Invoke(detour, null); } } } namespace XUnity.Common.Logging { internal class ConsoleLogger : XuaLogger { public ConsoleLogger(string source) : base(source) { } protected override void Log(LogLevel level, string message) { Console.WriteLine(GetDefaultPrefix(level) + " " + message); } } public enum LogLevel { Debug, Info, Warn, Error } internal class ModLoaderSpecificLogger : XuaLogger { public static class BepInExLogLevel { public const int None = 0; public const int Fatal = 1; public const int Error = 2; public const int Warning = 4; public const int Message = 8; public const int Info = 16; public const int Debug = 32; public const int All = 63; } private static Action<LogLevel, string> _logMethod; public ModLoaderSpecificLogger(string source) : base(source) { if (_logMethod != null) { return; } BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public; BindingFlags bindingAttr2 = BindingFlags.Instance | BindingFlags.Public; Type type = Type.GetType("BepInEx.Logging.LogLevel, BepInEx", throwOnError: false) ?? Type.GetType("BepInEx.Logging.LogLevel, BepInEx.Core", throwOnError: false); if ((object)type != null) { if ((object)(Type.GetType("BepInEx.Logging.ManualLogSource, BepInEx", throwOnError: false) ?? Type.GetType("BepInEx.Logging.ManualLogSource, BepInEx.Core", throwOnError: false)) != null) { MethodInfo method = (Type.GetType("BepInEx.Logging.Logger, BepInEx", throwOnError: false) ?? Type.GetType("BepInEx.Logging.Logger, BepInEx.Core", throwOnError: false)).GetMethod("CreateLogSource", bindingAttr, null, new Type[1] { typeof(string) }, null); object logInstance2 = method.Invoke(null, new object[1] { base.Source }); MethodInfo method2 = logInstance2.GetType().GetMethod("Log", bindingAttr2, null, new Type[2] { type, typeof(object) }, null); FastReflectionDelegate log2 = method2.CreateFastDelegate(); _logMethod = delegate(LogLevel level, string msg) { int num2 = Convert(level); log2(logInstance2, num2, msg); }; } else { Type type2 = Type.GetType("BepInEx.Logger, BepInEx", throwOnError: false); object logInstance = type2.GetProperty("CurrentLogger", bindingAttr).GetValue(null, null); MethodInfo method3 = logInstance.GetType().GetMethod("Log", bindingAttr2, null, new Type[2] { type, typeof(object) }, null); FastReflectionDelegate log = method3.CreateFastDelegate(); _logMethod = delegate(LogLevel level, string msg) { int num = Convert(level); log(logInstance, num, msg); }; } } else { Type type3 = Type.GetType("MelonLoader.MelonLogger, MelonLoader.ModHandler", throwOnError: false); if ((object)type3 != null) { MethodInfo method4 = type3.GetMethod("Log", bindingAttr, null, new Type[2] { typeof(ConsoleColor), typeof(string) }, null); MethodInfo method5 = type3.GetMethod("Log", bindingAttr, null, new Type[1] { typeof(string) }, null); MethodInfo method6 = type3.GetMethod("LogWarning", bindingAttr, null, new Type[1] { typeof(string) }, null); MethodInfo method7 = type3.GetMethod("LogError", bindingAttr, null, new Type[1] { typeof(string) }, null); FastReflectionDelegate logDebug = method4.CreateFastDelegate(); FastReflectionDelegate logInfo = method5.CreateFastDelegate(); FastReflectionDelegate logWarning = method6.CreateFastDelegate(); FastReflectionDelegate logError = method7.CreateFastDelegate(); _logMethod = delegate(LogLevel level, string msg) { switch (level) { case LogLevel.Debug: logDebug(null, ConsoleColor.Gray, msg); break; case LogLevel.Info: logInfo(null, msg); break; case LogLevel.Warn: logWarning(null, msg); break; case LogLevel.Error: logError(null, msg); break; default: throw new ArgumentException("level"); } }; } } if (_logMethod != null) { return; } throw new Exception("Did not recognize any mod loader!"); } protected override void Log(LogLevel level, string message) { _logMethod(level, message); } public static int Convert(LogLevel level) { return level switch { LogLevel.Debug => 32, LogLevel.Info => 16, LogLevel.Warn => 4, LogLevel.Error => 2, _ => 0, }; } } public abstract class XuaLogger { private static XuaLogger _default; private static XuaLogger _common; private static XuaLogger _resourceRedirector; public static XuaLogger AutoTranslator { get { if (_default == null) { _default = CreateLogger("XUnity.AutoTranslator"); } return _default; } set { _default = value ?? throw new ArgumentNullException("value"); } } public static XuaLogger Common { get { if (_common == null) { _common = CreateLogger("XUnity.Common"); } return _common; } set { _common = value ?? throw new ArgumentNullException("value"); } } public static XuaLogger ResourceRedirector { get { if (_resourceRedirector == null) { _resourceRedirector = CreateLogger("XUnity.ResourceRedirector"); } return _resourceRedirector; } set { _resourceRedirector = value ?? throw new ArgumentNullException("value"); } } public string Source { get; set; } internal static XuaLogger CreateLogger(string source) { try { return new ModLoaderSpecificLogger(source); } catch (Exception) { return new ConsoleLogger(source); } } public XuaLogger(string source) { Source = source; } public void Error(Exception e, string message) { Log(LogLevel.Error, message + Environment.NewLine + e); } public void Error(string message) { Log(LogLevel.Error, message); } public void Warn(Exception e, string message) { Log(LogLevel.Warn, message + Environment.NewLine + e); } public void Warn(string message) { Log(LogLevel.Warn, message); } public void Info(Exception e, string message) { Log(LogLevel.Info, message + Environment.NewLine + e); } public void Info(string message) { Log(LogLevel.Info, message); } public void Debug(Exception e, string message) { Log(LogLevel.Debug, message + Environment.NewLine + e); } public void Debug(string message) { Log(LogLevel.Debug, message); } protected abstract void Log(LogLevel level, string message); protected string GetDefaultPrefix(LogLevel level) { return level switch { LogLevel.Debug => "[DEBUG][" + Source + "]: ", LogLevel.Info => "[INFO][" + Source + "]: ", LogLevel.Warn => "[WARN][" + Source + "]: ", LogLevel.Error => "[ERROR][" + Source + "]: ", _ => "[UNKNOW][" + Source + "]: ", }; } } } namespace XUnity.Common.Harmony { public static class AccessToolsShim { private static readonly BindingFlags All; private static readonly Func<Type, string, Type[], Type[], MethodInfo> AccessTools_Method; private static readonly Func<Type, string, PropertyInfo> AccessTools_Property; static AccessToolsShim() { All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; MethodInfo method = ClrTypes.AccessTools.GetMethod("Method", All, null, new Type[4] { typeof(Type), typeof(string), typeof(Type[]), typeof(Type[]) }, null); MethodInfo? method2 = ClrTypes.AccessTools.GetMethod("Property", All, null, new Type[2] { typeof(Type), typeof(string) }, null); AccessTools_Method = (Func<Type, string, Type[], Type[], MethodInfo>)ExpressionHelper.CreateTypedFastInvoke(method); AccessTools_Property = (Func<Type, string, PropertyInfo>)ExpressionHelper.CreateTypedFastInvoke(method2); } public static MethodInfo Method(Type type, string name, params Type[] parameters) { return AccessTools_Method(type, name, parameters, null); } public static PropertyInfo Property(Type type, string name) { return AccessTools_Property(type, name); } } } namespace XUnity.Common.Extensions { public static class ExceptionExtensions { public static TException FirstInnerExceptionOfType<TException>(this Exception e) where TException : Exception { for (Exception ex = e; ex != null; ex = ex.InnerException) { if (ex is TException) { return (TException)ex; } } return null; } } public static class ObjectExtensions { public static Type GetUnityType(this object obj) { return obj.GetType(); } public static bool TryCastTo<TObject>(this object obj, out TObject castedObject) { if (obj is TObject val) { castedObject = val; return true; } castedObject = default(TObject); return false; } } public static class StreamExtensions { public static byte[] ReadFully(this Stream stream, int initialLength) { if (initialLength < 1) { initialLength = 32768; } byte[] array = new byte[initialLength]; int num = 0; int num2; while ((num2 = stream.Read(array, num, array.Length - num)) > 0) { num += num2; if (num == array.Length) { int num3 = stream.ReadByte(); if (num3 == -1) { return array; } byte[] array2 = new byte[array.Length * 2]; Array.Copy(array, array2, array.Length); array2[num] = (byte)num3; array = array2; num++; } } byte[] array3 = new byte[num]; Array.Copy(array, array3, num); return array3; } } public static class StringExtensions { private static readonly HashSet<char> InvalidFileNameChars = new HashSet<char>(Path.GetInvalidFileNameChars()); public static string UseCorrectDirectorySeparators(this string path) { if (Path.DirectorySeparatorChar == '\\') { return path.Replace('/', Path.DirectorySeparatorChar); } if (Path.DirectorySeparatorChar == '/') { return path.Replace('\\', Path.DirectorySeparatorChar); } return path; } public static bool IsNullOrWhiteSpace(this string value) { if (value == null) { return true; } for (int i = 0; i < value.Length; i++) { if (!char.IsWhiteSpace(value[i])) { return false; } } return true; } public static string MakeRelativePath(this string fullOrRelativePath, string basePath) { StringBuilder stringBuilder = new StringBuilder(); int i = 0; bool flag = false; string[] array = basePath.Split(':', '\\', '/'); List<string> list = fullOrRelativePath.Split(':', '\\', '/').ToList(); if (array.Length == 0 || list.Count <= 0 || array[0] != list[0]) { flag = true; } bool flag2 = false; for (int j = 0; j < list.Count; j++) { if (list[j] == "..") { if (flag2) { int num = j - 1; if (num >= 0) { list.RemoveAt(j); list.RemoveAt(num); j -= 2; } } } else { flag2 = true; } } if (!flag) { for (i = 1; i < array.Length && !(array[i] != list[i]); i++) { } for (int k = 0; k < array.Length - i; k++) { char directorySeparatorChar = Path.DirectorySeparatorChar; stringBuilder.Append(".." + directorySeparatorChar); } } for (int l = i; l < list.Count - 1; l++) { string value = list[l]; stringBuilder.Append(value).Append(Path.DirectorySeparatorChar); } string value2 = list[^1]; stringBuilder.Append(value2); return stringBuilder.ToString(); } public static string SanitizeForFileSystem(this string path) { StringBuilder stringBuilder = new StringBuilder(path.Length); foreach (char c in path) { if (!InvalidFileNameChars.Contains(c)) { stringBuilder.Append(c); } } return stringBuilder.ToString(); } public static string SplitToLines(this string text, int maxStringLength, params char[] splitOnCharacters) { StringBuilder stringBuilder = new StringBuilder(); int num; for (int i = 0; text.Length > i; i += num) { if (i != 0) { stringBuilder.Append('\n'); } num = ((i + maxStringLength <= text.Length) ? text.Substring(i, maxStringLength).LastIndexOfAny(splitOnCharacters) : (text.Length - i)); num = ((num == -1) ? maxStringLength : num); stringBuilder.Append(text.Substring(i, num).Trim()); } return stringBuilder.ToString(); } public static bool StartsWithStrict(this string str, string prefix) { int num = Math.Min(str.Length, prefix.Length); if (num < prefix.Length) { return false; } for (int i = 0; i < num; i++) { if (str[i] != prefix[i]) { return false; } } return true; } public static string GetBetween(this string strSource, string strStart, string strEnd) { int num = strSource.IndexOf(strStart); if (num != -1) { num += strStart.Length; int num2 = strSource.IndexOf(strEnd, num); if (num2 > num) { return strSource.Substring(num, num2 - num); } } return string.Empty; } public static bool RemindsOf(this string that, string other) { if (!that.StartsWith(other) && !other.StartsWith(that) && !that.EndsWith(other)) { return other.EndsWith(that); } return true; } } } namespace XUnity.Common.Constants { public static class ClrTypes { public static readonly Type AccessTools = FindTypeStrict("Harmony.AccessTools, 0Harmony") ?? FindTypeStrict("HarmonyLib.AccessTools, 0Harmony") ?? FindTypeStrict("Harmony.AccessTools, MelonLoader.ModHandler") ?? FindTypeStrict("HarmonyLib.AccessTools, MelonLoader.ModHandler"); public static readonly Type HarmonyMethod = FindTypeStrict("Harmony.HarmonyMethod, 0Harmony") ?? FindTypeStrict("HarmonyLib.HarmonyMethod, 0Harmony") ?? FindTypeStrict("Harmony.HarmonyMethod, MelonLoader.ModHandler") ?? FindTypeStrict("HarmonyLib.HarmonyMethod, MelonLoader.ModHandler"); public static readonly Type HarmonyInstance = FindTypeStrict("Harmony.HarmonyInstance, 0Harmony") ?? FindTypeStrict("Harmony.HarmonyInstance, MelonLoader.ModHandler"); public static readonly Type Harmony = FindTypeStrict("HarmonyLib.Harmony, 0Harmony") ?? FindTypeStrict("HarmonyLib.Harmony, MelonLoader.ModHandler"); public static readonly Type Hook = FindTypeStrict("MonoMod.RuntimeDetour.Hook, MonoMod.RuntimeDetour"); public static readonly Type Detour = FindTypeStrict("MonoMod.RuntimeDetour.Detour, MonoMod.RuntimeDetour"); public static readonly Type NativeDetour = FindTypeStrict("MonoMod.RuntimeDetour.NativeDetour, MonoMod.RuntimeDetour"); public static readonly Type DynamicMethodDefinition = FindTypeStrict("MonoMod.Utils.DynamicMethodDefinition, MonoMod.Utils"); public static readonly Type Imports = FindTypeStrict("MelonLoader.Imports, MelonLoader.ModHandler"); public static readonly Type MethodBase = FindType("System.Reflection.MethodBase"); public static readonly Type Task = FindType("System.Threading.Tasks.Task"); private static Type FindType(string name) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { Type type = assembly.GetType(name, throwOnError: false); if ((object)type != null) { return type; } } catch { } } return null; } private static Type FindTypeStrict(string name) { return Type.GetType(name, throwOnError: false); } } public class TypeContainer { public Type ClrType { get; } public Type UnityType { get; } public TypeContainer(Type type) { UnityType = type; ClrType = type; } public bool IsAssignableFrom(Type unityType) { if ((object)UnityType != null) { return UnityType.IsAssignableFrom(unityType); } return false; } } public static class UnityFeatures { private static readonly BindingFlags All; public static bool SupportsMouseScrollDelta { get; } public static bool SupportsClipboard { get; } public static bool SupportsCustomYieldInstruction { get; } public static bool SupportsSceneManager { get; } public static bool SupportsWaitForSecondsRealtime { get; set; } static UnityFeatures() { All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; SupportsMouseScrollDelta = false; SupportsClipboard = false; SupportsCustomYieldInstruction = false; SupportsSceneManager = false; SupportsWaitForSecondsRealtime = false; try { SupportsClipboard = (object)UnityTypes.TextEditor?.ClrType.GetProperty("text")?.GetSetMethod() != null; } catch (Exception) { } try { SupportsCustomYieldInstruction = UnityTypes.CustomYieldInstruction != null; } catch (Exception) { } try { SupportsSceneManager = UnityTypes.Scene != null && UnityTypes.SceneManager != null && (object)UnityTypes.SceneManager.ClrType.GetMethod("add_sceneLoaded", All) != null; } catch (Exception) { } try { SupportsMouseScrollDelta = (object)UnityTypes.Input?.ClrType.GetProperty("mouseScrollDelta") != null; } catch (Exception) { } try { SupportsWaitForSecondsRealtime = UnityTypes.WaitForSecondsRealtime != null; } catch (Exception) { } } } public static class UnityTypes { public static class TMP_Settings_Properties { public static CachedProperty Version = TMP_Settings?.ClrType.CachedProperty("version"); public static CachedProperty FallbackFontAssets = TMP_Settings?.ClrType.CachedProperty("fallbackFontAssets"); } public static class TMP_FontAsset_Properties { public static CachedProperty Version = TMP_FontAsset?.ClrType.CachedProperty("version"); } public static class AdvScenarioData_Properties { public static CachedProperty ScenarioLabels = AdvScenarioData?.ClrType.CachedProperty("ScenarioLabels"); } public static class UguiNovelText_Properties { public static CachedProperty TextGenerator = UguiNovelText?.ClrType.CachedProperty("TextGenerator"); } public static class UguiNovelText_Methods { public static CachedMethod SetAllDirty = UguiNovelText?.ClrType.CachedMethod("SetAllDirty"); } public static class UguiNovelTextGenerator_Methods { public static CachedMethod Refresh = UguiNovelTextGenerator?.ClrType.CachedMethod("Refresh"); } public static class AdvUguiMessageWindow_Properties { public static CachedProperty Text = AdvUguiMessageWindow?.ClrType.CachedProperty("Text"); public static CachedProperty Engine = AdvUguiMessageWindow?.ClrType.CachedProperty("Engine"); } public static class AdvUiMessageWindow_Fields { public static CachedField text = AdvUiMessageWindow?.ClrType.CachedField("text"); public static CachedField nameText = AdvUiMessageWindow?.ClrType.CachedField("nameText"); } public static class AdvUguiMessageWindow_Fields { public static FieldInfo text = AdvUguiMessageWindow?.ClrType.GetField("text", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); public static FieldInfo nameText = AdvUguiMessageWindow?.ClrType.GetField("nameText", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); public static FieldInfo engine = AdvUguiMessageWindow?.ClrType.GetField("engine", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } public static class AdvEngine_Properties { public static CachedProperty Page = AdvEngine?.ClrType.CachedProperty("Page"); } public static class AdvPage_Methods { public static CachedMethod RemakeTextData = AdvPage?.ClrType.CachedMethod("RemakeTextData"); public static CachedMethod RemakeText = AdvPage?.ClrType.CachedMethod("RemakeText"); public static CachedMethod ChangeMessageWindowText = AdvPage?.ClrType.CachedMethod("ChangeMessageWindowText", typeof(string), typeof(string), typeof(string), typeof(string)); } public static class UILabel_Properties { public static CachedProperty MultiLine = UILabel?.ClrType.CachedProperty("multiLine"); public static CachedProperty OverflowMethod = UILabel?.ClrType.CachedProperty("overflowMethod"); public static CachedProperty SpacingX = UILabel?.ClrType.CachedProperty("spacingX"); public static CachedProperty UseFloatSpacing = UILabel?.ClrType.CachedProperty("useFloatSpacing"); } public static class Text_Properties { public static CachedProperty Font = Text?.ClrType.CachedProperty("font"); public static CachedProperty FontSize = Text?.ClrType.CachedProperty("fontSize"); public static CachedProperty HorizontalOverflow = Text?.ClrType.CachedProperty("horizontalOverflow"); public static CachedProperty VerticalOverflow = Text?.ClrType.CachedProperty("verticalOverflow"); public static CachedProperty LineSpacing = Text?.ClrType.CachedProperty("lineSpacing"); public static CachedProperty ResizeTextForBestFit = Text?.ClrType.CachedProperty("resizeTextForBestFit"); public static CachedProperty ResizeTextMinSize = Text?.ClrType.CachedProperty("resizeTextMinSize"); public static CachedProperty ResizeTextMaxSize = Text?.ClrType.CachedProperty("resizeTextMaxSize"); } public static class InputField_Properties { public static CachedProperty Placeholder = InputField?.ClrType.CachedProperty("placeholder"); } public static class TMP_InputField_Properties { public static CachedProperty Placeholder = TMP_InputField?.ClrType.CachedProperty("placeholder"); } public static class Font_Properties { public static CachedProperty FontSize = Font?.ClrType.CachedProperty("fontSize"); } public static class AssetBundle_Methods { public static CachedMethod LoadAll = AssetBundle?.ClrType.CachedMethod("LoadAll", typeof(Type)); public static CachedMethod LoadAllAssets = AssetBundle?.ClrType.CachedMethod("LoadAllAssets", typeof(Type)); public static CachedMethod LoadFromFile = AssetBundle?.ClrType.CachedMethod("LoadFromFile", typeof(string)); public static CachedMethod CreateFromFile = AssetBundle?.ClrType.CachedMethod("CreateFromFile", typeof(string)); } public static class TextExpansion_Methods { public static CachedMethod SetMessageType = TextExpansion?.ClrType.CachedMethod("SetMessageType"); public static CachedMethod SkipTypeWriter = TextExpansion?.ClrType.CachedMethod("SkipTypeWriter"); } public static class GameObject_Methods { } public static class TextMesh_Methods { } public static class Text_Methods { } public static class InputField_Methods { } public static class TMP_Text_Methods { } public static class TMP_InputField_Methods { } public static class TextMeshPro_Methods { } public static class TextMeshProUGUI_Methods { } public static class UILabel_Methods { } public static class UIRect_Methods { } public static class SceneManager_Methods { public static readonly Action<UnityAction<Scene, LoadSceneMode>> add_sceneLoaded = (Action<UnityAction<Scene, LoadSceneMode>>)ExpressionHelper.CreateTypedFastInvokeUnchecked(typeof(SceneManager).GetMethod("add_sceneLoaded", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(UnityAction<Scene, LoadSceneMode>) }, null)); } public static class Texture2D_Methods { public static readonly Func<Texture2D, byte[], bool> LoadImage = (Func<Texture2D, byte[], bool>)ExpressionHelper.CreateTypedFastInvokeUnchecked(typeof(Texture2D).GetMethod("LoadImage", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(byte[]) }, null)); public static readonly Func<Texture2D, byte[]> EncodeToPNG = (Func<Texture2D, byte[]>)ExpressionHelper.CreateTypedFastInvokeUnchecked(typeof(Texture2D).GetMethod("EncodeToPNG", BindingFlags.Instance | BindingFlags.Public, null, new Type[0], null)); } public static class ImageConversion_Methods { public static readonly Func<Texture2D, byte[], bool, bool> LoadImage = (Func<Texture2D, byte[], bool, bool>)ExpressionHelper.CreateTypedFastInvokeUnchecked(ImageConversion?.ClrType.GetMethod("LoadImage", BindingFlags.Static | BindingFlags.Public, null, new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) }, null)); public static readonly Func<Texture2D, byte[]> EncodeToPNG = (Func<Texture2D, byte[]>)ExpressionHelper.CreateTypedFastInvokeUnchecked(ImageConversion?.ClrType.GetMethod("EncodeToPNG", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(Texture2D) }, null)); } public static readonly TypeContainer UILabel = FindType("UILabel"); public static readonly TypeContainer UIWidget = FindType("UIWidget"); public static readonly TypeContainer UIAtlas = FindType("UIAtlas"); public static readonly TypeContainer UISprite = FindType("UISprite"); public static readonly TypeContainer UITexture = FindType("UITexture"); public static readonly TypeContainer UI2DSprite = FindType("UI2DSprite"); public static readonly TypeContainer UIFont = FindType("UIFont"); public static readonly TypeContainer UIPanel = FindType("UIPanel"); public static readonly TypeContainer UIRect = FindType("UIRect"); public static readonly TypeContainer UIInput = FindType("UIInput"); public static readonly TypeContainer TextField = FindType("FairyGUI.TextField"); public static readonly TypeContainer TMP_InputField = FindType("TMPro.TMP_InputField"); public static readonly TypeContainer TMP_Text = FindType("TMPro.TMP_Text"); public static readonly TypeContainer TextMeshProUGUI = FindType("TMPro.TextMeshProUGUI"); public static readonly TypeContainer TextMeshPro = FindType("TMPro.TextMeshPro"); public static readonly TypeContainer TMP_FontAsset = FindType("TMPro.TMP_FontAsset"); public static readonly TypeContainer TMP_Settings = FindType("TMPro.TMP_Settings"); public static readonly TypeContainer GameObject = FindType("UnityEngine.GameObject"); public static readonly TypeContainer Transform = FindType("UnityEngine.Transform"); public static readonly TypeContainer TextMesh = FindType("UnityEngine.TextMesh"); public static readonly TypeContainer Text = FindType("UnityEngine.UI.Text"); public static readonly TypeContainer Image = FindType("UnityEngine.UI.Image"); public static readonly TypeContainer RawImage = FindType("UnityEngine.UI.RawImage"); public static readonly TypeContainer MaskableGraphic = FindType("UnityEngine.UI.MaskableGraphic"); public static readonly TypeContainer Graphic = FindType("UnityEngine.UI.Graphic"); public static readonly TypeContainer GUIContent = FindType("UnityEngine.GUIContent"); public static readonly TypeContainer WWW = FindType("UnityEngine.WWW"); public static readonly TypeContainer InputField = FindType("UnityEngine.UI.InputField"); public static readonly TypeContainer GUI = FindType("UnityEngine.GUI"); public static readonly TypeContainer GUI_ToolbarButtonSize = FindType("UnityEngine.GUI+ToolbarButtonSize"); public static readonly TypeContainer GUIStyle = FindType("UnityEngine.GUIStyle"); public static readonly TypeContainer ImageConversion = FindType("UnityEngine.ImageConversion"); public static readonly TypeContainer Texture2D = FindType("UnityEngine.Texture2D"); public static readonly TypeContainer Texture = FindType("UnityEngine.Texture"); public static readonly TypeContainer SpriteRenderer = FindType("UnityEngine.SpriteRenderer"); public static readonly TypeContainer Sprite = FindType("UnityEngine.Sprite"); public static readonly TypeContainer Object = FindType("UnityEngine.Object"); public static readonly TypeContainer TextEditor = FindType("UnityEngine.TextEditor"); public static readonly TypeContainer CustomYieldInstruction = FindType("UnityEngine.CustomYieldInstruction"); public static readonly TypeContainer SceneManager = FindType("UnityEngine.SceneManagement.SceneManager"); public static readonly TypeContainer Scene = FindType("UnityEngine.SceneManagement.Scene"); public static readonly TypeContainer UnityEventBase = FindType("UnityEngine.Events.UnityEventBase"); public static readonly TypeContainer BaseInvokableCall = FindType("UnityEngine.Events.BaseInvokableCall"); public static readonly TypeContainer Font = FindType("UnityEngine.Font"); public static readonly TypeContainer WaitForSecondsRealtime = FindType("UnityEngine.WaitForSecondsRealtime"); public static readonly TypeContainer Input = FindType("UnityEngine.Input"); public static readonly TypeContainer AssetBundleCreateRequest = FindType("UnityEngine.AssetBundleCreateRequest"); public static readonly TypeContainer AssetBundle = FindType("UnityEngine.AssetBundle"); public static readonly TypeContainer AssetBundleRequest = FindType("UnityEngine.AssetBundleRequest"); public static readonly TypeContainer Resources = FindType("UnityEngine.Resources"); public static readonly TypeContainer AsyncOperation = FindType("UnityEngine.AsyncOperation"); public static readonly TypeContainer TextAsset = FindType("UnityEngine.TextAsset"); public static readonly Type HorizontalWrapMode = FindClrType("UnityEngine.HorizontalWrapMode"); public static readonly Type TextOverflowModes = FindClrType("TMPro.TextOverflowModes"); public static readonly Type TextAlignmentOptions = FindClrType("TMPro.TextAlignmentOptions"); public static readonly Type VerticalWrapMode = FindClrType("UnityEngine.VerticalWrapMode"); public static readonly TypeContainer TextExpansion = FindType("UnityEngine.UI.TextExpansion"); public static readonly TypeContainer Typewriter = FindType("Typewriter"); public static readonly TypeContainer UguiNovelText = FindType("Utage.UguiNovelText"); public static readonly TypeContainer UguiNovelTextGenerator = FindType("Utage.UguiNovelTextGenerator"); public static readonly TypeContainer AdvEngine = FindType("Utage.AdvEngine"); public static readonly TypeContainer AdvPage = FindType("Utage.AdvPage"); public static readonly TypeContainer TextData = FindType("Utage.TextData"); public static readonly TypeContainer AdvUguiMessageWindow = FindType("Utage.AdvUguiMessageWindow") ?? FindType("AdvUguiMessageWindow"); public static readonly TypeContainer AdvUiMessageWindow = FindType("AdvUiMessageWindow"); public static readonly TypeContainer AdvDataManager = FindType("Utage.AdvDataManager"); public static readonly TypeContainer AdvScenarioData = FindType("Utage.AdvScenarioData"); public static readonly TypeContainer AdvScenarioLabelData = FindType("Utage.AdvScenarioLabelData"); public static readonly TypeContainer DicingTextures = FindType("Utage.DicingTextures"); public static readonly TypeContainer DicingImage = FindType("Utage.DicingImage"); public static readonly TypeContainer TextArea2D = FindType("Utage.TextArea2D"); public static readonly TypeContainer CubismRenderer = FindType("Live2D.Cubism.Rendering.CubismRenderer"); public static readonly TypeContainer TextWindow = FindType("Assets.System.Text.TextWindow"); private static Type FindClrType(string name) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { Type type = assembly.GetType(name, throwOnError: false); if ((object)type != null) { return type; } } catch { } } return null; } private static TypeContainer FindType(string name) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { Type type = assembly.GetType(name, throwOnError: false); if ((object)type != null) { return new TypeContainer(type); } } catch { } } return null; } } }
BepInEx/plugins/GameTranslator.dll
Decompiled 4 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameTranslator.Patches; using GameTranslator.Patches.Hooks; using GameTranslator.Patches.Hooks.texture; using GameTranslator.Patches.InteractiveTerminalAPI; using GameTranslator.Patches.Translatons; using GameTranslator.Patches.Translatons.Manipulator; using GameTranslator.Patches.Utils; using GameTranslator.Patches.Utils.Textures; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using Unity.Netcode; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityEngine.UIElements; using XUnity.Common.Constants; using XUnity.Common.Extensions; using XUnity.Common.Harmony; using XUnity.Common.Logging; using XUnity.Common.MonoMod; using XUnity.Common.Utilities; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")] [assembly: AssemblyCompany("GameTranslator")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+07f61dc32e06b0a96fb4f1446ea170f3e3b51376")] [assembly: AssemblyProduct("GameTranslator")] [assembly: AssemblyTitle("GameTranslator")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace GameTranslator { public class TranslateConfig { public class TranslateConfigFile { public string ConfigFilePath; public string ConfigFileName; public bool shouldTranslate; public int shouldTranslateMinLength = 200; public int shouldTranslateMaxLength; public bool shouldLoad = true; public IDictionary<string, string> normal = new Dictionary<string, string>(); public IDictionary<string, string> special = new Dictionary<string, string>(); public static HashSet<TranslateConfigFile> configs = new HashSet<TranslateConfigFile>(); public Dictionary<string, string> translatePairs = new Dictionary<string, string>(); private List<string> logs = new List<string>(); public List<RegexTranslation> regexTranslations = new List<RegexTranslation>(); public List<RegexTranslationSplitter> splitterRegexTranslations = new List<RegexTranslationSplitter>(); private static Regex _regex; internal readonly ConcurrentDictionary<string, DateTime> _translatePairLastAccess = new ConcurrentDictionary<string, DateTime>(); public static Regex regex { get { if (_regex == null) { Type typeFromHandle = typeof(TranslateConfigFile); lock (typeFromHandle) { if (_regex == null) { _regex = new Regex("(?<!\\\\)=", NormalTextTranslator.RegexCompiledSupportedFlag); } } } return _regex; } } public TranslateConfigFile(string configName, bool saveOnInit, bool shouldLoad) { ConfigFileName = configName; ConfigFilePath = TranslatePlugin.DefaultPath + configName + ".cfg"; this.shouldLoad = shouldLoad; if (ConfigFilePath == null) { throw new ArgumentNullException("configPath"); } ConfigFilePath = Path.GetFullPath(ConfigFilePath); if (this.shouldLoad && File.Exists(ConfigFilePath)) { Reload(); } else if (saveOnInit) { Save(); } configs.Add(this); } public void Reload() { translatePairs.Clear(); _translatePairLastAccess.Clear(); normal.Clear(); regexTranslations.Clear(); splitterRegexTranslations.Clear(); List<string> list = new List<string>(); string[] array = File.ReadAllLines(ConfigFilePath); foreach (string text in array) { if (text.StartsWith("#") || !text.Contains("=")) { continue; } string[] array2 = regex.Split(text); if (array2.Length != 2) { continue; } string text2 = array2[0].Replace("\\=", "="); string text3 = array2[1].Replace("\\=", "="); if (text2.StartsWith("r:")) { try { RegexTranslation item = new RegexTranslation(text2, text3); regexTranslations.Add(item); } catch (Exception ex) { string text4 = text2 + "=" + text3; list.Add("Invalid regex: " + text4 + " - " + ex.Message); TranslatePlugin.logger.LogWarning((object)("Failed to parse regex: " + text4 + ". Error: " + ex.Message)); } continue; } if (text2.StartsWith("sr:")) { try { RegexTranslationSplitter item2 = new RegexTranslationSplitter(text2, text3); splitterRegexTranslations.Add(item2); } catch (Exception ex2) { string text5 = text2 + "=" + text3; list.Add("Invalid splitter regex: " + text5 + " - " + ex2.Message); TranslatePlugin.logger.LogWarning((object)("Failed to parse splitter regex: " + text5 + ". Error: " + ex2.Message)); } continue; } if (normal.ContainsKey(text2)) { normal[text2] = text3; special[text3] = text2; } else { normal.Add(text2, text3); if (!special.ContainsKey(text3)) { special.Add(text3, text2); } } if (text2.Length < shouldTranslateMinLength) { shouldTranslateMinLength = text2.Length; } if (text2.Length > shouldTranslateMaxLength) { shouldTranslateMaxLength = text2.Length; } } if (list.Count > 0) { File.AppendAllLines(Path.Combine(Path.GetDirectoryName(ConfigFilePath), ConfigFileName + "_errors.log"), list); } } public void Log(string text) { logs.Add(text); string directoryName = Path.GetDirectoryName(ConfigFilePath); if (directoryName == null) { Directory.CreateDirectory(directoryName); } new List<string>().Add("##" + ConfigFileName); File.WriteAllLines(ConfigFilePath, logs); } public void Save() { string directoryName = Path.GetDirectoryName(ConfigFilePath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } if (!File.Exists(ConfigFilePath)) { File.Create(ConfigFilePath).Close(); } else { if (!shouldLoad) { return; } List<string> list = new List<string>(); list.Add("##" + ConfigFileName); foreach (KeyValuePair<string, string> item in normal) { string text = item.Key.Replace("=", "\\="); string text2 = item.Value.Replace("=", "\\="); list.Add(text + "=" + text2); } list.Add("##RegularExpression"); foreach (RegexTranslation regexTranslation in regexTranslations) { list.Add("r:" + regexTranslation.Key + "=" + regexTranslation.Value); } list.Add("##SplitterRegularExpression"); foreach (RegexTranslationSplitter splitterRegexTranslation in splitterRegexTranslations) { list.Add("sr:" + splitterRegexTranslation.Key + "=" + splitterRegexTranslation.Value); } File.WriteAllLines(ConfigFilePath, list.ToArray()); } } } private static Dictionary<string, DateTime> _fileLastModifiedTimes = new Dictionary<string, DateTime>(); private static DateTime _lastCheckTime = DateTime.Now; public static TranslateConfigFile normal; public static TranslateConfigFile hud; public static TranslateConfigFile items; public static TranslateConfigFile terminal; public static TranslateConfigFile text; public static TranslateConfigFile cmd_py; public static TranslateConfigFile cmd_zh; public static TranslateConfigFile gui; public static TranslateConfigFile interactiveTerminalAPI; public static NormalTextTranslator normalText; public static NormalTextTranslator hudText; public static NormalTextTranslator guiText; public static TextureTranslationCache cache; public static NormalTextTranslator itemsText; public static NormalTextTranslator terminalText; public static NormalTextTranslator textText; public static NormalTextTranslator cmdPyText; public static NormalTextTranslator cmdZhText; public static NormalTextTranslator interactiveTerminalAPIText; private static DateTime _lastCleanupTime = DateTime.Now; private static readonly TimeSpan CLEANUP_INTERVAL = TimeSpan.FromMinutes(30.0); public static void Load() { hud = CreateNewConfig("HUD-Translate", should: false); hud.shouldTranslate = TranslatePlugin.shouldTranslateHUD.Value; hudText = new NormalTextTranslator(hud.ConfigFileName + ".cfg"); hudText.Load(); items = CreateNewConfig("Item-Translate"); items.shouldTranslate = TranslatePlugin.shouldTranslateItems.Value; itemsText = new NormalTextTranslator(items.ConfigFileName + ".cfg"); itemsText.Load(); terminal = CreateNewConfig("Terminal-Translate"); terminal.shouldTranslate = TranslatePlugin.shouldTranslateTerimal.Value; terminalText = new NormalTextTranslator(terminal.ConfigFileName + ".cfg"); terminalText.Load(); cmd_py = CreateNewConfig("CMD-PY-Translate"); cmdPyText = new NormalTextTranslator(cmd_py.ConfigFileName + ".cfg"); cmdPyText.Load(); cmd_zh = CreateNewConfig("CMD-ZH-Translate"); cmdZhText = new NormalTextTranslator(cmd_zh.ConfigFileName + ".cfg"); cmdZhText.Load(); text = CreateNewConfig("SpecialText-Translate"); text.shouldTranslate = TranslatePlugin.shouldTranslateSpecialText.Value; textText = new NormalTextTranslator(text.ConfigFileName + ".cfg"); textText.Load(); gui = CreateNewConfig("GuiText-Translate"); gui.shouldTranslate = TranslatePlugin.shouldTranslateGui.Value; guiText = new NormalTextTranslator(gui.ConfigFileName + ".cfg"); guiText.Load(); interactiveTerminalAPI = CreateNewConfig("InteractiveTerminalAPI-Translate"); interactiveTerminalAPI.shouldTranslate = TranslatePlugin.shouldTranslateInteractiveTerminalAPI.Value; interactiveTerminalAPIText = new NormalTextTranslator(interactiveTerminalAPI.ConfigFileName + ".cfg"); interactiveTerminalAPIText.Load(); normal = CreateNewConfig("Normal-Translate", should: false); normal.shouldTranslate = TranslatePlugin.shouldTranslateNormalText.Value; normalText = new NormalTextTranslator(normal.ConfigFileName + ".cfg"); normalText.Load(); cache = new TextureTranslationCache(); cache.LoadTranslationFiles(); foreach (TranslateConfigFile config in TranslateConfigFile.configs) { if (File.Exists(config.ConfigFilePath)) { _fileLastModifiedTimes[config.ConfigFilePath] = File.GetLastWriteTime(config.ConfigFilePath); } } AsyncTranslationManager.Instance.ClearCache(); } public static void CheckForFileChanges() { try { if (DateTime.Now - _lastCheckTime < TimeSpan.FromSeconds(2.0)) { return; } _lastCheckTime = DateTime.Now; foreach (TranslateConfigFile config in TranslateConfigFile.configs) { if (!config.shouldLoad || !File.Exists(config.ConfigFilePath)) { continue; } DateTime lastWriteTime = File.GetLastWriteTime(config.ConfigFilePath); if (_fileLastModifiedTimes.TryGetValue(config.ConfigFilePath, out var value)) { if (lastWriteTime > value) { _fileLastModifiedTimes[config.ConfigFilePath] = lastWriteTime; config.Reload(); GetModuleTranslator(config)?.Load(); TextTranslate.ChangeTime++; } } else { _fileLastModifiedTimes[config.ConfigFilePath] = lastWriteTime; } } TextureTranslationCache.CheckForTextureFileChanges(); } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error in CheckForFileChanges: " + ex.Message)); } } public static void show(TranslateConfigFile file) { foreach (string key in file.normal.Keys) { TranslatePlugin.LogInfo(key + "=" + file.normal[key]); } } private static TranslateConfigFile CreateNewConfig(string fileName, bool should) { TranslatePlugin.logger.LogInfo((object)("GameTranslator is loading config file call " + fileName)); return new TranslateConfigFile(fileName, saveOnInit: true, should); } public static bool IsStringContainsEnglish(string input) { if (!string.IsNullOrEmpty(input)) { return new Regex("[a-zA-Z]").IsMatch(input); } return false; } public static bool IsStringContainsChinese(string input) { if (!string.IsNullOrEmpty(input)) { return new Regex("[\\u4e00-\\u9fa5]").IsMatch(input); } return false; } public static string useRegularExpression(string raw, string pattern, string result) { return Regex.Replace(raw, pattern, result); } public static string replaceByMap(string text, TranslateConfigFile file) { if (file.normal.Count == 0 && file.regexTranslations.Count == 0 && file.splitterRegexTranslations.Count == 0) { return text; } Stopwatch stopwatch = Stopwatch.StartNew(); try { if (DateTime.Now - _lastCleanupTime > CLEANUP_INTERVAL) { CleanupTranslatePairs(); _lastCleanupTime = DateTime.Now; } if (!file.shouldTranslate) { return text; } if (file.translatePairs.ContainsKey(text)) { file._translatePairLastAccess.TryAdd(text, DateTime.Now); file._translatePairLastAccess[text] = DateTime.Now; return file.translatePairs[text]; } StringBuffer stringBuffer = new StringBuffer(text); NormalTextTranslator moduleTranslator = GetModuleTranslator(file); if (moduleTranslator != null) { foreach (RegexTranslationSplitter splitterRegex in moduleTranslator._splitterRegexes) { string str = moduleTranslator.SplitterTranslate(stringBuffer.ToString(), splitterRegex, ignoreCase: false); stringBuffer.Clear().Append(str); } foreach (RegexTranslation defaultRegex in moduleTranslator._defaultRegexes) { if (defaultRegex.CompiledRegex != null && defaultRegex.CompiledRegex.IsMatch(stringBuffer.ToString())) { string str2 = defaultRegex.CompiledRegex.Replace(stringBuffer.ToString(), defaultRegex.Translation); stringBuffer.Clear().Append(str2); } } } foreach (KeyValuePair<string, string> item in file.normal.OrderByDescending((KeyValuePair<string, string> kv) => kv.Key.Length)) { stringBuffer.ReplaceFull(item.Key, item.Value); } string text2 = stringBuffer.ToString(); file.translatePairs[text] = text2; file._translatePairLastAccess.TryAdd(text, DateTime.Now); return text2; } finally { stopwatch.Stop(); if (stopwatch.ElapsedMilliseconds > 500) { string arg = ((text.Length > 50) ? (text.Substring(0, 50) + "...") : text); TranslatePlugin.logger.LogWarning((object)$"replaceByMap took {stopwatch.ElapsedMilliseconds}ms for text: {arg}"); } } } private static NormalTextTranslator GetModuleTranslator(TranslateConfigFile file) { if (file == normal) { return normalText; } if (file == hud) { return hudText; } if (file == items) { return itemsText; } if (file == terminal) { return terminalText; } if (file == text) { return textText; } if (file == cmd_py) { return cmdPyText; } if (file == cmd_zh) { return cmdZhText; } if (file == gui) { return guiText; } if (file == interactiveTerminalAPI) { return interactiveTerminalAPIText; } return null; } private static void CleanupTranslatePairs() { bool flag = GC.GetTotalMemory(forceFullCollection: false) > 104857600; foreach (TranslateConfigFile config in TranslateConfigFile.configs) { int num = 0; if (flag) { num = (int)((float)config.translatePairs.Count * 0.2f); num = Math.Max(1, Math.Min(num, config.translatePairs.Count)); } else if (config.translatePairs.Count > 6000) { num = config.translatePairs.Count - 6000; } if (num <= 0) { continue; } List<string> list = config._translatePairLastAccess.OrderBy((KeyValuePair<string, DateTime> kv) => kv.Value).Take(num).Select(delegate(KeyValuePair<string, DateTime> kv) { KeyValuePair<string, DateTime> keyValuePair = kv; return keyValuePair.Key; }) .ToList(); foreach (string item in list) { config.translatePairs.Remove(item); config._translatePairLastAccess.TryRemove(item, out var _); } TranslatePlugin.logger.LogInfo((object)$"Cleaned {list.Count} translate pairs from {config.ConfigFileName}. Remaining: {config.translatePairs.Count}"); } } private static TranslateConfigFile CreateNewConfig(string fileName) { return CreateNewConfig(fileName, should: true); } } [BepInPlugin("GameTranslator", "GameTranslator", "2.0.6")] public class TranslatePlugin : BaseUnityPlugin { public static ManualLogSource logger; public static ConfigEntry<int> syncTranslationThreshold; public static ConfigEntry<bool> showAvailableText; public static ConfigEntry<bool> showOtherDebug; public static ConfigEntry<bool> replaceUnsupportedCharacters; public static ConfigEntry<bool> enableGrabbableObjectPatch; public static ConfigEntry<bool> enableHUDManagerPatch; public static ConfigEntry<bool> enableTerminalPatch; public static ConfigEntry<string> language; public static ConfigEntry<bool> shouldTranslateNormalText; public static ConfigEntry<bool> shouldTranslateSpecialText; public static ConfigEntry<bool> shouldTranslateTerimal; public static ConfigEntry<bool> shouldTranslateInteractiveTerminalAPI; public static ConfigEntry<bool> TerimalCanUseChinese; public static ConfigEntry<bool> TerimalCanUsePinyinAbbreviation; public static ConfigEntry<bool> shouldTranslateGui; public static ConfigEntry<bool> shouldTranslateItems; public static ConfigEntry<bool> shouldTranslateHUD; public static ConfigEntry<bool> changeFont; public static ConfigEntry<string> fallbackFontTextMeshPro; public static ConfigEntry<string> shouldRemoveChar; public static ConfigEntry<bool> changeTexture; public static ConfigEntry<bool> cacheTexturesInMemory; public static ConfigEntry<bool> loadUnmodifiedTextures; public static ConfigEntry<bool> generateTerimalCommand; private const string PLUGIN_GUID = "GameTranslator"; private const string PLUGIN_NAME = "GameTranslator"; private const string PLUGIN_VERSION = "2.0.6"; internal static TranslatePlugin Instance; private readonly Harmony harmony = new Harmony("GameTranslator"); public static string DefaultPath; public static string TexturesPath; public static bool shouldTranslate; public static bool CacheTexturesInMemory => cacheTexturesInMemory.Value; private void Awake() { logger = ((BaseUnityPlugin)this).Logger; Instance = this; ((BaseUnityPlugin)this).Logger.LogInfo((object)"GameTranslator is loaded"); ConfigFile(); HookingHelper.PatchAll((IEnumerable<Type>)ImageHooks.All, false); HookingHelper.PatchAll((IEnumerable<Type>)ImageHooks.Sprite, false); HookingHelper.PatchAll((IEnumerable<Type>)ImageHooks.SpriteRenderer, false); ApplyForcedPatches(); ApplyGrabbableObjectPatch(); ApplyHUDManagerPatch(); ApplyTerminalPatch(); ApplyInteractiveTerminalAPIPatch(); if (replaceUnsupportedCharacters.Value) { FontSupportChecker.InitializeFonts(); } AsyncTranslationManager.Instance.Start(); } private void OnDestroy() { AsyncTranslationManager.Instance.Stop(); } private void Update() { try { TranslateConfig.CheckForFileChanges(); AsyncTranslationManager.Instance.ProcessMainThreadActions(); } catch (Exception ex) { logger.LogError((object)("Error in Update: " + ex.Message)); } } private void ConfigFile() { syncTranslationThreshold = ((BaseUnityPlugin)this).Config.Bind<int>("ASync", "Sync Translation Threshold", 200, "Define the character threshold to not use async translation"); showAvailableText = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Show Available Text", false, "Define whether to show available text"); showOtherDebug = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Show Other Debug", false, "Define whether to show other debug"); replaceUnsupportedCharacters = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Replace Unsupported Characters", false, "Define whether to replace unsupported characters with Unicode character u25A1"); enableGrabbableObjectPatch = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Enable GrabbableObject Patch", true, "Define whether to patch GrabbableObject"); enableHUDManagerPatch = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Enable HUDManager Patch", true, "Define whether to patch HUDManager"); enableTerminalPatch = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Enable Terminal Patch", true, "Define whether to patch Terminal"); language = ((BaseUnityPlugin)this).Config.Bind<string>("Setting", "Language", "Default", "Define what language folder is used"); shouldTranslateNormalText = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Translate Normal Text", true, "Define whether to use Normal Translate method"); shouldTranslateSpecialText = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Translate Special Text", false, "Define whether to use SpecialText Translate method"); shouldTranslateTerimal = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Translate Terminal", false, "Define whether translate Terminal"); shouldTranslateInteractiveTerminalAPI = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Translate InteractiveTerminalAPI", false, "Define whether translate InteractiveTerminalAPI"); TerimalCanUseChinese = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Terminal Can Use Non-English Shortcut Commands", false, "Define whether the terminal can use non-English shortcut commands, such as Chinese"); TerimalCanUsePinyinAbbreviation = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Terminal Can Use Custom Shortcut Commands", false, "Define whether the terminal can use custom shortcut commands"); shouldTranslateGui = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Translate Gui", false, "Define whether translate Gui"); shouldTranslateItems = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Translate Items", false, "Define whether translate Items"); shouldTranslateHUD = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Translate HUD", false, "Define whether translate HUD"); changeFont = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Change Font", false, "Define whether to change the font"); fallbackFontTextMeshPro = ((BaseUnityPlugin)this).Config.Bind<string>("Setting", "FallbackFontTextMeshPro", "", "Define the fallback font file used"); shouldRemoveChar = ((BaseUnityPlugin)this).Config.Bind<string>("Setting", "Custom Characters", "", "Define what vanilla characters will use custom ones"); changeTexture = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Change Texture", false, "Define whether to change the texture"); cacheTexturesInMemory = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Cache Textures In Memory", true, "Define whether to cache texture data in memory for faster loading"); loadUnmodifiedTextures = ((BaseUnityPlugin)this).Config.Bind<bool>("Setting", "Load Unmodified Textures", false, "Define whether to load textures that have not been modified"); DefaultPath = ((BaseUnityPlugin)this).Config.ConfigFilePath.Replace("GameTranslator.cfg", "translations\\" + language.Value + "\\"); if (!Directory.Exists(DefaultPath)) { logger.LogWarning((object)("Translation path does not exist: " + DefaultPath)); try { Directory.CreateDirectory(DefaultPath); logger.LogInfo((object)("Created translation directory: " + DefaultPath)); } catch (Exception ex) { logger.LogError((object)("Failed to create translation directory: " + ex.Message)); DefaultPath = Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), "translations", "default"); Directory.CreateDirectory(DefaultPath); logger.LogInfo((object)("Using fallback translation directory: " + DefaultPath)); } } TexturesPath = DefaultPath + "Texture\\"; if (!Directory.Exists(TexturesPath)) { Directory.CreateDirectory(TexturesPath); } TranslateConfig.Load(); TranslateExtensions.Load(); } public static List<char> getShouldRemoveChars() { return shouldRemoveChar.Value.ToCharArray().ToList(); } public static void LogInfo(string info) { if (logger != null) { logger.LogInfo((object)info); } } private void ApplyForcedPatches() { try { Type[] array = new Type[63] { typeof(GameObjectHook), typeof(GuiContentHook), typeof(TeshMeshProHook), typeof(TeshMeshProUGUIHook), typeof(TextArea2DHook), typeof(TextFieldHook), typeof(TextHook), typeof(TextMeshHook), typeof(Texture2DHook), typeof(TMP_FontAssetHook), typeof(TMP_TextHook), typeof(CubismRenderer_MainTexture_Hook), typeof(CubismRenderer_TryInitialize_Hook), typeof(Cursor_SetCursor_Hook), typeof(DicingTextures_GetTexture_Hook), typeof(Image_material_Hook), typeof(Image_overrideSprite_Hook), typeof(Image_sprite_Hook), typeof(ImageHooks), typeof(MaskableGraphic_OnEnable_Hook), typeof(Material_mainTexture_Hook), typeof(RawImage_texture_Hook), typeof(Sprite_texture_Hook), typeof(SpriteRenderer_sprite_Hook), typeof(UI2DSprite_material_Hook), typeof(UI2DSprite_sprite2D_Hook), typeof(UIAtlas_spriteMaterial_Hook), typeof(UIPanel_clipTexture_Hook), typeof(UIRect_OnInit_Hook), typeof(UISprite_atlas_Hook), typeof(UISprite_material_Hook), typeof(UISprite_OnInit_Hook), typeof(UITexture_mainTexture_Hook), typeof(UITexture_material_Hook), typeof(AsyncTranslationManager), typeof(ImageTranslationInfo), typeof(NormalTextTranslator), typeof(RegexTranslation), typeof(RegexTranslationSplitter), typeof(TextTranslationInfo), typeof(TextureDataResult), typeof(TextureTranslationCache), typeof(TextureTranslationInfo), typeof(TranslatedImage), typeof(TranslateExtensions), typeof(DefaultTextComponentManipulator), typeof(FairyGUITextComponentManipulator), typeof(ITextComponentManipulator), typeof(TextArea2DComponentManipulator), typeof(UguiNovelTextComponentManipulator), typeof(FontCache), typeof(FontHelper), typeof(FontSupportChecker), typeof(SceneManagerLoader), typeof(StringBuffer), typeof(TextHelper), typeof(TextTranslate), typeof(TextureTranslate), typeof(TranslationScopeHelper), typeof(ITextureLoader), typeof(LoadImageImageLoader), typeof(TextureLoader), typeof(TgaImageLoader) }; Type[] array2 = array; foreach (Type type in array2) { harmony.PatchAll(type); } ManualLogSource obj = logger; if (obj != null) { obj.LogInfo((object)"Forced patches applied successfully"); } } catch (Exception ex) { ManualLogSource obj2 = logger; if (obj2 != null) { obj2.LogWarning((object)("Error applying forced patches: " + ex.Message)); } } } private void ApplyGrabbableObjectPatch() { try { if (enableGrabbableObjectPatch != null && enableGrabbableObjectPatch.Value) { harmony.PatchAll(typeof(GrabbableObjectPatcher)); ManualLogSource obj = logger; if (obj != null) { obj.LogInfo((object)"GrabbableObject patch applied successfully"); } } else { ManualLogSource obj2 = logger; if (obj2 != null) { obj2.LogInfo((object)"GrabbableObject patch disabled by config"); } } } catch (Exception ex) { ManualLogSource obj3 = logger; if (obj3 != null) { obj3.LogWarning((object)("Error applying GrabbableObject patch: " + ex.Message)); } } } private void ApplyHUDManagerPatch() { try { if (enableHUDManagerPatch != null && enableHUDManagerPatch.Value) { harmony.PatchAll(typeof(HUDManagerPatcher)); ManualLogSource obj = logger; if (obj != null) { obj.LogInfo((object)"HUDManager patch applied successfully"); } } else { ManualLogSource obj2 = logger; if (obj2 != null) { obj2.LogInfo((object)"HUDManager patch disabled by config"); } } } catch (Exception ex) { ManualLogSource obj3 = logger; if (obj3 != null) { obj3.LogWarning((object)("Error applying HUDManager patch: " + ex.Message)); } } } private void ApplyTerminalPatch() { try { if (enableTerminalPatch != null && enableTerminalPatch.Value) { harmony.PatchAll(typeof(TerminalPatch)); ManualLogSource obj = logger; if (obj != null) { obj.LogInfo((object)"Terminal patch applied successfully"); } } else { ManualLogSource obj2 = logger; if (obj2 != null) { obj2.LogInfo((object)"Terminal patch disabled by config"); } } } catch (Exception ex) { ManualLogSource obj3 = logger; if (obj3 != null) { obj3.LogWarning((object)("Error applying Terminal patch: " + ex.Message)); } } } private void ApplyInteractiveTerminalAPIPatch() { try { if (shouldTranslateInteractiveTerminalAPI != null && shouldTranslateInteractiveTerminalAPI.Value) { harmony.PatchAll(typeof(InteractiveTerminalAPIPatch)); ManualLogSource obj = logger; if (obj != null) { obj.LogInfo((object)"InteractiveTerminalAPI patch applied successfully"); } InteractiveTerminalAPIPatch.Initialize(); } else { ManualLogSource obj2 = logger; if (obj2 != null) { obj2.LogInfo((object)"InteractiveTerminalAPI patch disabled by config"); } } } catch (Exception ex) { ManualLogSource obj3 = logger; if (obj3 != null) { obj3.LogWarning((object)("Error applying InteractiveTerminalAPI patch: " + ex.Message)); } } } } } namespace GameTranslator.Patches { [HarmonyPatch(typeof(GrabbableObject))] internal class GrabbableObjectPatcher { public static Dictionary<string, string> originToTranslated = new Dictionary<string, string>(); public static HashSet<string> translatedItems = new HashSet<string>(); [HarmonyPostfix] [HarmonyPatch("Start")] private static void start(ref GrabbableObject __instance) { if (!((Object)(object)__instance.itemProperties != (Object)null) || !TranslatePlugin.shouldTranslateItems.Value || Utility.IsNullOrWhiteSpace(__instance.itemProperties.itemName)) { return; } if (!translatedItems.Contains(__instance.itemProperties.itemName)) { originToTranslated[__instance.itemProperties.itemName] = TranslateConfig.replaceByMap(__instance.itemProperties.itemName, TranslateConfig.items); translatedItems.Add(originToTranslated[__instance.itemProperties.itemName]); __instance.itemProperties.itemName = originToTranslated[__instance.itemProperties.itemName]; } if (originToTranslated.ContainsKey(__instance.itemProperties.itemName)) { __instance.itemProperties.itemName = originToTranslated[__instance.itemProperties.itemName]; } ScanNodeProperties componentInChildren = ((Component)__instance).GetComponentInChildren<ScanNodeProperties>(); if ((Object)(object)componentInChildren != (Object)null && componentInChildren.headerText != null) { if (!translatedItems.Contains(componentInChildren.headerText)) { originToTranslated[componentInChildren.headerText] = TranslateConfig.replaceByMap(componentInChildren.headerText, TranslateConfig.items); translatedItems.Add(originToTranslated[componentInChildren.headerText]); componentInChildren.headerText = originToTranslated[componentInChildren.headerText]; } if (originToTranslated.ContainsKey(componentInChildren.headerText)) { componentInChildren.headerText = originToTranslated[componentInChildren.headerText]; } } } } [HarmonyPatch(typeof(HUDManager))] internal class HUDManagerPatcher { public static FieldInfo scanNodes = typeof(HUDManager).GetField("scanNodes", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); public static FieldInfo nodesOnScreen = typeof(HUDManager).GetField("nodesOnScreen", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); public static Dictionary<string, string> originToTranslated = new Dictionary<string, string>(); public static HashSet<string> translatedItems = new HashSet<string>(); private static string lastChat = ""; private static readonly BindingFlags All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static MethodInfo CanTipDisplay = typeof(HUDManager).GetMethod("CanTipDisplay", All); private static MethodInfo StopCoroutine = typeof(HUDManager).GetMethod("StopCoroutine", All, null, new Type[1] { typeof(IEnumerator) }, null); private static MethodInfo StartCoroutine = typeof(HUDManager).GetMethod("StartCoroutine", All, null, new Type[1] { typeof(IEnumerator) }, null); private static MethodInfo TipsPanelTimer = typeof(HUDManager).GetMethod("TipsPanelTimer", All); private static FieldInfo tipsPanelCoroutine = typeof(HUDManager).GetField("tipsPanelCoroutine", All); [HarmonyPostfix] [HarmonyPatch("Start")] private static void start(ref HUDManager __instance) { } [HarmonyPostfix] [HarmonyPatch("Update")] private static void update(HUDManager __instance) { if (!TranslatePlugin.shouldTranslateHUD.Value) { return; } string text = ((TMP_Text)__instance.chatText).text; if (lastChat.Length != text.Length) { string text2 = TranslateConfig.hudText.TryTranslate(text); if (!string.IsNullOrEmpty(text2) && text2 != text) { ((TMP_Text)__instance.chatText).text = text2; } } lastChat = text; } private static void fadeText(TextMeshProUGUI text, ref bool fade, float duration, float newAlpha) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) float a = ((Graphic)text).color.a; float num = 0f; fade = true; while (num < duration) { num += Time.deltaTime; float num2 = Mathf.Lerp(a, newAlpha, num / duration); ((Graphic)text).color = new Color(((Graphic)text).color.r, ((Graphic)text).color.g, ((Graphic)text).color.b, num2); } ((Behaviour)text).enabled = false; fade = false; } [HarmonyPostfix] [HarmonyPatch("AddChatMessage")] private static void changeChatMessage(HUDManager __instance, string chatMessage, string nameOfUserWhoTyped) { if (!TranslatePlugin.shouldTranslateHUD.Value || (Object)(object)__instance == (Object)null || ((object)__instance).Equals((object?)null)) { return; } TMP_Text chatText = (TMP_Text)(object)__instance.chatText; if ((Object)(object)chatText == (Object)null || ((object)chatText).Equals((object?)null) || !((Component)chatText).gameObject.activeInHierarchy) { return; } try { string text = chatText.text; if (!string.IsNullOrEmpty(text)) { string text2 = TranslateConfig.hudText.TryTranslate(text); if (!string.IsNullOrEmpty(text2) && !text2.Equals(text)) { chatText.text = text2; } } } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error in changeChatMessage: " + ex.Message)); } } [HarmonyPrefix] [HarmonyPatch("DisplayTip")] [HarmonyPriority(int.MaxValue)] private static bool changeTip(HUDManager __instance, ref string headerText, ref string bodyText, bool isWarning = false, bool useSave = false, string prefsKey = "LC_Tip1") { if (TranslatePlugin.shouldTranslateHUD.Value) { headerText = TranslateConfig.hudText.TryTranslate(headerText); bodyText = TranslateConfig.hudText.TryTranslate(bodyText); } return true; } [HarmonyPrefix] [HarmonyPatch("DisplayGlobalNotification")] [HarmonyPriority(int.MaxValue)] private static bool changeNotification(HUDManager __instance, ref string displayText) { if (TranslatePlugin.shouldTranslateHUD.Value) { displayText = TranslateConfig.hudText.TryTranslate(displayText); } return true; } } [HarmonyPatch(typeof(Terminal))] internal class TerminalPatch { public class Translator { public Dictionary<string, TerminalKeyword> keyValuePairs = new Dictionary<string, TerminalKeyword>(); public Dictionary<string, string> keys = new Dictionary<string, string>(); public TerminalKeyword getTranslateKey(TerminalKeyword old, bool useC) { if (!keyValuePairs.ContainsKey(((Object)old).name + old.word)) { if ((useC && !HaveChineseCMD(old.word)) || (!useC && !HavePinyinCMD(old.word))) { keyValuePairs.Add(((Object)old).name + old.word, old); return old; } string text = (useC ? getChineseCMD(old.word) : getPinyinCMD(old.word)); if (text == old.word) { keyValuePairs.Add(((Object)old).name + old.word, old); return old; } TerminalKeyword val = Copy(old); val.word = text; keyValuePairs.Add(((Object)old).name + old.word, val); if (val.compatibleNouns != null) { List<CompatibleNoun> list = val.compatibleNouns.ToList(); CompatibleNoun[] compatibleNouns = val.compatibleNouns; foreach (CompatibleNoun val2 in compatibleNouns) { if (!getTranslateKey(val2.noun, useC).word.Equals(val2.noun.word)) { CompatibleNoun val3 = TransReflection<CompatibleNoun>(val2); val3.noun = getTranslateKey(val2.noun, useC); list.Add(val3); } } val.compatibleNouns = list.ToArray(); } if ((Object)(object)val.defaultVerb != (Object)null && val.defaultVerb.compatibleNouns != null) { List<CompatibleNoun> list2 = val.defaultVerb.compatibleNouns.ToList(); CompatibleNoun[] compatibleNouns2 = val.defaultVerb.compatibleNouns; foreach (CompatibleNoun val4 in compatibleNouns2) { if (!getTranslateKey(val4.noun, useC).word.Equals(val4.noun.word)) { CompatibleNoun val5 = TransReflection<CompatibleNoun>(val4); val5.noun = getTranslateKey(val4.noun, useC); list2.Add(val5); } } val.defaultVerb.compatibleNouns = list2.ToArray(); } return val; } return keyValuePairs[((Object)old).name + old.word]; } public void getTranslateKey(TerminalKeyword old) { if (keys.ContainsKey(old.word)) { return; } keys.Add(old.word, old.word); if (old.compatibleNouns != null) { old.compatibleNouns.ToList(); CompatibleNoun[] compatibleNouns = old.compatibleNouns; foreach (CompatibleNoun val in compatibleNouns) { getTranslateKey(val.noun); } } if ((Object)(object)old.defaultVerb != (Object)null && old.defaultVerb.compatibleNouns != null) { old.defaultVerb.compatibleNouns.ToList(); CompatibleNoun[] compatibleNouns2 = old.defaultVerb.compatibleNouns; foreach (CompatibleNoun val2 in compatibleNouns2) { getTranslateKey(val2.noun); } } } } public static FieldInfo hasGottenVerb = typeof(Terminal).GetField("hasGottenVerb", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); public static FieldInfo hasGottenNoun = typeof(Terminal).GetField("hasGottenNoun", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); public static bool shouldTranslate = false; public static bool noText = false; public static int texdAdded = 0; public static FieldInfo modifyingText = typeof(Terminal).GetField("modifyingText", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); public static Dictionary<string, string> keyValuePairs = new Dictionary<string, string>(); public static TMP_InputField screenText = null; public static TextTranslationInfo info; public static HashSet<object> ig = new HashSet<object>(); private static int CheckForPlayerNameCommand(string firstWord, string secondWord) { if (firstWord == "radar") { return -1; } if (secondWord.Length <= 2) { return -1; } Debug.Log((object)("first word: " + firstWord + "; second word: " + secondWord)); List<string> list = new List<string>(); for (int i = 0; i < StartOfRound.Instance.mapScreen.radarTargets.Count; i++) { list.Add(StartOfRound.Instance.mapScreen.radarTargets[i].name); Debug.Log((object)$"name {i}: {list[i]}"); } secondWord = secondWord.ToLower(); for (int j = 0; j < list.Count; j++) { if (list[j].ToLower() == secondWord) { return j; } } Debug.Log((object)$"Target names length: {list.Count}"); for (int k = 0; k < list.Count; k++) { Debug.Log((object)"A"); string text = list[k].ToLower(); Debug.Log((object)$"Word #{k}: {text}; length: {text.Length}"); for (int num = secondWord.Length; num > 2; num--) { Debug.Log((object)$"c: {num}"); Debug.Log((object)secondWord.Substring(0, num)); if (text.StartsWith(secondWord.Substring(0, num))) { return k; } } } return -1; } [HarmonyPostfix] [HarmonyPatch("ParseWordOverrideOptions")] private static void ParseWordOverrideOptions(string playerWord, CompatibleNoun[] options, ref TerminalNode __result) { for (int i = 0; i < options.Length; i++) { for (int num = playerWord.Length; num > 0; num--) { if (getChineseCMD(options[i].noun.word).ToLower().StartsWith(playerWord.Substring(0, num).ToLower())) { __result = options[i].result; return; } if (getPinyinCMD(options[i].noun.word).ToLower().StartsWith(playerWord.Substring(0, num).ToLower())) { __result = options[i].result; return; } } } } [HarmonyPostfix] [HarmonyPatch("CheckForExactSentences")] private static void CheckForExactSentences(Terminal __instance, string playerWord, ref TerminalKeyword __result) { for (int i = 0; i < __instance.terminalNodes.allKeywords.Length; i++) { if (getChineseCMD(__instance.terminalNodes.allKeywords[i].word).EqualsIgnoreCase(playerWord)) { __result = __instance.terminalNodes.allKeywords[i]; break; } if (getPinyinCMD(__instance.terminalNodes.allKeywords[i].word).EqualsIgnoreCase(playerWord)) { __result = __instance.terminalNodes.allKeywords[i]; break; } } } private static string RemovePunctuation(string s) { StringBuilder stringBuilder = new StringBuilder(); foreach (char c in s) { if (!char.IsPunctuation(c)) { stringBuilder.Append(c); } } return stringBuilder.ToString().ToLower(); } [HarmonyPostfix] [HarmonyPatch("CallFunctionInAccessibleTerminalObject")] private static void CallFunctionInAccessibleTerminalObject(Terminal __instance, string word) { TerminalAccessibleObject[] array = Object.FindObjectsOfType<TerminalAccessibleObject>(); for (int i = 0; i < array.Length; i++) { if (getChineseCMD(array[i].objectCode).EqualsIgnoreCase(word) || getPinyinCMD(array[i].objectCode).EqualsIgnoreCase(word)) { Debug.Log((object)"Found accessible terminal object with corresponding string, calling function"); ((object)__instance).GetType().GetField("broadcastedCodeThisFrame", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(TranslateConfig.terminal, true); array[i].CallFunctionFromTerminal(); break; } } } [HarmonyPostfix] [HarmonyPatch("ParseWord")] private static void ParseWord(Terminal __instance, string playerWord, int specificityRequired, ref TerminalKeyword __result) { if (!TranslatePlugin.TerimalCanUseChinese.Value && !TranslatePlugin.TerimalCanUsePinyinAbbreviation.Value) { return; } if (playerWord.Length < specificityRequired) { __result = null; return; } TerminalKeyword val = null; for (int i = 0; i < __instance.terminalNodes.allKeywords.Length; i++) { if (__instance.terminalNodes.allKeywords[i].isVerb && (bool)hasGottenVerb.GetValue(__instance)) { continue; } bool accessTerminalObjects = __instance.terminalNodes.allKeywords[i].accessTerminalObjects; if (getChineseCMD(__instance.terminalNodes.allKeywords[i].word).EqualsIgnoreCase(playerWord)) { __result = __instance.terminalNodes.allKeywords[i]; return; } if (getPinyinCMD(__instance.terminalNodes.allKeywords[i].word).EqualsIgnoreCase(playerWord)) { __result = __instance.terminalNodes.allKeywords[i]; return; } if (!((Object)(object)val == (Object)null)) { continue; } for (int num = playerWord.Length; num > specificityRequired; num--) { if (getChineseCMD(__instance.terminalNodes.allKeywords[i].word).ToLower().StartsWith(playerWord.Substring(0, num).ToLower())) { val = __instance.terminalNodes.allKeywords[i]; } if (getPinyinCMD(__instance.terminalNodes.allKeywords[i].word).ToLower().StartsWith(playerWord.Substring(0, num).ToLower())) { val = __instance.terminalNodes.allKeywords[i]; } } } if ((Object)(object)val != (Object)null) { __result = val; } } [HarmonyPostfix] [HarmonyPatch("ParsePlayerSentence")] private static void customParser(Terminal __instance, ref TerminalNode __result) { string[] array = RemovePunctuation(__instance.screenText.text.Substring(__instance.screenText.text.Length - __instance.textAdded)).Split(Array.Empty<char>(), StringSplitOptions.RemoveEmptyEntries); if (array.Length > 1 && ((TranslatePlugin.TerimalCanUseChinese.Value && TranslateConfig.cmd_zh.normal.ContainsKey("transmit") && array[0].ToLower().Equals(TranslateConfig.cmd_zh.normal["transmit"])) || (TranslatePlugin.TerimalCanUsePinyinAbbreviation.Value && TranslateConfig.cmd_py.normal.ContainsKey("transmit") && array[0].ToLower().Equals(TranslateConfig.cmd_py.normal["transmit"])))) { try { string text = array[1]; SignalTranslator val = Object.FindObjectOfType<SignalTranslator>(); if ((Object)(object)val != (Object)null && Time.realtimeSinceStartup - val.timeLastUsingSignalTranslator > 8f && text.Length > 1) { if (!((NetworkBehaviour)__instance).IsServer) { val.timeLastUsingSignalTranslator = Time.realtimeSinceStartup; } __result = __instance.terminalNodes.specialNodes[22]; HUDManager.Instance.UseSignalTranslatorServerRpc(text.Substring(0, Mathf.Min(text.Length, 10))); } return; } catch (Exception ex) { TranslatePlugin.logger.LogError((object)ex.Message); return; } } if (array.Length > 1 && ((TranslatePlugin.TerimalCanUseChinese.Value && TranslateConfig.cmd_zh.normal.ContainsKey("switch") && array[0].ToLower().Equals(TranslateConfig.cmd_zh.normal["switch"])) || (TranslatePlugin.TerimalCanUsePinyinAbbreviation.Value && TranslateConfig.cmd_py.normal.ContainsKey("switch") && array[0].ToLower().Equals(TranslateConfig.cmd_py.normal["switch"])))) { int num = CheckForPlayerNameCommand(array[0], array[1]); if (num != -1) { StartOfRound.Instance.mapScreen.SwitchRadarTargetAndSync(num); __result = __instance.terminalNodes.specialNodes[20]; } } else if (array.Length > 1 && ((TranslatePlugin.TerimalCanUseChinese.Value && TranslateConfig.cmd_zh.normal.ContainsKey("ping") && array[0].ToLower().Equals(TranslateConfig.cmd_zh.normal["ping"])) || (TranslatePlugin.TerimalCanUsePinyinAbbreviation.Value && TranslateConfig.cmd_py.normal.ContainsKey("ping") && array[0].ToLower().Equals(TranslateConfig.cmd_py.normal["ping"])))) { int num2 = CheckForPlayerNameCommand(array[0], array[1]); if (num2 != -1) { StartOfRound.Instance.mapScreen.PingRadarBooster(num2); __result = __instance.terminalNodes.specialNodes[21]; } } else if (array.Length > 1 && ((TranslatePlugin.TerimalCanUseChinese.Value && TranslateConfig.cmd_zh.normal.ContainsKey("flash") && array[0].ToLower().Equals(TranslateConfig.cmd_zh.normal["flash"])) || (TranslatePlugin.TerimalCanUsePinyinAbbreviation.Value && TranslateConfig.cmd_py.normal.ContainsKey("flash") && array[0].ToLower().Equals(TranslateConfig.cmd_py.normal["flash"])))) { int num3 = CheckForPlayerNameCommand(array[0], array[1]); if (num3 != -1) { StartOfRound.Instance.mapScreen.FlashRadarBooster(num3); __result = __instance.terminalNodes.specialNodes[23]; } else if (StartOfRound.Instance.mapScreen.radarTargets[StartOfRound.Instance.mapScreen.targetTransformIndex].isNonPlayer) { StartOfRound.Instance.mapScreen.FlashRadarBooster(StartOfRound.Instance.mapScreen.targetTransformIndex); __result = __instance.terminalNodes.specialNodes[23]; } } } private static bool HaveChineseCMD(string name) { return TranslateConfig.cmd_zh.normal.ContainsKey(name); } private static bool HavePinyinCMD(string name) { return TranslateConfig.cmd_py.normal.ContainsKey(name); } private static string getChineseCMD(string name) { if (TranslatePlugin.TerimalCanUseChinese.Value && TranslateConfig.cmd_zh.normal.ContainsKey(name)) { return TranslateConfig.cmd_zh.normal[name]; } return ""; } private static string getPinyinCMD(string name) { if (TranslatePlugin.TerimalCanUsePinyinAbbreviation.Value && TranslateConfig.cmd_py.normal.ContainsKey(name)) { return TranslateConfig.cmd_py.normal[name]; } return ""; } private static string getOrginByChineseCMD(string name) { if (TranslateConfig.cmd_zh.special.ContainsKey(name)) { return TranslateConfig.cmd_zh.special[name]; } return name; } private static string getOrginByPinyinCMD(string name) { if (TranslateConfig.cmd_py.special.ContainsKey(name)) { return TranslateConfig.cmd_py.special[name]; } return name; } private static T TransReflection<T>(T tIn) { T val = Activator.CreateInstance<T>(); foreach (FieldInfo runtimeField in tIn.GetType().GetRuntimeFields()) { val.GetType().GetRuntimeField(runtimeField.Name).SetValue(val, runtimeField.GetValue(tIn)); } return val; } public static TerminalKeyword Copy(TerminalKeyword oldOne) { return TransReflection<TerminalKeyword>(oldOne); } [HarmonyPostfix] [HarmonyPatch("LoadNewNode")] private static void changeNewNodeText(Terminal __instance, TerminalNode node) { if (info != null) { info.Reset(__instance.screenText.text); } } [HarmonyPrefix] [HarmonyPatch("OnSubmit")] private static void changeSubmit(Terminal __instance) { if (info != null) { texdAdded = __instance.currentText.Length - info.OriginalText.Length; if (texdAdded != 0) { info.Reset(__instance.currentText); } } } [HarmonyPostfix] [HarmonyPatch("Update")] private static void changeUpdateText(Terminal __instance) { try { if ((info == null || !info.IsTranslated) && info != null && !info.IsTranslated && TranslatePlugin.shouldTranslateTerimal.Value) { if (TranslatePlugin.showAvailableText.Value && !string.IsNullOrEmpty(__instance.currentText) && TextTranslate.ShouldOutputDebug("terminal:" + __instance.currentText)) { TranslatePlugin.LogInfo("[Debug] Terminal available text: '" + __instance.currentText + "'"); } string translatedText = TranslateConfig.replaceByMap(__instance.currentText, TranslateConfig.terminal); info.SetTranslatedText(translatedText); SetText(info.TranslatedText, __instance); info.OriginalText = __instance.currentText; } } catch (Exception ex) { TranslatePlugin.logger.LogWarning((object)ex); } } public static void SetText(string text, Terminal Instance) { if (!((Object)(object)Instance == (Object)null)) { modifyingText.SetValue(Instance, true); ((Selectable)Instance.screenText).interactable = true; Instance.screenText.text = text; Instance.currentText = Instance.screenText.text; if ((Object)(object)Instance.screenText.verticalScrollbar != (Object)null) { Instance.screenText.verticalScrollbar.value = 0f; } } } [HarmonyPatch("Start")] [HarmonyPostfix] private static void startTerminal(Terminal __instance) { info = __instance.screenText.GetOrCreateTextTranslationInfo(); ig.Clear(); foreach (FieldInfo runtimeField in ((object)__instance).GetType().GetRuntimeFields()) { if (runtimeField.GetValue(__instance) != null && UnityTypes.TMP_Text.IsAssignableFrom(runtimeField.GetValue(__instance).GetType())) { ig.Add(runtimeField.GetValue(__instance)); } } info.MustIgnore = true; } } } namespace GameTranslator.Patches.Utils { internal static class FontCache { private static bool _hasReadFallbackFontTextMeshPro; private static Object FallbackFontTextMeshPro; public static object GetOrCreateFallbackFontTextMeshPro() { if (!_hasReadFallbackFontTextMeshPro) { try { _hasReadFallbackFontTextMeshPro = true; if (string.IsNullOrEmpty(TranslatePlugin.fallbackFontTextMeshPro.Value)) { FallbackFontTextMeshPro = null; return null; } string assetBundle = Path.Combine(TranslatePlugin.DefaultPath, TranslatePlugin.fallbackFontTextMeshPro.Value); FallbackFontTextMeshPro = FontHelper.GetTextMeshProFont(assetBundle); } catch (Exception ex) when (ex.ToString().ToLowerInvariant().Contains("missing") || ex.ToString().ToLowerInvariant().Contains("not found")) { TranslatePlugin.logger.LogWarning((object)("An error occurred while loading text mesh pro fallback font. This may be due to missing font file. Error: " + ex.Message)); } catch (Exception ex2) { TranslatePlugin.logger.LogError((object)("An error occurred while loading text mesh pro fallback font: " + TranslatePlugin.fallbackFontTextMeshPro.Value + ". Error: " + ex2.Message)); } } return FallbackFontTextMeshPro; } } internal static class FontHelper { private static readonly List<AssetBundle> _loadedBundles = new List<AssetBundle>(); public static Object GetTextMeshProFont(string assetBundle) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown if (string.IsNullOrEmpty(assetBundle)) { return null; } Object val = null; string text = Path.Combine(Paths.GameRoot, assetBundle); if (File.Exists(text)) { TranslatePlugin.logger.LogInfo((object)("Attempting to load TextMesh Pro font from asset bundle: " + text)); AssetBundle val2 = null; if (AssetBundle_Methods.LoadFromFile != null) { val2 = (AssetBundle)AssetBundle_Methods.LoadFromFile.Invoke((object)null, new object[1] { text }); } else { if (AssetBundle_Methods.CreateFromFile == null) { TranslatePlugin.logger.LogError((object)("Could not find an appropriate asset bundle load method while loading font: " + text)); return null; } val2 = (AssetBundle)AssetBundle_Methods.CreateFromFile.Invoke((object)null, new object[1] { text }); } if ((Object)(object)val2 == (Object)null) { TranslatePlugin.logger.LogWarning((object)("Could not load asset bundle while loading font: " + text)); return null; } _loadedBundles.Add(val2); if (UnityTypes.TMP_FontAsset != null) { if (AssetBundle_Methods.LoadAllAssets != null) { val = ((Object[])AssetBundle_Methods.LoadAllAssets.Invoke((object)val2, new object[1] { UnityTypes.TMP_FontAsset.UnityType }))?.FirstOrDefault(); } else if (AssetBundle_Methods.LoadAll != null) { val = ((Object[])AssetBundle_Methods.LoadAll.Invoke((object)val2, new object[1] { UnityTypes.TMP_FontAsset.UnityType }))?.FirstOrDefault(); } } } else { TranslatePlugin.logger.LogInfo((object)("Attempting to load TextMesh Pro font from internal Resources API: " + text)); val = Resources.Load(assetBundle); } if (val != (Object)null) { CachedProperty version = TMP_FontAsset_Properties.Version; string text2 = ((string)((version != null) ? version.Get((object)val) : null)) ?? "Unknown"; TranslatePlugin.logger.LogInfo((object)("Loaded TextMesh Pro font uses version: " + text2)); Object.DontDestroyOnLoad(val); } else { TranslatePlugin.logger.LogError((object)("Could not find the TextMeshPro font asset: " + assetBundle)); } return val; } public static string[] GetOSInstalledFontNames() { return Font.GetOSInstalledFontNames(); } public static void UnloadAllBundles() { foreach (AssetBundle loadedBundle in _loadedBundles) { try { if ((Object)(object)loadedBundle != (Object)null) { loadedBundle.Unload(true); } } catch (Exception ex) { TranslatePlugin.logger.LogError((object)("Error unloading bundle: " + ex.Message)); } } _loadedBundles.Clear(); } } public static class FontSupportChecker { private static readonly List<TMP_FontAsset> _availableFonts = new List<TMP_FontAsset>(); private static readonly Dictionary<char, bool> _characterSupportCache = new Dictionary<char, bool>(); private static readonly LRUCache<string, string> _textCache = new LRUCache<string, string>(1000); private static bool _isInitialized = false; private static readonly object _lockObject = new object(); public static void InitializeFonts() { if (_isInitialized) { return; } lock (_lockObject) { if (_isInitialized) { return; } _availableFonts.Clear(); _characterSupportCache.Clear(); _textCache.Clear(); object orCreateFallbackFontTextMeshPro = FontCache.GetOrCreateFallbackFontTextMeshPro(); TMP_FontAsset val = (TMP_FontAsset)((orCreateFallbackFontTextMeshPro is TMP_FontAsset) ? orCreateFallbackFontTextMeshPro : null); if ((Object)(object)val != (Object)null) { AddFont(val); } TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>(); TMP_FontAsset[] array2 = array; foreach (TMP_FontAsset val2 in array2) { if ((Object)(object)val2 != (Object)null && !_availableFonts.Contains(val2)) { AddFont(val2); } } _isInitialized = true; TranslatePlugin.logger.LogInfo((object)$"FontSupportChecker initialized with {_availableFonts.Count} fonts"); } } private static void AddFont(TMP_FontAsset font) { if ((Object)(object)font == (Object)null || _availableFonts.Contains(font)) { return; } _availableFonts.Add(font); if (font.fallbackFontAssetTable == null) { return; } foreach (TMP_FontAsset item in font.fallbackFontAssetTable) { if ((Object)(object)item != (Object)null && !_availableFonts.Contains(item)) { _availableFonts.Add(item); } } } public static void RegisterFont(TMP_FontAsset font) { if ((Object)(object)font == (Object)null) { return; } lock (_lockObject) { AddFont(font); _characterSupportCache.Clear(); _textCache.Clear(); TranslatePlugin.logger.LogDebug((object)("Registered new font: " + ((Object)font).name)); } } public static bool IsCharacterSupported(char character) { if (!_isInitialized) { return true; } if (_characterSupportCache.TryGetValue(character, out var value)) { return value; } value = _availableFonts.Any((TMP_FontAsset font) => (Object)(object)font != (Object)null && font.HasCharacter(character, false, false)); _characterSupportCache[character] = value; return value; } public static string ReplaceUnsupportedCharacters(string text) { if (string.IsNullOrEmpty(text) || !TranslatePlugin.replaceUnsupportedCharacters.Value) { return text; } if (!_isInitialized) { InitializeFonts(); } if (_textCache.TryGetValue(text, out string value)) { return value; } bool flag = true; foreach (char character in text) { if (!IsCharacterSupported(character)) { flag = false; break; } } if (flag) { _textCache.Add(text, text); return text; } StringBuilder stringBuilder = new StringBuilder(); bool flag2 = false; foreach (char c in text) { if (IsCharacterSupported(c)) { stringBuilder.Append(c); continue; } stringBuilder.Append('□'); flag2 = true; } string text2 = stringBuilder.ToString(); if (flag2 && TranslatePlugin.showOtherDebug.Value) { TranslatePlugin.logger.LogInfo((object)("[FontSupport] Replaced unsupported characters in text: '" + text + "' -> '" + text2 + "'")); } _textCache.Add(text, text2); return text2; } public static void ClearCache() { lock (_lockObject) { _characterSupportCache.Clear(); _textCache.Clear(); TranslatePlugin.logger.LogDebug((object)"FontSupportChecker cache cleared"); } } public static string GetStats() { return $"Fonts: {_availableFonts.Count}, CharacterCache: {_characterSupportCache.Count}, TextCache: {_textCache.Count}"; } } public class LRUCache<TKey, TValue> { private class CacheItem { public TKey Key { get; set; } public TValue Value { get; set; } } private readonly int _capacity; private readonly Dictionary<TKey, LinkedListNode<CacheItem>> _cacheMap; private readonly LinkedList<CacheItem> _lruList; public int Count => _cacheMap.Count; public LRUCache(int capacity) { _capacity = capacity; _cacheMap = new Dictionary<TKey, LinkedListNode<CacheItem>>(capacity); _lruList = new LinkedList<CacheItem>(); } public bool TryGetValue(TKey key, out TValue value) { if (_cacheMap.TryGetValue(key, out LinkedListNode<CacheItem> value2)) { value = value2.Value.Value; _lruList.Remove(value2); _lruList.AddFirst(value2); return true; } value = default(TValue); return false; } public void Add(TKey key, TValue value) { if (_cacheMap.TryGetValue(key, out LinkedListNode<CacheItem> value2)) { _lruList.Remove(value2); } else if (_cacheMap.Count >= _capacity) { RemoveLeastRecentlyUsed(); } LinkedListNode<CacheItem> linkedListNode = new LinkedListNode<CacheItem>(new CacheItem { Key = key, Value = value }); _lruList.AddFirst(linkedListNode); _cacheMap[key] = linkedListNode; } public void Clear() { _cacheMap.Clear(); _lruList.Clear(); } private void RemoveLeastRecentlyUsed() { LinkedListNode<CacheItem> last = _lruList.Last; if (last != null) { _cacheMap.Remove(last.Value.Key); _lruList.RemoveLast(); } } } internal static class SceneManagerLoader { public static void EnableSceneLoadScanInternal(Action<int> sceneLoaded) { Action<int> sceneLoaded2 = sceneLoaded; SceneManager.sceneLoaded += delegate(Scene arg1, LoadSceneMode arg2) { sceneLoaded2(((Scene)(ref arg1)).buildIndex); }; } } public class StringBuffer { private char[] value; private int length; private int capacity; private const int DEFAULT_CAPACITY = 16; public int Length { get { return length; } set { if (value < 0 || value > capacity) { throw new ArgumentOutOfRangeException("value"); } if (value < length) { Array.Clear(this.value, value, length - value); } length = value; } } public int Capacity { get { return capacity; } set { if (value < length) { throw new ArgumentOutOfRangeException("value"); } if (value != capacity) { char[] destinationArray = new char[value]; Array.Copy(this.value, 0, destinationArray, 0, length); this.value = destinationArray; capacity = value; } } } public StringBuffer() { value = new char[16]; length = 0; capacity = 16; } public StringBuffer(int capacity) { if (capacity < 0) { throw new ArgumentOutOfRangeException("capacity"); } value = new char[capacity]; length = 0; this.capacity = capacity; } public StringBuffer(string str) { if (str == null) { throw new ArgumentNullException("str"); } value = new char[str.Length + 16]; str.CopyTo(0, value, 0, str.Length); length = str.Length; capacity = str.Length + 16; } public void EnsureCapacity(int minimumCapacity) { if (minimumCapacity < 0) { throw new ArgumentOutOfRangeException("minimumCapacity"); } if (minimumCapacity > capacity) { int num = capacity * 2; if (num < minimumCapacity) { num = minimumCapacity; } Capacity = num; } } public StringBuffer Append(object obj) { if (obj == null) { return this; } return Append(obj.ToString()); } public StringBuffer Append(string str) { if (str == null) { return this; } int num = str.Length; EnsureCapacity(length + num); str.CopyTo(0, value, length, num); length += num; return this; } public StringBuffer Append(char c) { EnsureCapacity(length + 1); value[length] = c; length++; return this; } public StringBuffer Insert(int index, object obj) { if (obj == null) { return this; } return Insert(index, obj.ToString()); } public StringBuffer Insert(int index, string str) { if (index < 0 || index > length) { throw new ArgumentOutOfRangeException("index"); } if (str == null) { return this; } int num = str.Length; EnsureCapacity(length + num); Array.Copy(value, index, value, index + num, length - index); str.CopyTo(0, value, index, num); length += num; return this; } public StringBuffer Insert(int index, char c) { if (index < 0 || index > length) { throw new ArgumentOutOfRangeException("index"); } EnsureCapacity(length + 1); Array.Copy(value, index, value, index + 1, length - index); value[index] = c; length++; return this; } public StringBuffer Remove(int startIndex, int length) { if (startIndex < 0 || startIndex > this.length) { throw new ArgumentOutOfRangeException("startIndex"); } if (length < 0 || startIndex + length > this.length) { throw new ArgumentOutOfRangeException("length"); } Array.Copy(value, startIndex + length, value, startIndex, this.length - startIndex - length); Array.Clear(value, this.length - length, length); this.length -= length; return this; } public StringBuffer Replace(char oldChar, char newChar) { for (int i = 0; i < length; i++) { if (value[i] == oldChar) { value[i] = newChar; } } return this; } public StringBuffer Replace(string oldValue, string newValue) { if (oldValue == null) { throw new ArgumentNullException("oldValue"); } if (oldValue.Length == 0) { throw new ArgumentException("oldValue cannot be empty"); } if (newValue == null) { newValue = string.Empty; } int num = oldValue.Length; int num2 = newValue.Length; for (int num3 = IndexOf(oldValue); num3 >= 0; num3 = IndexOf(oldValue, num3 + num2)) { Remove(num3, num); Insert(num3, newValue); } return this; } public int IndexOf(char c) { return IndexOf(c, 0, length); } public int IndexOf(char c, int startIndex) { return IndexOf(c, startIndex, length - startIndex); } public int IndexOf(char c, int startIndex, int count) { if (startIndex < 0 || startIndex > length) { throw new ArgumentOutOfRangeException("startIndex"); } if (count < 0 || startIndex + count > length) { throw new ArgumentOutOfRangeException("count"); } return Array.IndexOf(value, c, startIndex, count); } public int IndexOf(string str) { return IndexOf(str, 0, length); } public int IndexOf(string str, int startIndex) { return IndexOf(str, startIndex, length - startIndex); } public int IndexOf(string str, int startIndex, int count) { if (str == null) { throw new ArgumentNullException("str"); } if (startIndex < 0 || startIndex > length) { throw new ArgumentOutOfRangeException("startIndex"); } if (count < 0 || startIndex + count > length) { throw new ArgumentOutOfRangeException("count"); } return value.ToString(startIndex, count).IndexOf(str); } public string Substring(int startIndex) { return Substring(startIndex, length - startIndex); } public string Substring(int startIndex, int length) { if (startIndex < 0 || startIndex > this.length) { throw new ArgumentOutOfRangeException("startIndex"); } if (length < 0 || startIndex + length > this.length) { throw new ArgumentOutOfRangeException("length"); } return new string(value, startIndex, length); } public bool Contains(string text) { return IndexOf(text) >= 0; } public StringBuffer ReplaceFull(string oldValue, string newValue) { if (oldValue == null) { throw new ArgumentNullException("oldValue"); } if (oldValue.Length == 0) { throw new ArgumentException("oldValue cannot be empty"); } if (newValue == null) { newValue = string.Empty; } int num = oldValue.Length; int num2 = newValue.Length; for (int num3 = IndexOfWord(oldValue); num3 >= 0; num3 = IndexOfWord(oldValue, num3 + num2)) { Remove(num3, num); Insert(num3, newValue); } return this; } private int IndexOfWord(string str) { return IndexOfWord(str, 0, length); } private int IndexOfWord(string str, int startIndex) { return IndexOfWord(str, startIndex, length - startIndex); } public int IndexOfWord(string str, int startIndex, int count) { if (str == null) { throw new ArgumentNullException("str"); } if (startIndex < 0 || startIndex > length) { throw new ArgumentOutOfRangeException("startIndex"); } if (count < 0 || startIndex + count > length) { throw new ArgumentOutOfRangeException("count"); } int num = str.Length; int[] array = new int[num]; int num2 = 0; computeLPSArray(str, num, array); int num3 = startIndex; while (num3 < startIndex + count) { if (str[num2] == value[num3]) { num2++; num3++; } if (num2 == num) { if ((num3 - num2 == 0 || !char.IsLetter(value[num3 - num2 - 1])) && (num3 == length || !char.IsLetter(value[num3]))) { return num3 - num2; } num2 = array[num2 - 1]; } else if (num3 < startIndex + count && str[num2] != value[num3]) { if (num2 != 0) { num2 = array[num2 - 1]; } else { num3++; } } } return -1; } private void computeLPSArray(string str, int M, int[] lps) { int num = 0; int num2 = 1; lps[0] = 0; while (num2 < M) { if (str[num2] == str[num]) { num = (lps[num2] = num + 1); num2++; } else if (num != 0) { num = lps[num - 1]; } else { lps[num2] = num; num2++; } } } private static bool IsWordChar(char c) { if (!char.IsLetter(c)) { return c == '_'; } return true; } public StringBuffer Clear() { Length = 0; return this; } public override string ToString() { return new string(value, 0, length); } } internal static class TextHelper { public static string Encode(string text) { return EscapeNewlines(text); } public static string[] ReadTranslationLineAndDecode(string str) { if (string.IsNullOrEmpty(str)) { return null; } string[] array = new string[2]; int num = 0; bool flag = false; int length = str.Length; StringBuilder stringBuilder = new StringBuilder((int)((double)length / 1.3)); for (int i = 0; i < length; i++) { char c = str[i]; if (flag) { char c2 = c; if (c2 <= '\\') { if (c2 != '=' && c2 != '\\') { stringBuilder.Append('\\'); stringBuilder.Append(c); flag = false; continue; } stringBuilder.Append(c); } else { switch (c2) { default: stringBuilder.Append('\\'); stringBuilder.Append(c); flag = false; continue; case 'u': { if (i + 4 >= length) { throw new Exception("Found invalid unicode in line: " + str); } if (i + 1 >= length || i + 2 >= length || i + 3 >= length || i + 4 >= length) { throw new Exception("Invalid unicode escape sequence at position " + i + " in line: " + str); } int num2 = int.Parse(new string(new char[4] { str[i + 1], str[i + 2], str[i + 3], str[i + 4] }), NumberStyles.HexNumber); stringBuilder.Append((char)num2); i += 4; break; } case 'r': stringBuilder.Append('\r'); break; case 'n': stringBuilder.Append('\n'); break; } } flag = false; continue; } switch (c) { case '\\': flag = true; break; case '=': if (num > 1) { return null; } array[num++] = stringBuilder.ToString(); stringBuilder.Length = 0; break; case '%': if (i + 2 < length && i + 1 < length && i + 2 < length && str[i + 1] == '3' && str[i + 2] == 'D') { stringBuilder.Append('='); i += 2; } else { stringBuilder.Append(c); } break; case '/': { int num3 = i + 1; if (num3 < length && num3 < length && str[num3] == '/') { array[num++] = stringBuilder.ToString(); if (num == 2) { return array; } return null; } stringBuilder.Append(c); break; } default: stringBuilder.Append(c); break; } } if (num != 1) { return null; } array[num++] = stringBuilder.ToString(); return array; } internal static string EscapeNewlines(string str) { if (str == null || str.Length == 0) { return ""; } int length = str.Length; StringBuilder stringBuilder = new StringBuilder(length + 4); int num = 0; while (num < length) { char c = str[num]; char c2 = c; if (c2 <= '\r') { switch (c2) { default: stringBuilder.Append(c); num++; continue; case '\r': stringBuilder.Append("\\r"); break; case '\n': stringBuilder.Append("\\n"); break; } } else { switch (c2) { default: stringBuilder.Append(c); num++; continue; case '\\': stringBuilder.Append('\\'); stringBuilder.Append(c); break; case '=': stringBuilder.Append('\\'); stringBuilder.Append(c); break; case '/': { int num2 = num + 1; if (num2 < length && str[num2] == '/') { stringBuilder.Append('\\'); stringBuilder.Append(c); stringBuilder.Append('\\'); stringBuilder.Append(c); num++; } else { stringBuilder.Append(c); } break; } } } num++; } return stringBuilder.ToString(); } } internal class TextTranslate { private static readonly Dictionary<string, DateTime> _debugOutputCache = new Dictionary<string, DateTime>(); private static readonly TimeSpan _debugOutputInterval = TimeSpan.FromSeconds(10.0); private static readonly TimeSpan _cacheCleanupInterval = TimeSpan.FromMinutes(5.0); private static DateTime _lastCleanupTime = DateTime.Now; public static TextTranslate Instance = new TextTranslate(); public static long ChangeTime = 0L; public static bool ShouldOutputDebug(string text) { if (!TranslatePlugin.showOtherDebug.Value && !TranslatePlugin.showAvailableText.Value) { return true; } DateTime now = DateTime.Now; if (now - _lastCleanupTime > _cacheCleanupInterval) { CleanupDebugCache(); _lastCleanupTime = now; } if (_debugOutputCache.TryGetValue(text, out var value) && now - value < _debugOutputInterval) { return false; } _debugOutputCache[text] = now; return true; } private static void CleanupDebugCache() { if (!TranslatePlugin.showOtherDebug.Value && !TranslatePlugin.showAvailableText.Value) { _debugOutputCache.Clear(); return; } DateTime now = DateTime.Now; List<string> list = new List<string>(); foreach (KeyValuePair<string, DateTime> item in _debugOutputCache) { if (now - item.Value > _cacheCleanupInterval) { list.Add(item.Key); } } foreach (string item2 in list) { _debugOutputCache.Remove(item2); } if (TranslatePlugin.showOtherDebug.Value && list.Count > 0) { TranslatePlugin.logger.LogInfo((object)$"[Debug] Cleaned up {list.Count} old debug cache entries"); } } internal void Hook_TextChanged(object ui) { if (TranslatePlugin.enableTerminalPatch != null && TranslatePlugin.enableTerminalPatch.Value) { try { Type type = Type.GetType("GameTranslator.Patches.TerminalPatch, GameTranslator"); if (type != null) { FieldInfo field = type.GetField("ig", BindingFlags.Static | BindingFlags.Public); if (field != null && field.GetValue(null) is HashSet<object> hashSet && hashSet.Contains(ui)) { return; } } } catch { } } TextTranslationInfo orCreateTextTranslationInfo = ui.GetOrCreateTextTranslationInfo(); bool ignoreComponentState = DiscoverComponent(ui, orCreateTextTranslationInfo); if (TranslatePlugin.shouldTranslateSpecialText.Value || TranslatePlugin.shouldTranslateNormalText.Value) { string text = ui.GetText(orCreateTextTranslationInfo); string text2 = TranslateOrQueue(ui, text, orCreateTextTranslationInfo, TranslateConfig.normalText, TranslateConfig.text, ignoreComponentState); if (!string.IsNullOrEmpty(text2) && !text2.Equals(text) && IsUIObjectValid(ui)) { SetText(ui, text2, isTranslated: true, text, orCreateTextTranslationInfo); } } } internal void Hook_TextChanged(object ui, ref string value) { if (TranslatePlugin.enableTerminalPatch != null && TranslatePlugin.enableTerminalPatch.Value) { try { Type type = Type.GetType("GameTranslator.Patches.TerminalPatch, GameTranslator"); if (type != null) { FieldInfo field = type.GetField("ig", BindingFlags.Static | BindingFlags.Public); if (field != null && field.GetValue(null) is HashSet<object> hashSet && hashSet.Contains(ui)) { return; } } } catch { } } TextTranslationInfo orCreateTextTranslationInfo = ui.GetOrCreateTextTranslationInfo(); bool ignoreComponentState = DiscoverComponent(ui, orCreateTextTranslationInfo); if (TranslatePlugin.shouldTranslateSpecialText.Value || TranslatePlugin.shouldTranslateNormalText.Value) { string text = TranslateOrQueue(ui, value, orCreateTextTranslationInfo, TranslateConfig.normalText, TranslateConfig.text, ignoreComponentState); if (!string.IsNullOrEmpty(text) && !text.Equals(value) && IsUIObjectValid(ui)) { value = text; } } } public string TranslateOrQueue(object ui, string text, TextTranslationInfo info, NormalTextTranslator normalText, TranslateConfig.TranslateConfigFile config, bool ignoreComponentState) { if (info != null && (info.IsCurrentlySettingText || info.MustIgnore)) { return null; } text = text ?? ui.GetText(info); if (Utility.IsNullOrWhiteSpace(text)) { return null; } if (info != null && info.IsTranslated) { if (!info.OriginalText.Equals(text) || info.ChangeTime != ChangeTime) { info.Reset(text); } else if (info.OriginalText.Equals(text) || info.TranslatedText.Equals(text)) { return info.TranslatedText; } } string cachedTranslation = AsyncTranslationManager.Instance.GetCachedTranslation(text, config); if (cachedTranslation != null) { if (TranslatePlugin.showOtherDebug.Value && ShouldOutputDebug("cached:" + text)) { TranslatePlugin.logger.LogInfo((object)("[Debug] Cached translation found for: '" + text + "' -> '" + cachedTranslation + "'")); } if (info != null) { info.OriginalText = text; info.SetTranslatedText(cachedTranslation); } if (!IsUIObjectValid(ui)) { return null; } try { if (info != null) { info.IsCurrentlySettingText = true; } ui.SetText(cachedTranslation, info); } catch (NullReferenceException) { } catch (IndexOutOfRangeException ex2) { TranslatePlugin.logger.LogError((object)("IndexOutOfRangeException in cached translation: " + ex2.Message)); } catch (Exception ex3) { TranslatePlugin.logger.LogError((object)("Exception in cached translation: " + ex3.Message)); } finally { if (info != null) { info.IsCurrentlySettingText = false; } } return cachedTranslation; } if (normalText == null || normalText.IsTranslatable(text, isToken: false)) { if (text.Length <= TranslatePlugin.syncTranslationThreshold.Value) { string text2 = TranslateImmediate(ui, text, info, normalText, config, ignoreComponentState); if (text2 != null) { AsyncTranslationManager.Instance.GetCachedTranslation(text, config); return text2; } } else { if (TranslatePlugin.showAvailableText.Value && ShouldOutputDebug("queued:" + text)) { TranslatePlugin.logger.LogInfo((object)("[Debug] Queued available text: '" + text + "'")); } AsyncTranslationManager.Instance.QueueTranslation(ui, text, info, normalText, config, ignoreComponentState); if (info != null && info.IsTranslated && info.TranslatedText != null) { return info.TranslatedText; } } } return null; } public string TranslateImmediate(object ui, string text, TextTranslationInfo info, NormalTextTranslator normalText, TranslateConfig.TranslateConfigFile config, bool ignoreComponentState) { if (info != null && (info.IsCurrentlySettingText || info.MustIgnore)) { return null; } text = text ?? ui.GetText(info); if (Utility.IsNullOrWhiteSpace(text)) { return null; } if (info != null && info.IsTranslated) { if (!info.OriginalText.Equals(text) || info.ChangeTime != ChangeTime) { info.Reset(text); } else if (info.OriginalText.Equals(text) || info.TranslatedText.Equals(text)) { return info.TranslatedText; } } string text2 = null; if ((normalText == null || normalText.IsTranslatable(text, isToken: false)) && (ignoreComponentState || ui.IsComponentActive())) { if (normalText != null && TranslatePlugin.shouldTranslateNormalText.Value) { if (TranslatePlugin.showAvailableText.Value && ShouldOutputDebug("available:" + text)) { TranslatePlugin.logger.LogInfo((object)("[Debug] Found available text: '" + text + "'")); } text2 = normalText.TryTranslate(text); } if (text2 != null) { text2 = TranslateConfig.replaceByMap(text2, config); SetTranslatedText(ui, text2, text, info); } } return text2; } internal void SetTranslatedText(object ui, string translatedText, string originalText, TextTranslationInfo info) { if (info != null) { info.OriginalText = originalText; info.SetTranslatedText(translatedText); } if (!IsUIObjectValid(ui)) { return; } try { if (info != null) { info.IsCurrentlySettingText = true; } ui.SetText(translatedText, info); } catch (NullReferenceException) { } catch (IndexOutOfRangeException ex2) { TranslatePlugin.logger.LogError((object)("IndexOutOfRangeException in SetTranslatedText: " + ex2.Message)); } catch (Exception ex3) { TranslatePlugin.logger.LogError((object)("Exception in SetTranslatedText: " + ex3.Message)); } finally { if (info != null) { info.IsCurrentlySettingText = false; } } } private void SetText(object ui, string text, bool isTranslated, string originalText, TextTranslationInfo info) { if ((info != null && info.IsCurrentlySettingText) || !IsUIObjectValid(ui)) { return; } try { if (info != null) { info.IsCurrentlySettingText = true; } ui.SetText(text, info); } catch (NullReferenceException) { } catch (IndexOutOfRangeException ex2) { TranslatePlugin.logger.LogError((object)("IndexOutOfRangeException in SetText: " + ex2.Message)); } catch (Exception ex3) { TranslatePlugin.logger.LogError((object)("Exception in SetText: " + ex3.Message)); } finally { if (info != null) { info.IsCurrentlySettingText = false; } } } private bool IsUIObjectValid(object ui) { if (ui == null) { return false; } try { Component val = (Component)((ui is Component) ? ui : null); if (val != null && Object.op_Implicit((Object)(object)val)) { GameObject gameObject = val.gameObject; if (Object.op_Implicit((Object)(object)gameObject)) { Behaviour val2 = (Behaviour)(object)((val is Behaviour) ? val : null); if (val2 != null) { return gameObject.activeInHierarchy && val2.enabled; } return gameObject.activeInHierarchy; } } return ui != null; } catch { return false; } } public bool DiscoverComponent(object ui, TextTranslationInfo info) { if (info != null && TranslatePlugin.changeFont.Value) { try { bool flag = ui.IsComponentActive(); if (TranslatePlugin.fallbackFontTextMeshPro.Value != null && flag) { info.ChangeFont(ui); return true; } return flag; } catch (Exception ex) { ManualLogSource logger = TranslatePlugin.logger; string text = "An error occurred while processing the UI."; string newLine = Environment.NewLine; logger.LogWarning((object)(text + newLine + ex)); } return false; } return true; } } public class TextureTranslate { public static TextureTranslate Instance = new TextureTranslate(); public static bool ImageHooksEnabled = true; internal void Hook_ImageChangedOnComponent(object source, ref Texture2D texture, bool isPrefixHooked, bool onEnable = false) { if (ImageHooksEnabled && TranslatePlugin.changeTexture.Value && source.IsKnownImageType()) { Sprite sprite = null; HandleImage(source, ref sprite, ref texture, isPrefixHooked); } } internal void Hook_ImageChangedOnComponent(object source, ref Sprite sprite, ref Texture2D texture, bool isPrefixHooked, bool onEnable) { if (ImageHooksEnabled && TranslatePlugin.changeTexture.Value && source.IsKnownImageType()) { HandleImage(source, ref sprite, ref texture, isPrefixHooked); } } internal void Hook_ImageChanged(ref Texture2D texture, bool isPrefixHooked) { if (ImageHooksEnabled && TranslatePlugin.changeTexture.Value && !((Object)(object)texture == (Object)null)) { Sprite sprite = null; HandleImage(null, ref sprite, ref texture, isPrefixHooked); } } public void HandleImage(object source, ref Sprite sprite, ref Texture2D texture, bool isPrefixHooked) { try { if (ShouldProcessTexture(source, texture)) { TranslateTexture(source, ref sprite, ref texture, isPrefixHooked); } } catch (Exception ex) { XuaLogger.AutoTranslator.Error(ex, "An error occurred while translating texture."); } } private void TranslateTexture(object source, ref Sprite sprite, ref Texture2D texture, bool isPrefixHooked) { try { ImageHooksEnabled = false; Texture2D val = texture; texture = texture ?? source.GetTexture(); if ((Object)(object)texture == (Object)null) { return; } TextureTranslationInfo orCreateTextureTranslationInfo = texture.GetOrCreateTextureTranslationInfo(); string key = orCreateTextureTranslationInfo.GetKey(); if (string.IsNullOrEmpty(key)) { return; } if (TranslateConfig.cache != null) { TranslateConfig.cache.UpdateTextureStatistics(key); } if (TranslateConfig.cache.TryGetTranslatedImage(key, out byte[] data, out TranslatedImage image)) { bool flag = texture.IsCompatible(image.ImageFormat); if (!orCreateTextureTranslationInfo.IsTranslated) { try { if (flag) { texture.LoadImageEx(data, image.ImageFormat, null); } else { orCreateTextureTranslationInfo.CreateTranslatedTexture(data, image.ImageFormat); } } finally { orCreateTextureTranslationInfo.IsTranslated = true; } } if (source != null && !orCreateTextureTranslationInfo.IsTranslated) { if (!flag) { Sprite val2 = source.SetTexture(orCreateTextureTranslationInfo.Translated, sprite, isPrefixHooked); if ((Object)(object)val2 != (Object)null) { orCreateTextureTranslationInfo.TranslatedSprite = val2; if (isPrefixHooked && (Object)(object)sprite != (Object)null) { sprite = val2; } } } if (!isPrefixHooked) { source.SetAllDirtyEx(); } } } if ((Object)(object)val == (Object)null) { texture = null; } else if (orCreateTextureTranslationInfo.UsingReplacedTexture) { if (orCreateTextureTranslationInfo.IsTranslated) { Texture2D translated = orCreateTextureTranslationInfo.Translated; if ((Object)(object)translated != (Object)null) { texture = translated; } } else { Texture2D target = orCreateTextureTranslationInfo.Original.Target; if ((Object)(object)target != (Object)null) { texture = target; } } } else { texture = val; } } catch (FileNotFoundException ex) { XuaLogger.AutoTranslator.Warn("Texture file not found: " + ex.FileName); } catch (FormatException ex2) { XuaLogger.AutoTranslator.Error((Exception)ex2, "Invalid image format."); } catch (Exception ex3) { XuaLogger.AutoTranslator.Error(ex3, "An unexpected error occurred while translating texture."); } finally { ImageHooksEnabled = true; } } private bool ShouldProcessTexture(object source, Texture2D texture) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected I4, but got Unknown if ((Object)(object)texture == (Object)null && source == null) { return false; } if ((Object)(object)texture != (Object)null) { TextureTranslationInfo orCreateTextureTranslationInfo = texture.GetOrCreateTextureTranslationInfo(); if (orCreateTextureTranslationInfo.IsTranslated && (Object)(object)orCreateTextureTranslationInfo.Translated != (Object)null) { return false; } int num = (int)texture.format; if (num == 1 || num == 9 || num == 63) { return false; } } return true; } private bool IsTextureFormatCompatible(Texture2D texture, TranslateExtensions.ImageFormat format) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 TextureFormat format2 = texture.format; switch (format) { case TranslateExtensions.ImageFormat.TGA: if ((int)format2 != 5 && (int)format2 != 4) { return (int)format2 == 3; } return true; default: return false; case TranslateExtensions.ImageFormat.PNG: return true; } } } internal static class TranslationScopeHelper { internal static class TranslationScopes { public const int None = -1; } public static bool EnableTranslationScoping = true; public static int GetScope(object ui) { if (EnableTranslationScoping) { try { Component val = (Component)((ui is Component) ? ui : null); if ((Object)(object)val != (Object)null && Object.op_Implicit((Object)(object)val)) { return GetScopeFromComponent(val); } if (ui is GUIContent) { return -1; } return GetActiveSceneId(); } catch (MissingMemberException ex) { XuaLogger.AutoTranslator.Error((Exception)ex, "A 'missing member' error occurred while retriving translation scope. Disabling translation scopes."); EnableTranslationScoping = false; } return -1; } return -1; } private static int GetScopeFromComponent(Component component) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) Scene scene = component.gameObject.scene; return ((Scene)(ref scene)).buildIndex; } public static int GetActiveSceneId() { if (UnityFeatures.SupportsSceneManager) { return GetActiveSceneIdBySceneManager(); } return GetActiveSceneIdByApplication(); } private static int GetActiveSceneIdBySceneManager() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).buildIndex; } public static void RegisterSceneLoadCallback(Action<int> sceneLoaded) { Action<int> sceneLoaded2 = sceneLoaded; SceneManager_Methods.add_sceneLoaded(delegate(Scene scene, LoadSceneMode mode) { sceneLoaded2(((Scene)(ref scene)).buildIndex); }); SceneManagerLoader.EnableSceneLoadScanInternal(sceneLoaded2); } private static int GetActiveSceneIdByApplication() { return Application.loadedLevel; } } } namespace GameTranslator.Patches.Utils.Textures { internal interface ITextureLoader { void Load(Texture2D texture, byte[] data); bool Verify(); } internal class LoadImageImageLoader : ITextureLoader { public void Load(Texture2D texture, byte[] data) { if (ImageConversion_Methods.LoadImage != null) { ImageConversion_Methods.LoadImage(texture, data, arg3: false); } else if (Texture2D_Methods.LoadImage != null) { Texture2D_Methods.LoadImage(texture, data); } } public bool Verify() { if (Texture2D_Methods.LoadImage == null) { return ImageConversion_Methods.LoadImage != null; } return true; } } internal static class TextureLoader { private static readonly Dictionary<TranslateExtensions.ImageFormat, ITextureLoader> Loaders; static TextureLoader() { Loaders = new Dictionary<TranslateExtensions.ImageFormat, ITextureLoader>(); Register(TranslateExtensions.ImageFormat.PNG, new LoadImageImageLoader()); Register(TranslateExtensions.ImageFormat.TGA, new TgaImageLoader()); } public static bool Register(TranslateExtensions.ImageFormat format, ITextureLoader loader) { try { if (loader.Verify()) { Loaders[format] = loader; return true; } } catch (Exception ex) { XuaLogger.AutoTranslator.Warn(ex, "An image loader could not be registered."); } return false; } public static void Load(Texture2D texture, byte[] data, TranslateExtensions.ImageFormat imageFormat) { if (Loaders.TryGetValue(imageFormat, out ITextureLoader value)) { value.Load(texture, data); } } } internal class TgaImageLoader : ITextureLoader { public void Load(Texture2D texture, byte[] data) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Invalid comparison between Unknown and I4 //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)texture == (Object)null && data == null) { return; } TextureFormat format = texture.format; using MemoryStream input = new MemoryStream(data); using BinaryReader binaryReader = new BinaryReader(input); binaryReader.BaseStream.Seek(12L, SeekOrigin.Begin); short num = binaryReader.ReadInt16(); short num2 = binaryReader.ReadInt16(); int num3 = binaryReader.ReadByte(); binaryReader.BaseStream.Seek(1L, SeekOrigin.Current); Color32[] array = (Color32[])(object)new Color32[num * num2]; if ((int)format == 3) { if (num3 == 32) { for (int i = 0; i < num * num2; i++) { byte b = binaryReader.ReadByte(); byte b2 = binaryReader.ReadByte(); byte b3 = binaryReader.ReadByte(); binaryReader.ReadByte(); array[i] = new Color32(b3, b2, b, byte.MaxValue); } } else { for (int j = 0; j < num * num2; j++) { byte b4 = binaryReader.ReadByte(); byte b5 = binaryReader.ReadByte(); byte b6 = binaryReader.ReadByte(); array[j] = new Color32(b6, b5, b4, byte.MaxValue); } } } else if (num3 == 32) { for (int k = 0; k < num * num2; k++) { byte b7 = binaryReader.ReadByte(); byte b8 = binaryReader.ReadByte(); byte b9 = binaryReader.ReadByte(); byte b10 = binaryReader.ReadByte(); array[k] = new Color32(b9, b8, b7, b10); } } else { for (int l = 0; l < num * num2; l++) { byte b11 = binaryReader.ReadByte(); byte b12 = binaryReader.ReadByte(); byte b13 = binaryReader.ReadByte(); array[l] = new Color32(b13, b12, b11, byte.MaxValue); } } texture.SetPixels32(array); texture.Apply(); } public bool Verify() { Load(null, null); return true; } } } namespace GameTranslator.Patches.Translatons { internal class AsyncTranslationManager { private class TranslationTask { public object UI { get; set; } public string OriginalText { get; set; } public TextTranslationInfo Info { get; set; }