Decompiled source of White Knuckle Russian Localization v2.1.6
plugins/WKLoc/WKLoc.Core.dll
Decompiled a month agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("WKLoc.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+de287f2d82518f94b32be74ee68529cb9c9f954e")] [assembly: AssemblyProduct("WKLoc.Core")] [assembly: AssemblyTitle("WKLoc.Core")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [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] [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] [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 WKLoc.Core { public static class AnchorExtractor { private const int MinLength = 4; public static string FromTemplateKey(string key) { if (string.IsNullOrEmpty(key)) { return string.Empty; } string text = string.Empty; string[] array = Regex.Split(key, "\\{\\d+\\}"); foreach (string text2 in array) { if (text2.Length > text.Length) { text = text2; } } if (text.Length < 4) { return string.Empty; } return text; } public static string FromRegex(string pattern) { if (string.IsNullOrEmpty(pattern)) { return string.Empty; } if (Regex.IsMatch(pattern, "\\(\\?[a-zA-Z-]*[iI]")) { return string.Empty; } char c = '\\'; string best = string.Empty; StringBuilder run = new StringBuilder(); int num = 0; int num2 = 0; for (int i = 0; i < pattern.Length; i++) { char c2 = pattern[i]; if (num2 > 0) { if (c2 == c) { i++; } else if (c2 == ']') { num2--; } continue; } if (num > 0) { if (c2 == c) { i++; continue; } switch (c2) { case '[': num2++; break; case '(': num++; break; case ')': num--; break; } continue; } switch (c2) { case '|': return string.Empty; case '(': EndRun(); num = 1; continue; case '[': EndRun(); num2 = 1; continue; case '{': DropLastAndEndRun(); for (; i + 1 < pattern.Length && pattern[i + 1] != '}'; i++) { } i++; continue; case '*': case '?': DropLastAndEndRun(); continue; case '+': EndRun(); continue; case '$': case '.': case '^': EndRun(); continue; case ')': case ']': case '}': return string.Empty; } if (c2 == c) { if (i + 1 >= pattern.Length) { return string.Empty; } char c3 = pattern[i + 1]; i++; if (char.IsLetterOrDigit(c3)) { EndRun(); } else { run.Append(c3); } } else { run.Append(c2); } } EndRun(); if (best.Length < 4) { return string.Empty; } return best; void DropLastAndEndRun() { if (run.Length > 0) { run.Length--; } EndRun(); } void EndRun() { if (run.Length > best.Length) { best = run.ToString(); } run.Length = 0; } } } public sealed class AutoGeneratedTextWriter : IDisposable { private readonly string _langDir; private readonly TimeSpan _delay; private readonly Func<string, TranslationData?> _deserialize; private readonly Func<TranslationData, string> _serialize; private readonly Action<Exception>? _onError; private readonly Action<int, string>? _onWrote; private readonly object _gate = new object(); private readonly object _flushGate = new object(); private readonly HashSet<string> _pending = new HashSet<string>(StringComparer.Ordinal); private readonly AutoResetEvent _signal = new AutoResetEvent(initialState: false); private Thread? _worker; private bool _started; private volatile bool _disposed; public AutoGeneratedTextWriter(string langDir, TimeSpan delay, Func<string, TranslationData?> deserialize, Func<TranslationData, string> serialize, Action<Exception>? onError = null, Action<int, string>? onWrote = null) { _langDir = langDir; _delay = ((delay < TimeSpan.Zero) ? TimeSpan.Zero : delay); _deserialize = deserialize; _serialize = serialize; _onError = onError; _onWrote = onWrote; } public void Enqueue(string key) { if (string.IsNullOrEmpty(key)) { return; } lock (_gate) { if (_disposed) { return; } _pending.Add(key); EnsureWorker(); } _signal.Set(); } private void EnsureWorker() { if (!_started) { _started = true; _worker = new Thread(WorkerLoop) { IsBackground = true, Name = "WKLoc-AutoGenWriter" }; _worker.Start(); } } private void WorkerLoop() { while (true) { _signal.WaitOne(); if (_disposed) { break; } if (_delay > TimeSpan.Zero) { Thread.Sleep(_delay); } Flush(); } } public void Flush() { lock (_flushGate) { FlushCore(); } } private void FlushCore() { while (true) { List<string> list; lock (_gate) { if (_pending.Count == 0) { break; } list = new List<string>(_pending); _pending.Clear(); } try { WriteBatch(list); } catch (Exception obj) { lock (_gate) { foreach (string item in list) { _pending.Add(item); } } _onError?.Invoke(obj); break; } } } private void WriteBatch(List<string> batch) { if (batch.Count == 0) { return; } Directory.CreateDirectory(_langDir); string text = Path.Combine(_langDir, "_AutoGenerated.json"); TranslationData translationData = new TranslationData(); if (File.Exists(text)) { TranslationData translationData2 = _deserialize(File.ReadAllText(text)); if (translationData2 != null) { translationData = translationData2; } } int num = 0; foreach (string item in batch) { if (!translationData.Exact.ContainsKey(item)) { translationData.Exact[item] = ""; num++; } } if (num != 0) { File.WriteAllText(text, _serialize(translationData)); _onWrote?.Invoke(num, text); } } public void Dispose() { lock (_gate) { if (_disposed) { return; } _disposed = true; } _signal.Set(); try { _worker?.Join(2000); } catch { } Flush(); _signal.Dispose(); } } public sealed class BatchCursor { public int Length { get; private set; } public int Index { get; private set; } public bool HasRemaining => Index < Length; public void Reset(int length) { Length = ((length >= 0) ? length : 0); Index = 0; } public bool Step(int batchSize, out int start, out int end) { if (Length <= 0) { start = 0; end = 0; return true; } if (Index >= Length) { start = Length; end = Length; return true; } start = Index; end = Math.Min(Index + Math.Max(1, batchSize), Length); Index = end; return Index >= Length; } } public static class BudgetMath { private const float BootstrapMs = 1f; private const int MinSamples = 5; private const double AbsFloorMs = 0.5; private const double FloorFraction = 0.05; private const double CeilingFraction = 0.5; public static float ComputeBudgetMs(float[] frameMs, int count) { if (count < 5) { return 1f; } double num = 0.0; double num2 = 0.0; for (int i = 0; i < count; i++) { float num3 = frameMs[i]; num += (double)num3; num2 += (double)num3 * (double)num3; } double num4 = num / (double)count; double num5 = num2 / (double)count - num4 * num4; double num6 = ((num5 > 0.0) ? Math.Sqrt(num5) : 0.0); double num7 = num4 * 0.05; double num8 = ((num6 > num7) ? num6 : num7); double num9 = num4 * 0.5; return (float)((num8 < 0.5) ? 0.5 : ((num8 > num9) ? num9 : num8)); } } public static class CollectFilter { private static readonly Regex Garbage = new Regex("^[.\\-_~=\\s\\/\\\\|:;()\\[\\]{}*+?!@#$%^&<>,\"']*$", RegexOptions.Compiled); private static readonly Regex Cyrillic = new Regex("[а-яА-ЯёЁ]", RegexOptions.Compiled); private static readonly Regex PureNumber = new Regex("^[+-]?\\d+(?:[.,]\\d+)?$", RegexOptions.Compiled); private static readonly Regex Timing = new Regex("^\\d{1,2}(?::\\d{1,2}){1,3}$", RegexOptions.Compiled); public static bool ShouldCollect(string? key) { if (string.IsNullOrWhiteSpace(key)) { return false; } string text = key.Trim(); if (text.Length <= 1) { return false; } if (Cyrillic.IsMatch(text)) { return false; } if (Garbage.IsMatch(text)) { return false; } if (PureNumber.IsMatch(text)) { return false; } if (Timing.IsMatch(text)) { return false; } if (text.StartsWith("<") && text.EndsWith(">") && !text.Contains(" ")) { return false; } return true; } } public sealed class DeferredRefreshQueue<T> where T : class { private struct Entry { public readonly T Item; public int Frames; public Entry(T item, int frames) { Item = item; Frames = frames; } } private readonly List<Entry> _entries = new List<Entry>(); public void Enqueue(T item, int frames) { if (item == null) { return; } frames = Math.Max(1, frames); for (int i = 0; i < _entries.Count; i++) { if (_entries[i].Item == item) { _entries[i] = new Entry(item, frames); return; } } _entries.Add(new Entry(item, frames)); } public void Tick(Action<T> action) { if (action == null) { return; } for (int num = _entries.Count - 1; num >= 0; num--) { Entry value = _entries[num]; value.Frames--; if (value.Frames > 0) { _entries[num] = value; } else { _entries.RemoveAt(num); action(value.Item); } } } } public static class FontBundleLocator { private static readonly HashSet<string> NonBundleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".cfg", ".config", ".cs", ".dll", ".exe", ".json", ".md", ".pdb", ".png", ".txt", ".xml" }; public static string[] GetCandidatePaths(IEnumerable<string> files) { List<string> list = new List<string>(); foreach (string file in files) { if (!string.IsNullOrWhiteSpace(file)) { string extension = Path.GetExtension(file); if (extension.Length <= 0 || !NonBundleExtensions.Contains(extension)) { list.Add(file); } } } list.Sort(StringComparer.OrdinalIgnoreCase); return list.ToArray(); } } public static class HotkeyDefaults { public const bool EnableHotkeys = false; public const string ToggleTranslationKey = "F6"; public const string ReloadKey = "F7"; } public static class LegacyTxtParser { private const char Backslash = '\\'; private const char Quote = '"'; private static readonly string RegexPrefix = "r:\""; private static readonly string SplitPrefix = "sr:\""; private static readonly string QuoteEq = "\"="; private static readonly string EscapedEq = "\\="; public static TranslationData Parse(IEnumerable<string> lines) { TranslationData translationData = new TranslationData(); HashSet<int> levels = new HashSet<int>(); bool captured = false; foreach (string line in lines) { if (string.IsNullOrEmpty(line)) { continue; } string text = line.TrimEnd('\r', '\n'); if (text.Length == 0) { continue; } if (text[0] == '#') { ApplyDirective(text, levels); continue; } if (text.StartsWith(SplitPrefix)) { ParseRegex(text, SplitPrefix, translationData.SplitterRegex); CaptureLevels(levels, translationData, ref captured); continue; } if (text.StartsWith(RegexPrefix)) { ParseRegex(text, RegexPrefix, translationData.Regex); CaptureLevels(levels, translationData, ref captured); continue; } int num = IndexOfUnescapedEquals(text); if (num >= 0) { string text2 = Unescape(text.Substring(0, num)); string value = Unescape(text.Substring(num + 1)); if (text2.Length != 0) { translationData.Exact[text2] = value; CaptureLevels(levels, translationData, ref captured); } } } return translationData; } private static void ApplyDirective(string line, HashSet<int> levels) { string[] array = line.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 3) { return; } string text = array[0].ToLowerInvariant(); if (array[1].ToLowerInvariant() != "level") { return; } for (int i = 2; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { ',' }); for (int j = 0; j < array2.Length; j++) { if (int.TryParse(array2[j], out var result)) { if (text == "#set") { levels.Add(result); } else if (text == "#unset") { levels.Remove(result); } } } } } private static void CaptureLevels(HashSet<int> levels, TranslationData data, ref bool captured) { if (!captured) { captured = true; if (levels.Count > 0) { data.Levels = new List<int>(levels).ToArray(); } } } private static void ParseRegex(string line, string prefix, Dictionary<string, string> target) { int num = line.IndexOf(QuoteEq, prefix.Length); if (num >= 0) { string text = Unescape(line.Substring(prefix.Length, num - prefix.Length)); string text2 = line.Substring(num + QuoteEq.Length); if (text2.Length >= 2 && text2[0] == '"' && text2[text2.Length - 1] == '"') { text2 = text2.Substring(1, text2.Length - 2); } text2 = Unescape(text2); if (text.Length != 0) { target[text] = text2; } } } private static int IndexOfUnescapedEquals(string s) { for (int i = 0; i < s.Length; i++) { if (s[i] == '=' && (i == 0 || s[i - 1] != '\\')) { return i; } } return -1; } private static string Unescape(string s) { return s.Replace(EscapedEq, "="); } } public static class PngWriter { private static readonly byte[] Signature = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 }; private static readonly uint[] CrcTable = BuildCrcTable(); public static byte[] EncodeRgba32(byte[] rgba, int width, int height, bool bottomUp) { if (rgba == null) { throw new ArgumentNullException("rgba"); } if (width <= 0 || height <= 0) { throw new ArgumentException("invalid dimensions"); } if (rgba.Length != width * height * 4) { throw new ArgumentException($"pixel buffer {rgba.Length} != {width}x{height}x4"); } int num = width * 4; byte[] array = new byte[height * (1 + num)]; for (int i = 0; i < height; i++) { int num2 = (bottomUp ? (height - 1 - i) : i); int num3 = i * (1 + num); array[num3] = 0; Buffer.BlockCopy(rgba, num2 * num, array, num3 + 1, num); } using MemoryStream memoryStream = new MemoryStream(); memoryStream.Write(Signature, 0, Signature.Length); byte[] array2 = new byte[13]; WriteBE32(array2, 0, width); WriteBE32(array2, 4, height); array2[8] = 8; array2[9] = 6; array2[10] = 0; array2[11] = 0; array2[12] = 0; WriteChunk(memoryStream, "IHDR", array2); WriteChunk(memoryStream, "IDAT", ZlibDeflate(array)); WriteChunk(memoryStream, "IEND", Array.Empty<byte>()); return memoryStream.ToArray(); } private static byte[] ZlibDeflate(byte[] data) { using MemoryStream memoryStream = new MemoryStream(); memoryStream.WriteByte(120); memoryStream.WriteByte(1); using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Fastest, leaveOpen: true)) { deflateStream.Write(data, 0, data.Length); } uint num = Adler32(data); memoryStream.WriteByte((byte)((num >> 24) & 0xFF)); memoryStream.WriteByte((byte)((num >> 16) & 0xFF)); memoryStream.WriteByte((byte)((num >> 8) & 0xFF)); memoryStream.WriteByte((byte)(num & 0xFF)); return memoryStream.ToArray(); } private static void WriteChunk(Stream s, string type, byte[] data) { byte[] array = new byte[4]; WriteBE32(array, 0, data.Length); s.Write(array, 0, 4); byte[] bytes = Encoding.ASCII.GetBytes(type); s.Write(bytes, 0, 4); if (data.Length != 0) { s.Write(data, 0, data.Length); } uint value = Crc32(bytes, data); byte[] array2 = new byte[4]; WriteBE32(array2, 0, (int)value); s.Write(array2, 0, 4); } private static void WriteBE32(byte[] buf, int offset, int value) { buf[offset] = (byte)((value >> 24) & 0xFF); buf[offset + 1] = (byte)((value >> 16) & 0xFF); buf[offset + 2] = (byte)((value >> 8) & 0xFF); buf[offset + 3] = (byte)(value & 0xFF); } private static uint Adler32(byte[] data) { uint num = 1u; uint num2 = 0u; foreach (byte b in data) { num = (num + b) % 65521; num2 = (num2 + num) % 65521; } return (num2 << 16) | num; } private static uint[] BuildCrcTable() { uint[] array = new uint[256]; for (uint num = 0u; num < 256; num++) { uint num2 = num; for (int i = 0; i < 8; i++) { num2 = (((num2 & 1) != 0) ? (0xEDB88320u ^ (num2 >> 1)) : (num2 >> 1)); } array[num] = num2; } return array; } private static uint Crc32(byte[] type, byte[] data) { uint num = uint.MaxValue; byte[] array = type; foreach (byte b in array) { num = CrcTable[(num ^ b) & 0xFF] ^ (num >> 8); } array = data; foreach (byte b2 in array) { num = CrcTable[(num ^ b2) & 0xFF] ^ (num >> 8); } return num ^ 0xFFFFFFFFu; } } public sealed class ReloadableScaleState { private const float Epsilon = 0.001f; private float _baseSize; private float _lastApplied; private int _generation = -1; private bool _initialized; public bool NeedsApply(int generation) { if (_initialized) { return _generation != generation; } return true; } public float BeginApply(int generation, float current) { if (!_initialized || Math.Abs(current - _lastApplied) > 0.001f) { _baseSize = current; } _generation = generation; _initialized = true; return _baseSize; } public void CompleteApply(float applied) { _lastApplied = applied; } } public sealed class ReloadDebounce { private readonly object _gate = new object(); private volatile bool _pending; private double _lastEventSeconds; public void NotifyChange(double nowSeconds) { lock (_gate) { _lastEventSeconds = nowSeconds; _pending = true; } } public bool TryConsume(double nowSeconds, double quietSeconds) { if (!_pending) { return false; } lock (_gate) { if (!_pending) { return false; } if (nowSeconds - _lastEventSeconds < quietSeconds) { return false; } _pending = false; return true; } } } public static class ResizerTxtParser { public static Dictionary<string, string> Parse(IEnumerable<string> lines) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); foreach (string line in lines) { if (string.IsNullOrEmpty(line)) { continue; } string text = line.TrimEnd('\r', '\n'); int num = text.IndexOf('='); if (num > 0) { string text2 = text.Substring(0, num).Trim(); string text3 = text.Substring(num + 1).Trim(); if (text2.Length != 0 && text3.Length != 0) { dictionary[text2] = text3; } } } return dictionary; } } public sealed class RetirementQueue<T> { private readonly Queue<T> _items = new Queue<T>(); public int Count => _items.Count; public void Enqueue(T item) { _items.Enqueue(item); } public int Drain(int maxItems, Action<T> retire) { if (maxItems <= 0 || retire == null) { return 0; } int num = Math.Min(maxItems, _items.Count); for (int i = 0; i < num; i++) { retire(_items.Dequeue()); } return num; } } public static class ScanInterval { public static bool ShouldRefresh(float now, float lastRefresh, float intervalSeconds) { if (intervalSeconds <= 0f) { return false; } return now - lastRefresh > intervalSeconds; } } public static class SceneLogFormatter { public static IEnumerable<string> FormatBuildScenes(IEnumerable<(int Index, string Path)> scenes) { yield return "Build scenes:"; foreach (var scene in scenes) { string text = Path.GetFileNameWithoutExtension(scene.Path); if (string.IsNullOrWhiteSpace(text)) { text = "<unknown>"; } yield return $" {scene.Index}: {text}"; } } } public static class SpriteGeometry { public static (float X, float Y) NormalizedPivot(float pivotPxX, float pivotPxY, float rectW, float rectH) { if (rectW <= 0f || rectH <= 0f) { return (X: 0.5f, Y: 0.5f); } return (X: pivotPxX / rectW, Y: pivotPxY / rectH); } public static float MatchedPpu(float origPpu, float origW, float newW) { if (!(origPpu <= 0f) && !(origW <= 0f)) { return origPpu * (newW / origW); } return 100f; } public static (float L, float B, float R, float T) ScaleBorder(float l, float b, float r, float t, float origW, float origH, float newW, float newH) { if (origW <= 0f || origH <= 0f) { return (L: 0f, B: 0f, R: 0f, T: 0f); } float num = newW / origW; float num2 = newH / origH; float num3 = l * num; float num4 = r * num; float num5 = b * num2; float num6 = t * num2; if (num3 + num4 > newW && num3 + num4 > 0f) { float num7 = newW / (num3 + num4); num3 *= num7; num4 *= num7; } if (num5 + num6 > newH && num5 + num6 > 0f) { float num8 = newH / (num5 + num6); num5 *= num8; num6 *= num8; } return (L: num3, B: num5, R: num4, T: num6); } } public static class TextNormalizer { private const char Cr = '\r'; private const char Lf = '\n'; private const char Backslash = '\\'; private const char Nul = '\0'; private const char ZeroWidth = '\u200b'; private const char Nbsp = '\u00a0'; public static string CanonicalKey(string? str) { if (string.IsNullOrEmpty(str)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(str.Length); for (int i = 0; i < str.Length; i++) { char c = str[i]; switch (c) { case '\r': if (i + 1 < str.Length && str[i + 1] == '\n') { i++; } stringBuilder.Append('\\'); stringBuilder.Append('n'); break; case '\n': stringBuilder.Append('\\'); stringBuilder.Append('n'); break; default: stringBuilder.Append(c); break; case '\0': case '\u00a0': case '\u200b': break; } } return stringBuilder.ToString().Replace("<noparse></noparse>", "").Trim(); } } public static class TextureNameNormalizer { private static readonly Regex HashSuffix = new Regex("\\s*-?\\[[0-9A-Fa-f]+-[0-9A-Fa-f]+\\]$", RegexOptions.Compiled); public static string GetOriginalName(string fileNameNoExt) { if (string.IsNullOrEmpty(fileNameNoExt)) { return fileNameNoExt; } return HashSuffix.Replace(fileNameNoExt, "").TrimEnd(Array.Empty<char>()); } } public static class TextureSlots { public static IReadOnlyList<string> Allowed { get; } = new string[3] { "_MainTex", "_BaseMap", "_BaseColorMap" }; public static bool ShouldDump(int width, int height, int maxSize) { if (maxSize > 0) { if (width <= maxSize) { return height <= maxSize; } return false; } return true; } public static IEnumerable<string> FilterSlots(IEnumerable<string> materialPropertyNames) { HashSet<string> hashSet = new HashSet<string>(materialPropertyNames); return Allowed.Where(hashSet.Contains); } } public static class ThirdPartyTextTranslator { private static readonly Regex Cyrillic = new Regex("[\\u0400-\\u04FF]", RegexOptions.Compiled); public static string TranslateOrCapture(string s, Func<string, string?> resolve, Action<string> capture) { if (string.IsNullOrWhiteSpace(s) || Cyrillic.IsMatch(s)) { return s; } string text = resolve(s); if (text == null) { string text2 = s.ToLowerInvariant(); if (text2 != s) { text = resolve(text2); } } if (text != null) { return text; } capture(s); return s; } } public sealed class TranslationData { public int[] Levels { get; set; } = new int[0]; public string? FileFontOverride { get; set; } public Dictionary<string, string> Exact { get; set; } = new Dictionary<string, string>(); public Dictionary<string, string> Regex { get; set; } = new Dictionary<string, string>(); public Dictionary<string, string> SplitterRegex { get; set; } = new Dictionary<string, string>(); public Dictionary<string, string> StringFontOverride { get; set; } = new Dictionary<string, string>(); } public sealed class TranslationStore { private sealed class Bucket { public readonly Dictionary<string, string> Exact = new Dictionary<string, string>(StringComparer.Ordinal); public readonly List<(Regex Rx, string Repl, string Anchor)> Regex = new List<(Regex, string, string)>(); public readonly List<(Regex Rx, string Repl, string Anchor)> Splitters = new List<(Regex, string, string)>(); public readonly List<(Regex Rx, string Repl, List<string> Ph, string Anchor)> Templates = new List<(Regex, string, List<string>, string)>(); public readonly List<(Regex Rx, string Font)> FontRegex = new List<(Regex, string)>(); public readonly List<(Regex Rx, string Font)> FontSplitters = new List<(Regex, string)>(); public readonly Dictionary<string, string> FontExactByKey = new Dictionary<string, string>(StringComparer.Ordinal); public readonly Dictionary<string, string> FontExactByValue = new Dictionary<string, string>(StringComparer.Ordinal); public readonly HashSet<string> AutoSize = new HashSet<string>(StringComparer.Ordinal); public readonly List<Regex> AutoSizePatterns = new List<Regex>(); public readonly Dictionary<string, float> TextSize = new Dictionary<string, float>(StringComparer.Ordinal); public readonly List<(Regex Rx, float Size)> TextSizePatterns = new List<(Regex, float)>(); } private readonly struct PrefixMeta { public string Val { get; } public bool AutoSize { get; } public float? TextSize { get; } public PrefixMeta(string val, bool autoSize, float? textSize) { Val = val; AutoSize = autoSize; TextSize = textSize; } } private static readonly TimeSpan DefaultRegexTimeout = TimeSpan.FromMilliseconds(50.0); private static readonly string LiteralNl = "\\n"; private static readonly string RealNl = '\n'.ToString(); private const string AutoSizePrefix = "autosize:"; private const string SetSizePrefix = "setsize:"; private static readonly Regex SetSizePrefixRegex = new Regex("^setsize:([0-9]+(?:[.,][0-9]+)?):", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant); public const int Global = int.MinValue; private readonly Dictionary<int, Bucket> _buckets = new Dictionary<int, Bucket>(); private readonly HashSet<string> _knownSourceKeys = new HashSet<string>(StringComparer.Ordinal); private readonly HashSet<string> _knownResultKeys = new HashSet<string>(StringComparer.Ordinal); private readonly Dictionary<string, Regex> _rxCache = new Dictionary<string, Regex>(StringComparer.Ordinal); private readonly HashSet<Regex> _disabledRegex = new HashSet<Regex>(); private readonly HashSet<string> _warnedPatterns = new HashSet<string>(StringComparer.Ordinal); private readonly TimeSpan _regexTimeout; private readonly Action<string>? _warning; private const int ResolveCacheCap = 10000; private readonly Dictionary<string, string?> _resolveCache = new Dictionary<string, string>(StringComparer.Ordinal); private readonly Dictionary<string, string?> _fontCache = new Dictionary<string, string>(StringComparer.Ordinal); private readonly Dictionary<string, (float? Size, bool AutoSize)> _directiveCache = new Dictionary<string, (float?, bool)>(StringComparer.Ordinal); private int _currentLevel = int.MinValue; public int CurrentLevel { get { return _currentLevel; } set { if (_currentLevel != value) { _currentLevel = value; InvalidateCaches(); } } } public int ExactCount { get { int num = 0; foreach (Bucket value in _buckets.Values) { num += value.Exact.Count + value.Templates.Count; } return num; } } public int RegexCount { get { int num = 0; foreach (Bucket value in _buckets.Values) { num += value.Regex.Count; } return num; } } public int CompiledPatternCount => _rxCache.Count; public TranslationStore(TimeSpan? regexTimeout = null, Action<string>? warning = null) { _regexTimeout = regexTimeout ?? DefaultRegexTimeout; _warning = warning; } private static string ToRealNl(string s) { if (s.IndexOf(LiteralNl, StringComparison.Ordinal) >= 0) { return s.Replace(LiteralNl, RealNl); } return s; } private Bucket B(int level) { if (!_buckets.TryGetValue(level, out Bucket value)) { value = new Bucket(); _buckets[level] = value; } return value; } private Regex GetCompiled(string pattern) { if (!_rxCache.TryGetValue(pattern, out Regex value)) { value = new Regex(pattern, RegexOptions.Compiled | RegexOptions.CultureInvariant, _regexTimeout); _rxCache[pattern] = value; } return value; } public void Add(TranslationData data, bool scoping = true) { if (data == null) { return; } InvalidateCaches(); int[] array = ((scoping && data.Levels != null && data.Levels.Length != 0) ? data.Levels : new int[1] { -2147483648 }); int[] array2 = array; foreach (int level in array2) { Bucket bucket = B(level); foreach (KeyValuePair<string, string> item in data.Exact) { string text = TextNormalizer.CanonicalKey(item.Key); if (text.Length == 0 || string.IsNullOrEmpty(item.Value)) { continue; } PrefixMeta prefixMeta = StripCommandPrefixes(item.Value); if (Regex.IsMatch(text, "\\{\\d+\\}")) { AddTemplateRule(bucket, text, prefixMeta.Val, prefixMeta.AutoSize, prefixMeta.TextSize); _knownSourceKeys.Add(text); continue; } bucket.Exact[text] = prefixMeta.Val; _knownSourceKeys.Add(text); _knownResultKeys.Add(TextNormalizer.CanonicalKey(prefixMeta.Val)); if (prefixMeta.AutoSize) { bucket.AutoSize.Add(text); } if (prefixMeta.TextSize.HasValue) { bucket.TextSize[text] = prefixMeta.TextSize.Value; } } foreach (KeyValuePair<string, string> item2 in data.Regex) { if (string.IsNullOrEmpty(item2.Key)) { continue; } try { PrefixMeta prefixMeta2 = StripCommandPrefixes(item2.Value ?? string.Empty); Regex compiled = GetCompiled(item2.Key); bucket.Regex.Add((compiled, prefixMeta2.Val, AnchorExtractor.FromRegex(item2.Key))); _knownSourceKeys.Add(TextNormalizer.CanonicalKey(item2.Key)); if (prefixMeta2.AutoSize) { bucket.AutoSizePatterns.Add(compiled); } if (prefixMeta2.TextSize.HasValue) { bucket.TextSizePatterns.Add((compiled, prefixMeta2.TextSize.Value)); } } catch (Exception e) { WarnInvalidPattern(item2.Key, e); } } if (data.SplitterRegex == null) { continue; } foreach (KeyValuePair<string, string> item3 in data.SplitterRegex) { if (string.IsNullOrEmpty(item3.Key)) { continue; } try { PrefixMeta prefixMeta3 = StripCommandPrefixes(item3.Value ?? string.Empty); Regex compiled2 = GetCompiled(AnchorSplitterPattern(item3.Key)); bucket.Splitters.Add((compiled2, prefixMeta3.Val, AnchorExtractor.FromRegex(item3.Key))); _knownSourceKeys.Add(TextNormalizer.CanonicalKey(item3.Key)); if (prefixMeta3.AutoSize) { bucket.AutoSizePatterns.Add(compiled2); } if (prefixMeta3.TextSize.HasValue) { bucket.TextSizePatterns.Add((compiled2, prefixMeta3.TextSize.Value)); } } catch (Exception e2) { WarnInvalidPattern(item3.Key, e2); } } } string fileFontOverride = data.FileFontOverride; if (!string.IsNullOrEmpty(fileFontOverride)) { foreach (KeyValuePair<string, string> item4 in data.Exact) { RegisterExactFontOverride(array, item4.Key, item4.Value, fileFontOverride); } RegisterRegexFontOverrides(array, data.Regex, fileFontOverride, splitter: false); RegisterRegexFontOverrides(array, data.SplitterRegex, fileFontOverride, splitter: true); } if (data.StringFontOverride == null) { return; } foreach (KeyValuePair<string, string> item5 in data.StringFontOverride) { if (TextNormalizer.CanonicalKey(item5.Key).Length != 0 && !string.IsNullOrEmpty(item5.Value)) { data.Exact.TryGetValue(item5.Key, out string value); RegisterExactFontOverride(array, item5.Key, value ?? string.Empty, item5.Value); if (data.Regex != null && data.Regex.ContainsKey(item5.Key)) { RegisterRegexFontOverride(array, item5.Key, item5.Value, splitter: false); } if (data.SplitterRegex != null && data.SplitterRegex.ContainsKey(item5.Key)) { RegisterRegexFontOverride(array, item5.Key, item5.Value, splitter: true); } } } } private void InvalidateCaches() { _resolveCache.Clear(); _fontCache.Clear(); _directiveCache.Clear(); } private void RegisterExactFontOverride(int[] levels, string sourceKey, string translatedValue, string fontName) { string text = TextNormalizer.CanonicalKey(sourceKey); if (text.Length == 0 || string.IsNullOrEmpty(fontName)) { return; } string text2 = (string.IsNullOrEmpty(translatedValue) ? string.Empty : TextNormalizer.CanonicalKey(StripCommandPrefixes(translatedValue).Val)); foreach (int level in levels) { Bucket bucket = B(level); bucket.FontExactByKey[text] = fontName; string text3 = text2; if (text3.Length == 0 && bucket.Exact.TryGetValue(text, out string value)) { text3 = TextNormalizer.CanonicalKey(value); } if (text3.Length > 0) { bucket.FontExactByValue[text3] = fontName; } } } private void RegisterRegexFontOverrides(int[] levels, Dictionary<string, string>? patterns, string fontName, bool splitter) { if (patterns == null) { return; } foreach (KeyValuePair<string, string> pattern in patterns) { RegisterRegexFontOverride(levels, pattern.Key, fontName, splitter); } } private void RegisterRegexFontOverride(int[] levels, string pattern, string fontName, bool splitter) { if (string.IsNullOrEmpty(pattern) || string.IsNullOrEmpty(fontName)) { return; } try { string pattern2 = (splitter ? AnchorSplitterPattern(pattern) : pattern); Regex compiled = GetCompiled(pattern2); foreach (int level in levels) { Bucket bucket = B(level); if (splitter) { bucket.FontSplitters.Add((compiled, fontName)); } else { bucket.FontRegex.Add((compiled, fontName)); } } } catch (Exception e) { WarnInvalidPattern(pattern, e); } } private static string AnchorSplitterPattern(string pattern) { string text = pattern; if (!text.StartsWith("^")) { text = "^" + text; } if (!text.EndsWith("$")) { text += "$"; } return text; } private void AddTemplateRule(Bucket bucket, string key, string val, bool autoSize, float? textSize) { List<string> list = new List<string>(); foreach (Match item in Regex.Matches(key, "\\{(\\d+)\\}")) { list.Add(item.Groups[1].Value); } string input = Regex.Escape(key); input = Regex.Replace(input, "\\\\\\{\\d+\\}", "([+-]?\\d+(?:[.,]\\d+)?)"); input = "^" + input + "$"; try { Regex compiled = GetCompiled(input); bucket.Templates.Add((compiled, val, list, AnchorExtractor.FromTemplateKey(key))); if (autoSize) { bucket.AutoSizePatterns.Add(compiled); } if (textSize.HasValue) { bucket.TextSizePatterns.Add((compiled, textSize.Value)); } } catch (Exception e) { WarnInvalidPattern(input, e); } } private static PrefixMeta StripCommandPrefixes(string value) { if (string.IsNullOrEmpty(value)) { return new PrefixMeta(value, autoSize: false, null); } bool autoSize = false; float? textSize = null; string text = value; while (!string.IsNullOrEmpty(text)) { if (text.StartsWith("autosize:", StringComparison.OrdinalIgnoreCase)) { autoSize = true; text = text.Substring("autosize:".Length); continue; } if (!text.StartsWith("setsize:", StringComparison.OrdinalIgnoreCase)) { break; } Match match = SetSizePrefixRegex.Match(text); if (!match.Success || !float.TryParse(match.Groups[1].Value.Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result <= 0f) { break; } textSize = result; text = text.Substring(match.Length); } return new PrefixMeta(text, autoSize, textSize); } public string? Resolve(string? current) { if (string.IsNullOrEmpty(current)) { return null; } string key = TextNormalizer.CanonicalKey(current); if (_resolveCache.TryGetValue(key, out string value)) { return value; } string text = ResolveByKey(key, useSplitter: true); if (_resolveCache.Count >= 10000) { _resolveCache.Clear(); } _resolveCache[key] = text; return text; } private string? Resolve(string? current, bool useSplitter) { if (string.IsNullOrEmpty(current)) { return null; } return ResolveByKey(TextNormalizer.CanonicalKey(current), useSplitter); } private string? ResolveByKey(string key, bool useSplitter) { string rxText = ToRealNl(key); string text = ExactStage(CurrentLevel, key); if (text == null && CurrentLevel != int.MinValue) { text = ExactStage(int.MinValue, key); } if (text != null) { return text; } text = RegexStage(CurrentLevel, rxText); if (text == null && CurrentLevel != int.MinValue) { text = RegexStage(int.MinValue, rxText); } if (text != null) { return text; } if (useSplitter) { text = SplitterStage(CurrentLevel, rxText); if (text == null && CurrentLevel != int.MinValue) { text = SplitterStage(int.MinValue, rxText); } } return text; } private string? ExactStage(int level, string key) { if (!_buckets.TryGetValue(level, out Bucket value)) { return null; } if (value.Exact.TryGetValue(key, out string value2)) { return ToRealNl(value2); } foreach (var (rx, text, list, text2) in value.Templates) { if ((text2.Length <= 0 || key.IndexOf(text2, StringComparison.Ordinal) >= 0) && TryMatch(rx, key, out Match match)) { string text3 = text; for (int i = 1; i < match.Groups.Count; i++) { text3 = text3.Replace("{" + list[i - 1] + "}", match.Groups[i].Value); } return ToRealNl(text3); } } return null; } private string? RegexStage(int level, string rxText) { if (!_buckets.TryGetValue(level, out Bucket value)) { return null; } foreach (var (rx, replacement, text) in value.Regex) { if ((text.Length <= 0 || rxText.IndexOf(text, StringComparison.Ordinal) >= 0) && TryMatch(rx, rxText, out Match match)) { return ToRealNl(match.Result(replacement)); } } return null; } private string? SplitterStage(int level, string rxText) { if (!_buckets.TryGetValue(level, out Bucket value)) { return null; } foreach (var (rx, template, text) in value.Splitters) { if ((text.Length <= 0 || rxText.IndexOf(text, StringComparison.Ordinal) >= 0) && TryMatch(rx, rxText, out Match match)) { return ToRealNl(ApplySplitter(match, template)); } } return null; } private string ApplySplitter(Match m, string template) { StringBuilder stringBuilder = new StringBuilder(template); for (int num = m.Groups.Count - 1; num >= 1; num--) { Group obj = m.Groups[num]; string oldValue = "$" + num; if (!obj.Success) { stringBuilder.Replace(oldValue, string.Empty); } else { string value = obj.Value; string newValue = Resolve(value, useSplitter: false) ?? value; stringBuilder.Replace(oldValue, newValue); } } return stringBuilder.ToString(); } public string? GetFont(string? key) { if (string.IsNullOrEmpty(key)) { return null; } string text = TextNormalizer.CanonicalKey(key); if (_fontCache.TryGetValue(text, out string value)) { return value; } string fontByKey = GetFontByKey(text); if (_fontCache.Count >= 10000) { _fontCache.Clear(); } _fontCache[text] = fontByKey; return fontByKey; } private string? GetFontByKey(string normalized) { string text = FontExactStage(CurrentLevel, normalized); if (text == null && CurrentLevel != int.MinValue) { text = FontExactStage(int.MinValue, normalized); } if (text != null) { return text; } string rxText = ToRealNl(normalized); text = FontRegexStage(CurrentLevel, rxText); if (text == null && CurrentLevel != int.MinValue) { text = FontRegexStage(int.MinValue, rxText); } return text; } private string? FontExactStage(int level, string normalized) { if (!_buckets.TryGetValue(level, out Bucket value)) { return null; } if (value.FontExactByKey.TryGetValue(normalized, out string value2)) { return value2; } if (!value.FontExactByValue.TryGetValue(normalized, out value2)) { return null; } return value2; } private string? FontRegexStage(int level, string rxText) { if (!_buckets.TryGetValue(level, out Bucket value)) { return null; } for (int num = value.FontRegex.Count - 1; num >= 0; num--) { if (IsMatchSafe(value.FontRegex[num].Rx, rxText)) { return value.FontRegex[num].Font; } } for (int num2 = value.FontSplitters.Count - 1; num2 >= 0; num2--) { if (IsMatchSafe(value.FontSplitters[num2].Rx, rxText)) { return value.FontSplitters[num2].Font; } } return null; } public bool KnowsText(string? text) { if (string.IsNullOrEmpty(text)) { return false; } return KnowsCanonical(TextNormalizer.CanonicalKey(text)); } public bool KnowsCanonical(string? canonicalKey) { if (!string.IsNullOrEmpty(canonicalKey)) { if (!_knownSourceKeys.Contains(canonicalKey)) { return _knownResultKeys.Contains(canonicalKey); } return true; } return false; } public bool RequiresAutoSize(string? original) { if (string.IsNullOrEmpty(original)) { return false; } return GetDirectives(TextNormalizer.CanonicalKey(original)).AutoSize; } private bool AutoSizeIn(int level, string key) { if (!_buckets.TryGetValue(level, out Bucket value)) { return false; } if (value.AutoSize.Contains(key)) { return true; } string input = ToRealNl(key); foreach (Regex autoSizePattern in value.AutoSizePatterns) { if (IsMatchSafe(autoSizePattern, key) || IsMatchSafe(autoSizePattern, input)) { return true; } } return false; } public float? GetTextSize(string? original) { if (string.IsNullOrEmpty(original)) { return null; } return GetDirectives(TextNormalizer.CanonicalKey(original)).Size; } private (float? Size, bool AutoSize) GetDirectives(string key) { if (_directiveCache.TryGetValue(key, out (float?, bool) value)) { return value; } float? item = TextSizeIn(CurrentLevel, key); if (!item.HasValue && CurrentLevel != int.MinValue) { item = TextSizeIn(int.MinValue, key); } bool item2 = AutoSizeIn(CurrentLevel, key) || (CurrentLevel != int.MinValue && AutoSizeIn(int.MinValue, key)); value = (item, item2); if (_directiveCache.Count >= 10000) { _directiveCache.Clear(); } _directiveCache[key] = value; return value; } private float? TextSizeIn(int level, string key) { if (!_buckets.TryGetValue(level, out Bucket value)) { return null; } if (value.TextSize.TryGetValue(key, out var value2)) { return value2; } string input = ToRealNl(key); foreach (var (rx, value3) in value.TextSizePatterns) { if (IsMatchSafe(rx, key) || IsMatchSafe(rx, input)) { return value3; } } return null; } private bool TryMatch(Regex rx, string input, out Match match) { match = Match.Empty; if (_disabledRegex.Count > 0 && _disabledRegex.Contains(rx)) { return false; } try { match = rx.Match(input); return match.Success; } catch (RegexMatchTimeoutException) { _disabledRegex.Add(rx); WarnOnce("timeout:" + rx, $"Regex timed out and was disabled: {rx}"); return false; } } private bool IsMatchSafe(Regex rx, string input) { Match match; return TryMatch(rx, input, out match); } private void WarnInvalidPattern(string pattern, Exception e) { WarnOnce("invalid:" + pattern, "Invalid regex skipped: " + pattern + " (" + e.Message + ")"); } private void WarnOnce(string key, string message) { if (_warning != null && _warnedPatterns.Add(key)) { _warning(message); } } } public static class TranslationWatchFilter { private const char Backslash = '\\'; public static bool ShouldTrigger(string? fullPath) { if (string.IsNullOrEmpty(fullPath)) { return false; } string extension = Path.GetExtension(fullPath); if (!extension.Equals(".json", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".png", StringComparison.OrdinalIgnoreCase)) { return false; } if (Path.GetFileName(fullPath).Equals("_AutoGenerated.json", StringComparison.OrdinalIgnoreCase)) { return false; } if (fullPath.Replace('\\', '/').IndexOf("/Texture_Dump/", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } return true; } } public sealed class VersionedTextValue { private string _text = string.Empty; private string _canonical = string.Empty; private int _generation = -1; public void Set(string text, string canonical, int generation) { _text = text; _canonical = canonical; _generation = generation; } public bool Matches(string text, int generation) { if (generation == _generation) { if (!(_text == text)) { return _canonical == TextNormalizer.CanonicalKey(text); } return true; } return false; } } }
plugins/WKLoc/WKLoc.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Febucci.UI; using Febucci.UI.Core; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using TMPro; using UnityEngine; using UnityEngine.LowLevel; using UnityEngine.PlayerLoop; using UnityEngine.SceneManagement; using UnityEngine.UI; using WKLoc.Apply; using WKLoc.Assets; using WKLoc.Collect; using WKLoc.Core; using WKLoc.Layout; using WKLoc.Loc; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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] [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 WKLoc { internal static class Diagnostics { private static readonly HashSet<string> Warned = new HashSet<string>(); public static void WarnOnce(string key, string message) { if (Warned.Add(key)) { Debug.LogWarning((object)("[WKLoc] " + message)); } } } [BepInPlugin("WKLoc", "WKLoc", "1.0.0")] public sealed class WkLocPlugin : BaseUnityPlugin { public const string Guid = "WKLoc"; public static WkLocPlugin Instance; public static PluginConfig Cfg; public static TranslationStore Store; private Harmony _harmony; private static bool _applicationQuitting; public ManualLogSource Log => ((BaseUnityPlugin)this).Logger; public string PluginDir => Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location); public string LangDir => Path.Combine(PluginDir, "Translation", Cfg.Language.Value); private void Awake() { //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Expected O, but got Unknown Instance = this; Cfg = new PluginConfig(((BaseUnityPlugin)this).Config); Application.quitting += ShutdownApplication; Store = TranslationLoader.ReadAll(LangDir, ((BaseUnityPlugin)this).Logger, Cfg.EnableTranslationScoping.Value); FontProvider.Init(PluginDir, Cfg.OverrideFontTMP.Value, Cfg.OverrideFontLegacy.Value, ((BaseUnityPlugin)this).Logger); TextureSwapper.Init(LangDir, ((BaseUnityPlugin)this).Logger); UiResizer.Init(LangDir, ((BaseUnityPlugin)this).Logger); TextCollector.Enabled = Cfg.EnableTextCollection.Value; TextCollector.Init(LangDir); TextureCollector.Enabled = Cfg.EnableTextureCollection.Value; TextureCollector.MaxSize = Cfg.TextureDumpMaxSize.Value; TextureCollector.Init(LangDir); SceneManager.sceneLoaded += HandleLoadedScene; TranslationStore store = Store; Scene activeScene = SceneManager.GetActiveScene(); store.CurrentLevel = ((Scene)(ref activeScene)).buildIndex; _harmony = new Harmony("WKLoc"); PatchAllIsolated(); FrameDriver.Install(typeof(WkLocPlugin), Sweeper.Tick); if (Cfg.EnableHotkeys.Value && Cfg.EnableAutoReload.Value) { TranslationWatcher.Install(LangDir); } foreach (string item in SceneLogFormatter.FormatBuildScenes(GetBuildScenes())) { ((BaseUnityPlugin)this).Logger.LogInfo((object)item); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("WKLoc loaded. LangDir=" + LangDir)); } private void PatchAllIsolated() { int num = 0; int num2 = 0; Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly()); foreach (Type type in typesFromAssembly) { try { List<MethodInfo> list = _harmony.CreateClassProcessor(type).Patch(); if (list != null && list.Count > 0) { num++; } } catch (Exception ex) { num2++; ((BaseUnityPlugin)this).Logger.LogWarning((object)("[WKLoc] Patch class FAILED: " + type.FullName + ": " + ex.Message)); } } ((BaseUnityPlugin)this).Logger.LogInfo((object)$"[WKLoc] Harmony patch classes: {num} ok, {num2} failed."); } private static IEnumerable<(int Index, string Path)> GetBuildScenes() { for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++) { yield return (Index: i, Path: SceneUtility.GetScenePathByBuildIndex(i)); } } private void HandleLoadedScene(Scene scene, LoadSceneMode mode) { Store.CurrentLevel = ((Scene)(ref scene)).buildIndex; TextPipeline.InvalidateTranslationContext(); Sweeper.RefreshTouchedComponents(); RendererNet.OnSceneChange(); TextureScanner.OnSceneChange(); if (Cfg.DebugLog.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)$"[WKLoc] Scene loaded: '{((Scene)(ref scene)).name}' buildIndex={((Scene)(ref scene)).buildIndex}"); } if (TextPipeline.Enabled) { TextureSwapper.ReplaceAllInScene(); } TextureCollector.CollectScene(); TextCollector.Flush(); } private void OnDestroy() { TextCollector.Flush(); } private void OnApplicationQuit() { ShutdownApplication(); } private static void ShutdownApplication() { if (!_applicationQuitting) { _applicationQuitting = true; TextCollector.Shutdown(); TextureDumpQueue.Shutdown(); TranslationWatcher.Shutdown(); TextureSwapper.Shutdown(); } } } public sealed class PluginConfig { public ConfigEntry<string> Language; public ConfigEntry<string> OverrideFontTMP; public ConfigEntry<string> OverrideFontLegacy; public ConfigEntry<bool> EnableTranslationScoping; public ConfigEntry<bool> EnableTextCollection; public ConfigEntry<bool> EnableTextureCollection; public ConfigEntry<int> TextureDumpMaxSize; public ConfigEntry<bool> EnablePolling; public ConfigEntry<float> RendererScanSeconds; public ConfigEntry<bool> EnableHotkeys; public ConfigEntry<KeyCode> ToggleKey; public ConfigEntry<KeyCode> ReloadKey; public ConfigEntry<bool> EnableAutoReload; public ConfigEntry<bool> DebugLog; public PluginConfig(ConfigFile cfg) { //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) Language = cfg.Bind<string>("General", "Language", "ru", "Код языка перевода (папка Translation/<lang>)."); OverrideFontTMP = cfg.Bind<string>("Fonts", "OverrideFontTMP", "main", "Имя TMP-шрифта из первого font AssetBundle рядом с плагином (пусто = первый/default из бандла)."); OverrideFontLegacy = cfg.Bind<string>("Fonts", "OverrideFontLegacy", "main-legacy", "Имя legacy-шрифта (uGUI Text) из первого font AssetBundle рядом с плагином (пусто = default/первый)."); EnableTranslationScoping = cfg.Bind<bool>("General", "EnableTranslationScoping", true, "Привязка переводов к сценам (Levels). Выкл = все переводы глобальные."); EnableTextCollection = cfg.Bind<bool>("Collection", "EnableTextCollection", false, "Собирать непереведённый текст в _AutoGenerated.json на ходу."); EnableTextureCollection = cfg.Bind<bool>("Collection", "EnableTextureCollection", false, "Дампить невстреченные текстуры в Texture_Dump/ на ходу."); TextureDumpMaxSize = cfg.Bind<int>("Collection", "TextureDumpMaxSize", 0, "Макс. сторона текстуры (px) для дампа материалов; 0 = без ограничения."); EnablePolling = cfg.Bind<bool>("Apply", "EnablePolling", true, "Добор пропущенного текста/текстур бюджетным сканером (мастер-тумблер)."); RendererScanSeconds = cfg.Bind<float>("Apply", "RendererScanSeconds", 3f, "Период active-scene скана 3D-рендереров для динамики (сек). 0 = только при смене сцены. Текст и UI-текстуры ловятся хуками без скана."); EnableHotkeys = cfg.Bind<bool>("Hotkeys", "EnableHotkeys", false, "Включить горячие клавиши."); ToggleKey = cfg.Bind<KeyCode>("Hotkeys", "ToggleTranslation", ParseKey("F6"), "Вкл/выкл перевод."); ReloadKey = cfg.Bind<KeyCode>("Hotkeys", "Reload", ParseKey("F7"), "Перезагрузить перевод."); EnableAutoReload = cfg.Bind<bool>("Hotkeys", "AutoReloadOnFileChange", true, "Авто-перезагрузка перевода при изменении файлов в папке языка (эквивалент F7; работает только при EnableHotkeys=true)."); DebugLog = cfg.Bind<bool>("Debug", "LogText", false, "Подробный лог перевода/применения (диагностика)."); } private static KeyCode ParseKey(string key) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) return (KeyCode)Enum.Parse(typeof(KeyCode), key); } } } namespace WKLoc.Loc { public static class TranslationLoader { private static readonly string[] NonTranslation = new string[2] { "interface_resizer.json", "resize_rules.json" }; public static TranslationStore ReadAll(string langDir, ManualLogSource log, bool scoping) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown TranslationStore val = new TranslationStore((TimeSpan?)null, (Action<string>)delegate(string message) { log.LogWarning((object)message); }); if (!Directory.Exists(langDir)) { log.LogWarning((object)("Lang dir not found: " + langDir)); return val; } string[] files = Directory.GetFiles(langDir, "*.json"); foreach (string path in files) { string fileName = Path.GetFileName(path); bool flag = false; string[] nonTranslation = NonTranslation; foreach (string value in nonTranslation) { if (fileName.Equals(value, StringComparison.OrdinalIgnoreCase)) { flag = true; } } if (flag) { continue; } try { TranslationData val2 = JsonConvert.DeserializeObject<TranslationData>(File.ReadAllText(path)); if (val2 != null) { val.Add(val2, scoping); } } catch (Exception ex) { log.LogError((object)("Failed to load " + fileName + ": " + ex.Message)); } } log.LogInfo((object)$"Loaded {val.ExactCount} exact, {val.RegexCount} regex from {langDir}"); return val; } } public static class TranslationWatcher { private const double QuietSeconds = 0.5; private static FileSystemWatcher? _fsw; private static readonly ReloadDebounce _debounce = new ReloadDebounce(); public static void Install(string langDir) { if (_fsw != null) { return; } if (!Directory.Exists(langDir)) { Debug.LogWarning((object)("[WKLoc] Auto-reload watcher skipped: lang dir not found: " + langDir)); return; } try { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(langDir); fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite; fileSystemWatcher.Changed += HandleEvent; fileSystemWatcher.Created += HandleEvent; fileSystemWatcher.Deleted += HandleEvent; fileSystemWatcher.Renamed += HandleRenamed; fileSystemWatcher.EnableRaisingEvents = true; _fsw = fileSystemWatcher; Debug.Log((object)("[WKLoc] Auto-reload watcher active: " + langDir)); } catch (Exception ex) { Debug.LogWarning((object)("[WKLoc] Auto-reload watcher unavailable: " + ex.Message)); _fsw = null; } } public static bool ConsumePendingReload() { return _debounce.TryConsume(NowSeconds(), 0.5); } private static void HandleEvent(object sender, FileSystemEventArgs e) { Notify(e.FullPath); } private static void HandleRenamed(object sender, RenamedEventArgs e) { Notify(e.FullPath); } private static void Notify(string fullPath) { if (TranslationWatchFilter.ShouldTrigger(fullPath)) { _debounce.NotifyChange(NowSeconds()); } } public static void Shutdown() { FileSystemWatcher fsw = _fsw; _fsw = null; if (fsw == null) { return; } try { fsw.EnableRaisingEvents = false; fsw.Changed -= HandleEvent; fsw.Created -= HandleEvent; fsw.Deleted -= HandleEvent; fsw.Renamed -= HandleRenamed; fsw.Dispose(); } catch (Exception ex) { Debug.LogWarning((object)("[WKLoc] Auto-reload watcher shutdown failed: " + ex.Message)); } } private static double NowSeconds() { return (double)DateTime.UtcNow.Ticks / 10000000.0; } } } namespace WKLoc.Layout { public sealed class ResizeRule { public float? scale { get; set; } public string? align { get; set; } } public static class UiResizer { private static Dictionary<string, ResizeRule> _nameRules = new Dictionary<string, ResizeRule>(StringComparer.Ordinal); private static Dictionary<string, string> _pathRules = new Dictionary<string, string>(StringComparer.Ordinal); private static KeyValuePair<string, ResizeRule>[] _orderedNameRules = Array.Empty<KeyValuePair<string, ResizeRule>>(); private static readonly Regex _pct = new Regex("ChangeFontSizeByPercentage\\(([\\d.]+)\\)", RegexOptions.Compiled); private static readonly Regex _align = new Regex("TMP_Alignment\\((\\w+)\\)", RegexOptions.Compiled); public static void Init(string langDir, ManualLogSource log) { if (TryRead<Dictionary<string, ResizeRule>>(Path.Combine(langDir, "resize_rules.json"), log, out Dictionary<string, ResizeRule> value)) { _nameRules = value; List<KeyValuePair<string, ResizeRule>> list = new List<KeyValuePair<string, ResizeRule>>(_nameRules); list.Sort(delegate(KeyValuePair<string, ResizeRule> a, KeyValuePair<string, ResizeRule> b) { int num = b.Key.Length.CompareTo(a.Key.Length); return (num == 0) ? StringComparer.Ordinal.Compare(a.Key, b.Key) : num; }); _orderedNameRules = list.ToArray(); } if (TryRead<Dictionary<string, string>>(Path.Combine(langDir, "interface_resizer.json"), log, out Dictionary<string, string> value2)) { _pathRules = value2; } log.LogInfo((object)$"Resize: {_nameRules.Count} name rules, {_pathRules.Count} path rules."); } private static bool TryRead<T>(string path, ManualLogSource log, out T? value) where T : class, new() { if (!File.Exists(path)) { value = new T(); return true; } try { value = JsonConvert.DeserializeObject<T>(File.ReadAllText(path)) ?? new T(); return true; } catch (Exception ex) { value = null; log.LogWarning((object)("Resize rules reload kept previous '" + Path.GetFileName(path) + "': " + ex.Message)); return false; } } private static (float? scale, string? align) Resolve(string objName, Transform tr) { float? item = null; string item2 = null; KeyValuePair<string, ResizeRule>[] orderedNameRules = _orderedNameRules; for (int i = 0; i < orderedNameRules.Length; i++) { KeyValuePair<string, ResizeRule> keyValuePair = orderedNameRules[i]; if (objName.IndexOf(keyValuePair.Key, StringComparison.Ordinal) >= 0) { if (keyValuePair.Value.scale.HasValue) { item = keyValuePair.Value.scale; } if (!string.IsNullOrEmpty(keyValuePair.Value.align)) { item2 = keyValuePair.Value.align; } break; } } if (_pathRules.TryGetValue(GetPath(tr), out string value)) { Match match = _pct.Match(value); if (match.Success && float.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { item = result; } Match match2 = _align.Match(value); if (match2.Success) { item2 = match2.Groups[1].Value; } } return (scale: item, align: item2); } public static void Apply(TMP_Text t, float baseSize) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)t == (Object)null) { return; } try { var (num, value) = Resolve(((Object)((Component)t).gameObject).name, t.transform); t.fontSize = (num.HasValue ? (baseSize * num.Value) : baseSize); if (!string.IsNullOrEmpty(value) && Enum.TryParse<TextAlignmentOptions>(value, out TextAlignmentOptions result)) { t.alignment = result; } } catch (Exception ex) { Diagnostics.WarnOnce("resize-tmp", "TMP resize apply failed: " + ex.Message); } } public static void Apply(Text t, float baseSize) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)t == (Object)null) { return; } try { var (num, text) = Resolve(((Object)((Component)t).gameObject).name, ((Component)t).transform); t.fontSize = Mathf.Max(1, Mathf.RoundToInt(num.HasValue ? (baseSize * num.Value) : baseSize)); if (!string.IsNullOrEmpty(text)) { TextAnchor? val = ToAnchor(text); if (val.HasValue) { t.alignment = val.Value; } } } catch (Exception ex) { Diagnostics.WarnOnce("resize-legacy", "Legacy resize apply failed: " + ex.Message); } } private static TextAnchor? ToAnchor(string a) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) string text = a.ToLowerInvariant(); string obj = ((text.Contains("top") || text.Contains("upper") || text.Contains("capline")) ? "Upper" : ((text.Contains("bottom") || text.Contains("lower") || text.Contains("baseline")) ? "Lower" : "Middle")); string text2 = (text.Contains("left") ? "Left" : (text.Contains("right") ? "Right" : "Center")); if (!Enum.TryParse<TextAnchor>(obj + text2, out TextAnchor result)) { return null; } return result; } private static string GetPath(Transform tr) { string text = ((Object)tr).name; while ((Object)(object)tr.parent != (Object)null) { tr = tr.parent; text = ((Object)tr).name + "/" + text; } return text; } } } namespace WKLoc.Collect { public static class TextCollector { private static readonly TimeSpan WriteDelay = TimeSpan.FromMilliseconds(50.0); public static bool Enabled; private static AutoGeneratedTextWriter? _writer; public static void Init(string langDir) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown _writer = new AutoGeneratedTextWriter(langDir, WriteDelay, (Func<string, TranslationData>)((string json) => JsonConvert.DeserializeObject<TranslationData>(json)), (Func<TranslationData, string>)((TranslationData data) => JsonConvert.SerializeObject((object)data, (Formatting)1)), (Action<Exception>)delegate(Exception e) { Debug.LogError((object)("[WKLoc] AutoGenerated write error: " + e.Message)); }, (Action<int, string>)delegate(int added, string path) { Debug.Log((object)$"[WKLoc] Collected {added} new string(s) -> {path}"); }); if (Enabled) { Debug.Log((object)("[WKLoc] Text collection ON -> " + langDir + "/_AutoGenerated.json")); } } public static void Capture(string text) { if (!Enabled) { return; } AutoGeneratedTextWriter writer = _writer; if (writer != null) { string text2 = TextNormalizer.CanonicalKey(text); if (CollectFilter.ShouldCollect(text2) && !WkLocPlugin.Store.KnowsCanonical(text2)) { writer.Enqueue(text2); } } } public static void Flush() { AutoGeneratedTextWriter writer = _writer; if (writer == null) { return; } try { writer.Flush(); } catch (Exception ex) { Debug.LogError((object)("[WKLoc] AutoGenerated flush error: " + ex.Message)); } } public static void Shutdown() { AutoGeneratedTextWriter writer = _writer; _writer = null; if (writer == null) { return; } try { writer.Dispose(); } catch (Exception ex) { Debug.LogError((object)("[WKLoc] AutoGenerated shutdown error: " + ex.Message)); } } } public static class TextureCollector { public static bool Enabled; public static int MaxSize; private static string _dumpDir = ""; private static readonly HashSet<string> _dumped = new HashSet<string>(StringComparer.OrdinalIgnoreCase); public static void Init(string langDir) { _dumpDir = Path.Combine(langDir, "Texture_Dump"); } public static void CollectScene() { if (!Enabled) { return; } try { Directory.CreateDirectory(_dumpDir); Image[] array = Resources.FindObjectsOfTypeAll<Image>(); foreach (Image val in array) { if ((Object)(object)val.sprite != (Object)null) { DumpSprite(val.sprite); } } SpriteRenderer[] array2 = Resources.FindObjectsOfTypeAll<SpriteRenderer>(); foreach (SpriteRenderer val2 in array2) { if ((Object)(object)val2.sprite != (Object)null) { DumpSprite(val2.sprite); } } RawImage[] array3 = Resources.FindObjectsOfTypeAll<RawImage>(); foreach (RawImage val3 in array3) { if ((Object)(object)val3.texture != (Object)null) { DumpTexture(val3.texture, ((Object)val3.texture).name, null); } } CollectMaterials(); } catch (Exception ex) { Debug.LogError((object)("[WKLoc] Texture collect error: " + ex.Message)); } } public static void DumpSprite(Sprite sprite) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)sprite.texture == (Object)null)) { DumpTexture((Texture)(object)sprite.texture, ((Object)sprite).name, sprite.textureRect); } } public static void DumpOne(Texture tex) { if ((Object)(object)tex != (Object)null) { DumpTexture(tex, ((Object)tex).name, null); } } public static void CollectMaterials() { Renderer[] array = Resources.FindObjectsOfTypeAll<Renderer>(); for (int i = 0; i < array.Length; i++) { DumpRendererMaterials(array[i]); } } public static void DumpRendererMaterials(Renderer r) { if ((Object)(object)r == (Object)null) { return; } Material[] sharedMaterials = r.sharedMaterials; if (sharedMaterials == null) { return; } Material[] array = sharedMaterials; foreach (Material val in array) { if ((Object)(object)val == (Object)null) { continue; } if (val.HasProperty("_MainTex") && (Object)(object)val.mainTexture != (Object)null) { DumpOne(val.mainTexture); } foreach (string item in TextureSlots.Allowed) { if (val.HasProperty(item)) { Texture texture = val.GetTexture(item); if ((Object)(object)texture != (Object)null) { DumpOne(texture); } } } } } private static void DumpTexture(Texture src, string rawName, Rect? region) { string originalName = TextureNameNormalizer.GetOriginalName(rawName); if (string.IsNullOrEmpty(originalName) || _dumped.Contains(originalName)) { return; } if (TextureSwapper.Has(originalName)) { _dumped.Add(originalName); } else if (MaxSize <= 0 || TextureSlots.ShouldDump(src.width, src.height, MaxSize)) { try { Texture2D obj = MakeReadable(src, region); byte[] rawTextureData = obj.GetRawTextureData(); int width = ((Texture)obj).width; int height = ((Texture)obj).height; Object.Destroy((Object)(object)obj); TextureDumpQueue.Enqueue(rawTextureData, width, height, Path.Combine(_dumpDir, originalName + ".png")); _dumped.Add(originalName); } catch (Exception ex) { Debug.LogError((object)("[WKLoc] Dump " + originalName + ": " + ex.Message)); } } } private static Texture2D MakeReadable(Texture src, Rect? region) { //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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Expected O, but got Unknown //IL_00d6: Unknown result type (might be due to invalid IL or missing references) int num; Rect value; if (!region.HasValue) { num = 0; } else { value = region.Value; num = Mathf.RoundToInt(((Rect)(ref value)).x); } int num2 = num; int num3; if (!region.HasValue) { num3 = 0; } else { value = region.Value; num3 = Mathf.RoundToInt(((Rect)(ref value)).y); } int num4 = num3; int num5; if (!region.HasValue) { num5 = src.width; } else { value = region.Value; num5 = Mathf.RoundToInt(((Rect)(ref value)).width); } int num6 = num5; int num7; if (!region.HasValue) { num7 = src.height; } else { value = region.Value; num7 = Mathf.RoundToInt(((Rect)(ref value)).height); } int num8 = num7; RenderTexture temporary = RenderTexture.GetTemporary(src.width, src.height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2); RenderTexture active = RenderTexture.active; Texture2D val = null; try { Graphics.Blit(src, temporary); RenderTexture.active = temporary; val = new Texture2D(num6, num8, (TextureFormat)4, false); val.ReadPixels(new Rect((float)num2, (float)num4, (float)num6, (float)num8), 0, 0); val.Apply(); return val; } catch { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } throw; } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); } } } public static class TextureDumpQueue { private readonly struct Job { public readonly byte[] Rgba; public readonly int W; public readonly int H; public readonly string Path; public Job(byte[] rgba, int w, int h, string path) { Rgba = rgba; W = w; H = h; Path = path; } } private const int Capacity = 256; private static readonly BlockingCollection<Job> _queue = new BlockingCollection<Job>(256); private static Thread? _worker; private static volatile bool _started; private static readonly object _lifecycleGate = new object(); private static bool _shuttingDown; public static void Enqueue(byte[] rgba, int w, int h, string path) { lock (_lifecycleGate) { if (_shuttingDown) { Debug.LogWarning((object)"[WKLoc] Dump queue is shutting down, dropping a texture."); return; } EnsureWorker(); if (!_queue.TryAdd(new Job(rgba, w, h, path))) { Debug.LogWarning((object)"[WKLoc] Dump queue full, dropping a texture."); } } } private static void EnsureWorker() { if (!_started) { _started = true; _worker = new Thread(WorkerLoop) { IsBackground = true, Name = "WKLoc-DumpWorker" }; _worker.Start(); } } private static void WorkerLoop() { foreach (Job item in _queue.GetConsumingEnumerable()) { try { byte[] bytes = PngWriter.EncodeRgba32(item.Rgba, item.W, item.H, true); string directoryName = Path.GetDirectoryName(item.Path); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } File.WriteAllBytes(item.Path, bytes); } catch (Exception ex) { Debug.LogError((object)("[WKLoc] Dump write " + item.Path + ": " + ex.Message)); } } } public static void Shutdown() { Thread worker; lock (_lifecycleGate) { if (_shuttingDown) { return; } _shuttingDown = true; if (!_started) { return; } _queue.CompleteAdding(); worker = _worker; } try { worker?.Join(2000); } catch { } } } } namespace WKLoc.Assets { public static class FontProvider { private static readonly Dictionary<string, TMP_FontAsset> _tmp = new Dictionary<string, TMP_FontAsset>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, Font> _legacy = new Dictionary<string, Font>(StringComparer.OrdinalIgnoreCase); private static TMP_FontAsset? _mainTmp; private static Font? _mainLegacy; private static bool _fallbackDone; public static void Init(string pluginDir, string overrideTmpName, string overrideLegacyName, ManualLogSource log) { try { string text = LoadFirstFontBundle(pluginDir, log); if (text == null) { log.LogInfo((object)"No font AssetBundle with TMP/legacy fonts found next to the plugin."); } else { log.LogInfo((object)("Font AssetBundle loaded: " + Path.GetFileName(text))); } string text2 = overrideTmpName?.Trim() ?? ""; TMP_FontAsset value2; if (text2.Length > 0 && _tmp.TryGetValue(text2, out TMP_FontAsset value)) { _mainTmp = value; } else if (_tmp.TryGetValue("default", out value2)) { _mainTmp = value2; } else { using Dictionary<string, TMP_FontAsset>.Enumerator enumerator = _tmp.GetEnumerator(); if (enumerator.MoveNext()) { _mainTmp = enumerator.Current.Value; } } string text3 = overrideLegacyName?.Trim() ?? ""; Font value4; if (text3.Length > 0 && _legacy.TryGetValue(text3, out Font value3)) { _mainLegacy = value3; } else if (_legacy.TryGetValue("default", out value4)) { _mainLegacy = value4; } else { using Dictionary<string, Font>.Enumerator enumerator2 = _legacy.GetEnumerator(); if (enumerator2.MoveNext()) { _mainLegacy = enumerator2.Current.Value; } } if ((Object)(object)_mainLegacy == (Object)null) { try { _mainLegacy = Font.CreateDynamicFontFromOSFont(new string[7] { "Arial", "Segoe UI", "Tahoma", "Verdana", "Liberation Sans", "DejaVu Sans", "Noto Sans" }, 16); if ((Object)(object)_mainLegacy != (Object)null) { Object.DontDestroyOnLoad((Object)(object)_mainLegacy); } } catch (Exception ex) { log.LogWarning((object)("OS legacy font create failed: " + ex.Message)); } } log.LogInfo((object)(string.Format("Bundle fonts: {0} TMP [{1}], ", _tmp.Count, string.Join(", ", _tmp.Keys)) + string.Format("{0} legacy [{1}]. ", _legacy.Count, string.Join(", ", _legacy.Keys)) + "MainTMP: " + (((Object)(object)_mainTmp != (Object)null) ? ((Object)_mainTmp).name : "<none>") + ", MainLegacy: " + (((Object)(object)_mainLegacy != (Object)null) ? ((Object)_mainLegacy).name : "<none>"))); EnsureFallback(); } catch (Exception ex2) { log.LogError((object)("Font bundle error: " + ex2.Message)); } } private static string? LoadFirstFontBundle(string pluginDir, ManualLogSource log) { if (!Directory.Exists(pluginDir)) { return null; } string[] candidatePaths = FontBundleLocator.GetCandidatePaths((IEnumerable<string>)Directory.GetFiles(pluginDir)); foreach (string text in candidatePaths) { AssetBundle val = null; try { val = AssetBundle.LoadFromFile(text); if (!((Object)(object)val == (Object)null)) { int count = _tmp.Count; int count2 = _legacy.Count; TMP_FontAsset[] array = val.LoadAllAssets<TMP_FontAsset>(); foreach (TMP_FontAsset val2 in array) { _tmp[((Object)val2).name.Trim()] = val2; } Font[] array2 = val.LoadAllAssets<Font>(); foreach (Font val3 in array2) { _legacy[((Object)val3).name.Trim()] = val3; } if (_tmp.Count > count || _legacy.Count > count2) { return text; } } } catch (Exception ex) { log.LogDebug((object)("Skipped font bundle candidate '" + Path.GetFileName(text) + "': " + ex.Message)); } finally { if ((Object)(object)val != (Object)null) { val.Unload(false); } } } return null; } public static void EnsureFallback() { if (_fallbackDone || (Object)(object)_mainTmp == (Object)null) { return; } try { List<TMP_FontAsset> fallbackFontAssets = TMP_Settings.fallbackFontAssets; if (fallbackFontAssets != null) { if (!fallbackFontAssets.Contains(_mainTmp)) { fallbackFontAssets.Add(_mainTmp); } _fallbackDone = true; } } catch { } } public static TMP_FontAsset? GetTmp(string? name) { if (string.IsNullOrEmpty(name) || !_tmp.TryGetValue(name.Trim(), out TMP_FontAsset value)) { return null; } return value; } public static Font? GetLegacy(string? name) { if (string.IsNullOrEmpty(name) || !_legacy.TryGetValue(name.Trim(), out Font value)) { return null; } return value; } public static void TryApply(TMP_Text text) { TryApply(text, null); } public static void TryApply(TMP_Text text, TMP_FontAsset? font) { if (!((Object)(object)text == (Object)null)) { TMP_FontAsset val = font ?? _mainTmp; if (!((Object)(object)val == (Object)null) && (Object)(object)text.font != (Object)(object)val) { text.font = val; } } } public static void TryApply(Text text) { TryApply(text, null); } public static void TryApply(Text text, Font? font) { if (!((Object)(object)text == (Object)null)) { Font val = font ?? _mainLegacy; if (!((Object)(object)val == (Object)null) && (Object)(object)text.font != (Object)(object)val) { text.font = val; } } } } public static class TextureSwapper { private sealed class SpriteLookup { public int Gen; public Sprite? Result; } private static readonly Dictionary<string, Texture2D> _textureMap = new Dictionary<string, Texture2D>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, Sprite> _variantCache = new Dictionary<string, Sprite>(StringComparer.OrdinalIgnoreCase); private static readonly RetirementQueue<Sprite> _retiredSprites = new RetirementQueue<Sprite>(); private static readonly List<Sprite> _pendingRetiredSprites = new List<Sprite>(); private static readonly List<Sprite> _retainedSprites = new List<Sprite>(); private static readonly List<Texture2D> _retainedTextures = new List<Texture2D>(); private static readonly Action<Sprite> _destroySprite = delegate(Sprite sprite) { if ((Object)(object)sprite != (Object)null) { Object.Destroy((Object)(object)sprite); } }; private static readonly ConditionalWeakTable<Sprite, SpriteLookup> _spriteLookup = new ConditionalWeakTable<Sprite, SpriteLookup>(); private static int _lookupGeneration; private static bool _shutdown; public static void Init(string langDir, ManualLogSource log) { string path = Path.Combine(langDir, "Texture"); if (!Directory.Exists(path)) { log.LogInfo((object)"No Texture dir."); return; } string[] files = Directory.GetFiles(path, "*.png"); for (int i = 0; i < files.Length; i++) { LoadTextureFile(files[i], log); } log.LogInfo((object)$"Textures: {_textureMap.Count}."); } private static bool LoadTextureFile(string file, ManualLogSource log) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown Texture2D val = null; try { string originalName = TextureNameNormalizer.GetOriginalName(Path.GetFileNameWithoutExtension(file)); if (string.IsNullOrEmpty(originalName)) { return false; } byte[] array = File.ReadAllBytes(file); if (_textureMap.TryGetValue(originalName, out Texture2D value)) { if (!ImageConversion.LoadImage(value, array)) { throw new InvalidDataException("Invalid PNG: " + file); } ((Object)value).name = originalName; } else { val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, array)) { throw new InvalidDataException("Invalid PNG: " + file); } ((Object)val).name = originalName; _textureMap[originalName] = val; val = null; } return true; } catch (Exception ex) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } log.LogError((object)("Texture " + file + ": " + ex.Message)); return false; } } public static void Reload(string langDir, ManualLogSource log) { _lookupGeneration++; List<Sprite> list = new List<Sprite>(_variantCache.Values); list.AddRange(_pendingRetiredSprites); _pendingRetiredSprites.Clear(); _variantCache.Clear(); string path = Path.Combine(langDir, "Texture"); HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (Directory.Exists(path)) { string[] files = Directory.GetFiles(path, "*.png"); foreach (string obj in files) { string originalName = TextureNameNormalizer.GetOriginalName(Path.GetFileNameWithoutExtension(obj)); if (_textureMap.ContainsKey(originalName)) { hashSet.Add(originalName); } if (LoadTextureFile(obj, log)) { hashSet.Add(originalName); } } } List<string> list2 = new List<string>(); foreach (KeyValuePair<string, Texture2D> item in _textureMap) { if (!hashSet.Contains(item.Key)) { list2.Add(item.Key); } } foreach (string item2 in list2) { _retainedTextures.Add(_textureMap[item2]); _textureMap.Remove(item2); } for (int num = _retainedSprites.Count - 1; num >= 0; num--) { if (_textureMap.ContainsKey(((Object)_retainedSprites[num]).name)) { list.Add(_retainedSprites[num]); _retainedSprites.RemoveAt(num); } } foreach (Sprite item3 in list) { if (_textureMap.ContainsKey(((Object)item3).name)) { _pendingRetiredSprites.Add(item3); } else { _retainedSprites.Add(item3); } } log.LogInfo((object)$"Textures: {_textureMap.Count}."); } public static bool Has(string name) { if (!string.IsNullOrEmpty(name)) { return _textureMap.ContainsKey(name); } return false; } public static void TickCleanup(int maxItems = 32) { _retiredSprites.Drain(maxItems, _destroySprite); } public static void Shutdown() { if (_shutdown) { return; } _shutdown = true; foreach (Sprite value in _variantCache.Values) { _retiredSprites.Enqueue(value); } _variantCache.Clear(); foreach (Sprite pendingRetiredSprite in _pendingRetiredSprites) { _retiredSprites.Enqueue(pendingRetiredSprite); } _pendingRetiredSprites.Clear(); foreach (Sprite retainedSprite in _retainedSprites) { _retiredSprites.Enqueue(retainedSprite); } _retainedSprites.Clear(); _retiredSprites.Drain(int.MaxValue, _destroySprite); foreach (Texture2D value2 in _textureMap.Values) { if ((Object)(object)value2 != (Object)null) { Object.Destroy((Object)(object)value2); } } _textureMap.Clear(); foreach (Texture2D retainedTexture in _retainedTextures) { if ((Object)(object)retainedTexture != (Object)null) { Object.Destroy((Object)(object)retainedTexture); } } _retainedTextures.Clear(); } private static void MatchSampling(Texture original, Texture2D replacement) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)original == (Object)null) && !((Object)(object)replacement == (Object)null)) { ((Texture)replacement).filterMode = original.filterMode; ((Texture)replacement).wrapMode = original.wrapMode; ((Texture)replacement).anisoLevel = original.anisoLevel; } } public static Sprite? GetReplacementSprite(Sprite current) { if ((Object)(object)current == (Object)null) { return null; } if (_spriteLookup.TryGetValue(current, out SpriteLookup value) && value.Gen == _lookupGeneration) { return value.Result; } Sprite result = BuildReplacementSprite(current); _spriteLookup.Remove(current); _spriteLookup.Add(current, new SpriteLookup { Gen = _lookupGeneration, Result = result }); return result; } private static Sprite? BuildReplacementSprite(Sprite current) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) if (!_textureMap.TryGetValue(((Object)current).name, out Texture2D value)) { return null; } MatchSampling((Texture)(object)current.texture, value); float x = current.pivot.x; float y = current.pivot.y; Rect rect = current.rect; float width = ((Rect)(ref rect)).width; rect = current.rect; ValueTuple<float, float> valueTuple = SpriteGeometry.NormalizedPivot(x, y, width, ((Rect)(ref rect)).height); float item = valueTuple.Item1; float item2 = valueTuple.Item2; float pixelsPerUnit = current.pixelsPerUnit; rect = current.rect; float num = SpriteGeometry.MatchedPpu(pixelsPerUnit, ((Rect)(ref rect)).width, (float)((Texture)value).width); float x2 = current.border.x; float y2 = current.border.y; float z = current.border.z; float w = current.border.w; rect = current.rect; float width2 = ((Rect)(ref rect)).width; rect = current.rect; ValueTuple<float, float, float, float> valueTuple2 = SpriteGeometry.ScaleBorder(x2, y2, z, w, width2, ((Rect)(ref rect)).height, (float)((Texture)value).width, (float)((Texture)value).height); float item3 = valueTuple2.Item1; float item4 = valueTuple2.Item2; float item5 = valueTuple2.Item3; float item6 = valueTuple2.Item4; string key = ((Object)current).name + "|" + Math.Round(item, 4) + "|" + Math.Round(item2, 4) + "|" + Math.Round(num, 3) + "|" + Math.Round(item3, 2) + "|" + Math.Round(item4, 2) + "|" + Math.Round(item5, 2) + "|" + Math.Round(item6, 2); if (_variantCache.TryGetValue(key, out Sprite value2)) { if (!((Object)(object)value2 == (Object)(object)current)) { return value2; } return null; } Sprite val = Sprite.Create(value, new Rect(0f, 0f, (float)((Texture)value).width, (float)((Texture)value).height), new Vector2(item, item2), num, 0u, (SpriteMeshType)0, new Vector4(item3, item4, item5, item6)); ((Object)val).name = ((Object)current).name; _variantCache[key] = val; return val; } public static Texture? GetReplacementTexture(Texture current) { if ((Object)(object)current == (Object)null) { return null; } if (_textureMap.TryGetValue(((Object)current).name, out Texture2D value) && (Object)(object)value != (Object)(object)current) { MatchSampling(current, value); return (Texture?)(object)value; } return null; } public static void ReplaceMaterials() { if (_textureMap.Count != 0) { Renderer[] array = Resources.FindObjectsOfTypeAll<Renderer>(); for (int i = 0; i < array.Length; i++) { ReplaceRendererMaterials(array[i]); } } } public static void ReplaceRendererMaterials(Renderer r) { if ((Object)(object)r == (Object)null || _textureMap.Count == 0) { return; } Material[] sharedMaterials = r.sharedMaterials; if (sharedMaterials == null) { return; } Material[] array = sharedMaterials; foreach (Material val in array) { if ((Object)(object)val == (Object)null) { continue; } if (val.HasProperty("_MainTex") && (Object)(object)val.mainTexture != (Object)null) { Texture replacementTexture = GetReplacementTexture(val.mainTexture); if ((Object)(object)replacementTexture != (Object)null) { val.mainTexture = replacementTexture; } } foreach (string item in TextureSlots.Allowed) { if (!val.HasProperty(item)) { continue; } Texture texture = val.GetTexture(item); if (!((Object)(object)texture == (Object)null)) { Texture replacementTexture2 = GetReplacementTexture(texture); if ((Object)(object)replacementTexture2 != (Object)null) { val.SetTexture(item, replacementTexture2); } } } } } public static void ReplaceImage(Image img) { if (!((Object)(object)img == (Object)null) && !((Object)(object)img.sprite == (Object)null)) { Sprite replacementSprite = GetReplacementSprite(img.sprite); if ((Object)(object)replacementSprite != (Object)null) { img.sprite = replacementSprite; } } } public static void ReplaceRawImage(RawImage ri) { if (!((Object)(object)ri == (Object)null) && !((Object)(object)ri.texture == (Object)null)) { Texture replacementTexture = GetReplacementTexture(ri.texture); if ((Object)(object)replacementTexture != (Object)null) { ri.texture = replacementTexture; } } } public static void ReplaceSpriteRenderer(SpriteRenderer sr) { if (!((Object)(object)sr == (Object)null) && !((Object)(object)sr.sprite == (Object)null)) { Sprite replacementSprite = GetReplacementSprite(sr.sprite); if ((Object)(object)replacementSprite != (Object)null) { sr.sprite = replacementSprite; } } } public static void ReplaceAllInScene() { if (_textureMap.Count == 0) { return; } Image[] array = Resources.FindObjectsOfTypeAll<Image>(); for (int i = 0; i < array.Length; i++) { ReplaceImage(array[i]); } RawImage[] array2 = Resources.FindObjectsOfTypeAll<RawImage>(); for (int i = 0; i < array2.Length; i++) { ReplaceRawImage(array2[i]); } SpriteRenderer[] array3 = Resources.FindObjectsOfTypeAll<SpriteRenderer>(); for (int i = 0; i < array3.Length; i++) { ReplaceSpriteRenderer(array3[i]); } ReplaceMaterials(); foreach (Sprite pendingRetiredSprite in _pendingRetiredSprites) { _retiredSprites.Enqueue(pendingRetiredSprite); } _pendingRetiredSprites.Clear(); } } } namespace WKLoc.Apply { public static class ContextMenuRefresher { private static readonly DeferredRefreshQueue<Transform> _queue = new DeferredRefreshQueue<Transform>(); public static void Queue(Transform root, int frames = 2) { if (!((Object)(object)root == (Object)null)) { _queue.Enqueue(root, frames); } } public static void Tick() { _queue.Tick((Action<Transform>)Refresh); } private static void Refresh(Transform root) { if ((Object)(object)root == (Object)null) { return; } try { TextPipeline.ApplyAllInChildren((Component)(object)root); TMP_Text[] componentsInChildren = ((Component)root).GetComponentsInChildren<TMP_Text>(true); foreach (TMP_Text val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { ((Graphic)val).SetAllDirty(); val.ForceMeshUpdate(true, false); } } Text[] componentsInChildren2 = ((Component)root).GetComponentsInChildren<Text>(true); foreach (Text val2 in componentsInChildren2) { if (!((Object)(object)val2 == (Object)null)) { val2.cachedTextGenerator.Invalidate(); ((Graphic)val2).SetAllDirty(); val2.FontTextureChanged(); } } Canvas.ForceUpdateCanvases(); } catch (Exception ex) { Diagnostics.WarnOnce("context-refresh", "Context menu refresh failed: " + ex.Message); } } } public static class FrameBudget { private const int Size = 60; private static readonly float[] _frames = new float[60]; private static int _idx; private static int _count; private static readonly Stopwatch _sw = new Stopwatch(); public static void StartFrame() { float num = Time.unscaledDeltaTime * 1000f; if (num > 0f && num <= 1000f) { _frames[_idx] = num; _idx = (_idx + 1) % 60; if (_count < 60) { _count++; } } _sw.Restart(); } public static float BudgetMs() { return BudgetMath.ComputeBudgetMs(_frames, _count); } public static bool Exceeded(float budgetMs) { return _sw.Elapsed.TotalMilliseconds > (double)budgetMs; } } public static class FrameDriver { public static void Install(Type marker, Action tick) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) PlayerLoopSystem currentPlayerLoop = PlayerLoop.GetCurrentPlayerLoop(); int num = FindPhase(currentPlayerLoop.subSystemList, typeof(Update)); if (num >= 0) { PlayerLoopSystem val = currentPlayerLoop.subSystemList[num]; PlayerLoopSystem[] array = val.subSystemList ?? Array.Empty<PlayerLoopSystem>(); if (FindPhase(array, marker) < 0) { PlayerLoopSystem[] array2 = (PlayerLoopSystem[])(object)new PlayerLoopSystem[array.Length + 1]; Array.Copy(array, array2, array.Length); array2[^1] = new PlayerLoopSystem { type = marker, updateDelegate = new UpdateFunction(RunSafely) }; val.subSystemList = array2; currentPlayerLoop.subSystemList[num] = val; PlayerLoop.SetPlayerLoop(currentPlayerLoop); } } void RunSafely() { try { tick(); } catch { } } } private static int FindPhase(PlayerLoopSystem[]? phases, Type type) { if (phases == null) { return -1; } for (int i = 0; i < phases.Length; i++) { if (phases[i].type == type) { return i; } } return -1; } } [HarmonyPatch] public static class HookSetters { [HarmonyPatch] public static class TmpFormattedTextHooks { public static IEnumerable<MethodBase> TargetMethods() { MethodInfo methodInfo = AccessTools.Method(typeof(TMP_Text), "SetText", new Type[2] { typeof(string), typeof(bool) }, (Type[])null); if (methodInfo != null) { yield return methodInfo; } for (int floats = 1; floats <= 7; floats++) { Type[] array = new Type[1 + floats]; array[0] = typeof(string); for (int i = 0; i < floats; i++) { array[1 + i] = typeof(float); } MethodInfo methodInfo2 = AccessTools.Method(typeof(TMP_Text), "SetText", array, (Type[])null); if (methodInfo2 != null) { yield return methodInfo2; } } } [HarmonyPrefix] public static void BeforeSetText(TMP_Text __instance, ref string sourceText) { TouchedComponents.Track((Object)(object)__instance); if (TextPipeline.HookSuppressionDepth <= 0) { TextPipeline.Transform(__instance, ref sourceText); } } [HarmonyPostfix] public static void AfterSetText(TMP_Text __instance) { TextPipeline.PostTransform(__instance); } } [HarmonyPatch] public static class ObjectiveViewerHooks { [HarmonyPatch(typeof(UI_ObjectiveViewer), "Awake")] [HarmonyPostfix] public static void RefreshObjectiveViewerAfterAwake(UI_ObjectiveViewer __instance) { ObjectiveViewerRefresher.Register(__instance); TextPipeline.ApplyAllInChildren((Component)(object)__instance); ObjectiveViewerRefresher.Queue(__instance); } [HarmonyPatch(typeof(UI_ObjectiveViewer), "ShowViewer")] [HarmonyPostfix] public static void RefreshObjectiveViewerAfterShow(UI_ObjectiveViewer __instance) { ObjectiveViewerRefresher.Register(__instance); TextPipeline.ApplyAllInChildren((Component)(object)__instance); ObjectiveViewerRefresher.Queue(__instance); } [HarmonyPatch(typeof(UI_ObjectiveViewer), "SetTitle")] [HarmonyPrefix] public static void TranslateObjectiveViewerTitle(ref string s) { TextPipeline.TransformDetached(ref s); } [HarmonyPatch(typeof(UI_ObjectiveViewer), "CreateOrUpdateObjective")] [HarmonyPrefix] public static void TranslateObjectiveStrings(ref string title, ref string desc) { TextPipeline.TransformDetached(ref title); TextPipeline.TransformDetached(ref desc); } [HarmonyPatch(typeof(UI_ObjectiveViewer), "CreateOrUpdateObjective")] [HarmonyPostfix] public static void RefreshObjectiveViewerAfterCreateOrUpdate() { ObjectiveViewerRefresher.QueueAll(1); } } [HarmonyPatch] public static class ContextMenuHooks { [HarmonyPatch(typeof(ContextMenu_Manager), "ShowMenu")] [HarmonyPostfix] public static void RefreshContextMenuOptionsAfterShow(ContextMenu_Manager __instance) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.contextMenu == (Object)null)) { TextPipeline.ApplyAllInChildren((Component)(object)__instance.contextMenu); ContextMenuRefresher.Queue((Transform)(object)__instance.contextMenu); } } } [HarmonyPatch] public static class OsFileHooks { [HarmonyPatch(typeof(OS_File), "Initialize")] [HarmonyPostfix] public static void QueueOsFileAfterInitialize(OS_File __instance) { OsFileBackplateRefresher.Queue(__instance); } [HarmonyPatch(typeof(OS_File), "SetText")] [HarmonyPostfix] public static void QueueOsFileAfterSetText(OS_File __instance) { OsFileBackplateRefresher.Queue(__instance); } } [HarmonyPatch] public static class PerkCardHooks { [HarmonyPatch(typeof(App_PerkPage_Card), "Initialize")] [HarmonyPostfix] public static void RefreshPerkCardAfterInitialize(App_PerkPage_Card __instance) { TextPipeline.ApplyAllInChildren((Component)(object)__instance); } [HarmonyPatch(typeof(App_PerkPage_Card), "BuyCard")] [HarmonyPostfix] public static void RefreshPerkCardAfterPurchase(App_PerkPage_Card __instance) { TextPipeline.ApplyAllInChildren((Component)(object)__instance); } } [HarmonyPatch] public static class FacilityCardHooks { [HarmonyPatch(typeof(App_Facility_Card), "Initialize")] [HarmonyPostfix] public static void RefreshFacilityCardAfterInitialize(App_Facility_Card __instance) { TextPipeline.ApplyAllInChildren((Component)(object)__instance); } [HarmonyPatch(typeof(App_Facility_Card), "CheckLock")] [HarmonyPostfix] public static void RefreshFacilityCardAfterLockCheck(App_Facility_Card __instance) { TextPipeline.ApplyAllInChildren((Component)(object)__instance); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static void BeforeTmpTextSet(TMP_Text __instance, ref string value) { TouchedComponents.Track((Object)(object)__instance); if (TextPipeline.HookSuppressionDepth <= 0) { TextPipeline.Transform(__instance, ref value); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] public static void AfterTmpTextSet(TMP_Text __instance) { TextPipeline.PostTransform(__instance); } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static void BeforeLegacyTextSet(Text __instance, ref string value, out bool __state) { TouchedComponents.Track((Object)(object)__instance); string text = __instance.text; if (TextPipeline.HookSuppressionDepth <= 0) { TextPipeline.Transform(__instance, ref value); } __state = !string.Equals(text, value, StringComparison.Ordinal); } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] public static void AfterLegacyTextSet(Text __instance, bool __state) { TextPipeline.PostTransform(__instance); if (!__state) { return; } try { if (__instance != null) { __instance.FontTextureChanged(); } } catch { } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static void BeforeLegacyInputSet(InputField __instance, ref string value) { TouchedComponents.Track((Object)(object)__instance); if (TextPipeline.HookSuppressionDepth <= 0) { TextPipeline.Transform(__instance, ref value); TextPipeline.PrepareInputRichText(__instance, ref value); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] public static void AfterLegacyInputSet(InputField __instance) { TextPipeline.PostInputChanged(__instance); } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static void BeforeTmpInputSet(TMP_InputField __instance, ref string value) { TouchedComponents.Track((Object)(object)__instance); if (TextPipeline.HookSuppressionDepth <= 0) { TextPipeline.Transform(__instance, ref value); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] public static void AfterTmpInputSet(TMP_InputField __instance) { TextPipeline.PostInputChanged(__instance); } [HarmonyPatch(typeof(TextMeshProUGUI), "Awake")] [HarmonyPostfix] public static void PrimeTmpUiOnAwake(TextMeshProUGUI __instance) { RefreshOne((Component)(object)__instance); } [HarmonyPatch(typeof(TextMeshProUGUI), "OnEnable")] [HarmonyPostfix] public static void PrimeTmpUiOnEnable(TextMeshProUGUI __instance) { RefreshOne((Component)(object)__instance); } [HarmonyPatch(typeof(TextMeshPro), "Awake")] [HarmonyPostfix] public static void PrimeWorldTmpOnAwake(TextMeshPro __instance) { RefreshOne((Component)(object)__instance); } [HarmonyPatch(typeof(TextMeshPro), "OnEnable")] [HarmonyPostfix] public static void PrimeWorldTmpOnEnable(TextMeshPro __instance) { RefreshOne((Component)(object)__instance); } [HarmonyPatch(typeof(Text), "OnEnable")] [HarmonyPostfix] public static void PrimeLegacyOnEnable(Text __instance) { RefreshOne((Component)(object)__instance); } [HarmonyPatch(typeof(InputField), "OnEnable")] [HarmonyPostfix] public static void PrimeLegacyInputOnEnable(InputField __instance) { RefreshOne((Component)(object)__instance); } [HarmonyPatch(typeof(TMP_InputField), "OnEnable")] [HarmonyPostfix] public static void PrimeTmpInputOnEnable(TMP_InputField __instance) { RefreshOne((Component)(object)__instance); } private static void RefreshOne(Component c) { if (!((Object)(object)c == (Object)null)) { TouchedComponents.Track((Object)(object)c); TextPipeline.ApplyAll(c); } } } [HarmonyPatch] public static class HookTextures { [HarmonyPatch(typeof(MaskableGraphic), "OnEnable")] [HarmonyPostfix] public static void Graphic_Enable(MaskableGraphic __instance) { if (!TextPipeline.Enabled) { return; } try { Image val = (Image)(object)((__instance is Image) ? __instance : null); if (val != null) { TextureSwapper.ReplaceImage(val); return; } RawImage val2 = (RawImage)(object)((__instance is RawImage) ? __instance : null); if (val2 != null) { TextureSwapper.ReplaceRawImage(val2); } } catch { } } public static Sprite FilterSprite(Sprite value) { if ((Object)(object)value == (Object)null) { return value; } try { if (TextureCollector.Enabled) { TextureCollector.DumpSprite(value); } if (TextPipeline.Enabled) { Sprite replacementSprite = TextureSwapper.GetReplacementSprite(value); if ((Object)(object)replacementSprite != (Object)null) { return replacementSprite; } } } catch (Exception ex) { Debug.LogError((object)("[WKLoc] FilterSprite: " + ex.Message)); } return value; } public static Texture FilterTexture(Texture value) { if ((Object)(object)value == (Object)null) { return value; } try { if (TextureCollector.Enabled) { TextureCollector.DumpOne(value); } if (TextPipeline.Enabled) { Texture replacementTexture = TextureSwapper.GetReplacementTexture(value); if ((Object)(object)replacementTexture != (Object)null) { return replacementTexture; } } } catch (Exception ex) { Debug.LogError((object)("[WKLoc] FilterTexture: " + ex.Message)); } return value; } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static void Image_Set(ref Sprite value) { value = FilterSprite(value); } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static void SR_Set(ref Sprite value) { value = FilterSprite(value); } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static void Raw_Set(ref Texture value) { value = FilterTexture(value); } [HarmonyPatch(typeof(Resources), "Load", new Type[] { typeof(string), typeof(Type) })] [HarmonyPostfix] public static void Load_Post(ref Object __result) { Object obj = __result; Sprite val = (Sprite)(object)((obj is Sprite) ? obj : null); if (val != null) { Sprite val2 = FilterSprite(val); if ((Object)(object)val2 != (Object)(object)val) { __result = (Object)(object)val2; } return; } Object obj2 = __result; Texture val3 = (Texture)(object)((obj2 is Texture) ? obj2 : null); if (val3 != null) { Texture val4 = FilterTexture(val3); if ((Object)(object)val4 != (Object)(object)val3) { __result = (Object)(object)val4; } } } } public static class HookThirdParty { [HarmonyPatch(typeof(UT_TextScrawl), "ShowText")] public static class Scrawl_ShowText { [HarmonyPrefix] public static void BeforeScrawlStarts(UT_TextScrawl __instance, ref string s) { if (TextPipeline.Enabled) { string original = s; s = Translate(s); TMP_Text componentInChildren = ((Component)__instance).GetComponentInChildren<TMP_Text>(true); if ((Object)(object)componentInChildren != (Object)null) { TextPipeline.RememberSourceText(componentInChildren, original); } else { Text componentInChildren2 = ((Component)__instance).GetComponentInChildren<Text>(true); if ((Object)(object)componentInChildren2 != (Object)null) { TextPipeline.RememberSourceText(componentInChildren2, original); } } } TextPipeline.HookSuppressionDepth++; } [HarmonyFinalizer] public static void AfterScrawlAttempt() { TextPipeline.HookSuppressionDepth--; } } [HarmonyPatch(typeof(TypewriterCore), "ShowText", new Type[] { typeof(string) })] public static class Typewriter_ShowText { [HarmonyPrefix] public static void BeforeTypewriterStarts(ref string text) { if (TextPipeline.Enabled) { text = Translate(text); } TextPipeline.HookSuppressionDepth++; } [HarmonyFinalizer] public static void AfterTypewriterAttempt() { TextPipeline.HookSuppressionDepth--; } } [HarmonyPatch(typeof(TAnimCore), "ConvertText")] public static class TAnim_ConvertText { [HarmonyPrefix] public static void BeforeAnimatorParse(TAnimCore __instance, ref string textToParse) { if (TextPipeline.Enabled) { if (WkLocPlugin.Cfg.DebugLog.Value) { string text = textToParse ?? "<null>"; string text2 = WkLocPlugin.Store.Resolve(text); bool flag = text2 != null && text2 != text; Debug.Log((object)string.Format("[WKLoc] TAnim.ConvertText src=[{0}] len={1} hit={2}", text, text.Length, flag ? "YES" : "NO")); } string text3 = textToParse ?? string.Empty; textToParse = Translate(textToParse); TextAnimator_TMP val = (TextAnimator_TMP)(object)((__instance is TextAnimator_TMP) ? __instance : null); if (val != null && (Object)(object)val.TMProComponent != (Object)null) { if (textToParse != text3) { TextPipeline.RememberSourceText(val.TMProComponent, text3); } TMP_FontAsset val2 = ResolveTmpFont(TextPipeline.RecallSourceText(val.TMProComponent, text3)); if ((Object)(object)val2 != (Object)null) { FontProvider.TryApply(val.TMProComponent, val2); } if (WkLocPlugin.Cfg.DebugLog.Value) { Debug.Log((object)("[WKLoc] TAnim font now=[" + (((Object)(object)val.TMProComponent.font != (Object)null) ? ((Object)val.TMProComponent.font).name : "<null>") + "] go='" + ((Object)((Component)val.TMProComponent).gameObject).name + "'")); } } } TextPipeline.HookSuppressionDepth++; } [HarmonyFinalizer] public static void AfterAnimatorParseAttempt() { TextPipeline.HookSuppressionDepth--; } } private static string Translate(string s) { return ThirdPartyTextTranslator.TranslateOrCapture(s, (Func<string, string>)WkLocPlugin.Store.Resolve, (Action<string>)TextCollector.Capture); } private static TMP_FontAsset? ResolveTmpFont(string s) { if (string.IsNullOrWhiteSpace(s)) { return null; } string font = WkLocPlugin.Store.GetFont(s); if (font == null) { string text = s.ToLowerInvariant(); if (text != s) { font = WkLocPlugin.Store.GetFont(text); } } return FontProvider.GetTmp(font); } } public static class ObjectiveViewerRefresher { private static readonly DeferredRefreshQueue<UI_ObjectiveViewer> _queue = new DeferredRefreshQueue<UI_ObjectiveViewer>(); private static readonly List<WeakReference<UI_ObjectiveViewer>> _known = new List<WeakReference<UI_ObjectiveViewer>>(); public static void Register(UI_ObjectiveViewer viewer) { if ((Object)(object)viewer == (Object)null) { return; } for (int num = _known.Count - 1; num >= 0; num--) { if (!_known[num].TryGetTarget(out UI_ObjectiveViewer target) || (Object)(object)target == (Object)null) { _known.RemoveAt(num); } else if (target == viewer) { return; } } _known.Add(new WeakReference<UI_ObjectiveViewer>(viewer)); } public static void QueueAll(int frames) { for (int num = _known.Count - 1; num >= 0; num--) { if (!_known[num].TryGetTarget(out UI_ObjectiveViewer target) || (Object)(object)target == (Object)null) { _known.RemoveAt(num); } else { Queue(target, frames); } } } public static void Queue(UI_ObjectiveViewer viewer, int frames = 2) { if (!((Object)(object)viewer == (Object)null)) { _queue.Enqueue(viewer, frames); } } public static void Tick() { _queue.Tick((Action<UI_ObjectiveViewer>)Refresh); } public static void Refresh(UI_ObjectiveViewer viewer) { if ((Object)(object)viewer == (Object)null) { return; } try { TMP_Text[] componentsInChildren = ((Component)viewer).GetComponentsInChildren<TMP_Text>(true); foreach (TMP_Text val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !TextGuards.IsTAnim((Component)(object)val)) { ApplyObjectiveViewerText(val, !TextGuards.HasScrawl((Component)(object)val)); ((Graphic)val).SetAllDirty(); val.ForceMeshUpdate(true, false); } } Text[] componentsInChildren2 = ((Component)viewer).GetComponentsInChildren<Text>(true); foreach (Text val2 in componentsInChildren2) { if (!((Object)(object)val2 == (Object)null) && !TextGuards.IsInputChild((Graphic)(object)val2)) { ApplyObjectiveViewerText(val2, !TextGuards.HasScrawl((Component)(object)val2)); val2.cachedTextGenerator.Invalidate(); ((Graphic)val2).SetAllDirty(); val2.FontTextureChanged(); } } Canvas.ForceUpdateCanvases(); } catch (Exception ex) { Diagnostics.WarnOnce("objective-refresh", "Objective refresh failed: " + ex.Message); } } private static void ApplyObjectiveViewerText(object component, bool applyFont) { if (IsObjectiveHeader(component)) { TextPipeline.Reapply(component, applyFont); } else { TextPipeline.ApplyAll(component); } } private static bool IsObjectiveHeader(object component) { TMP_Text val = (TMP_Text)((component is TMP_Text) ? component : null); string text; if (val == null) { Text val2 = (Text)((component is Text) ? component : null); text = ((val2 == null) ? null : val2.text); } else { text = val.text; } string text2 = text; if (string.IsNullOrEmpty(text2)) { return false; } string text3 = TextPipeline.RecallSourceText(component, text2); if (!IsObjectiveHeaderString(text2)) { return IsObjectiveHeaderString(text3); } return true; } private static bool IsObjectiveHeaderString(string text) { string text2 = TextNormalizer.CanonicalKey(text); if (!text2.Contains("OBJECTIVES:")) { return text2.Contains("ЗАДАЧИ:"); } return true; } } public static class OsFileBackplateRefresher { private static readonly DeferredRefreshQueue<OS_File> _queue = new DeferredRefreshQueue<OS_File>(); public static void Queue(OS_File file, int frames = 2) { if (!((Object)(object)file == (Object)null)) { _queue.Enqueue(file, frames); } } public static void Tick() { _queue.Tick((Action<OS_File>)Refresh); } public static void Refresh(OS_File file) { if ((Object)(object)file == (Object)null) { return; } try { InputField nameText = file.nameText; if (!((Object)(object)nameText == (Object)null) && !((Object)(object)file.nameTextBackplate == (Object)null)) { if ((Object)(object)nameText.textComponent != (Object)null) { nameText.textComponent.supportRichText = true; nameText.textComponent.cachedTextGenerator.Invalidate(); nameText.textComponent.FontTextureChanged(); } Canvas.ForceUpdateCanvases(); file.ResetBackerWidth(); } } catch (Exception ex) { Diagnostics.WarnOnce("os-file-refresh", "OS file backplate refresh failed: " + ex.Message); } } } public static class RendererNet { public static float LatencySeconds = 3f; private const int BatchSize = 100; private static float _lastRefresh; private static Object[]? _rend; private static bool _forceRefresh; private static readonly BatchCursor _cursor = new BatchCursor(); public static void OnSceneChange() { _rend = null; _lastRefresh = 0f; _forceRefresh = true; _cursor.Reset(0); } public static void Tick(float budgetMs) { if (!TextPipeline.Enabled) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (_forceRefresh || _rend == null || (!_cursor.HasRemaining && ScanInterval.ShouldRefresh(realtimeSinceStartup, _lastRefresh, LatencySeconds))) { if (FrameBudget.Exceeded(budgetMs)) { return; } Object[] rend = (Object[])(object)Object.FindObjectsOfType<Renderer>(); _rend = rend; _cursor.Reset(_rend.Length); _lastRefresh = realtimeSinceStartup; _forceRefresh = false; } ProcessBatch(budgetMs); } private static void ProcessBatch(float budgetMs) { if (_rend == null || _rend.Length == 0 || !_cursor.HasRemaining || FrameBudget.Exceeded(budgetMs)) { return; } int num = default(int); int num2 = default(int); _cursor.Step(100, ref num, ref num2); for (int i = num; i < num2; i++) { Object val = _rend[i]; if (val == (Object)null) { continue; } try { SpriteRenderer val2 = (SpriteRenderer)(object)((val is SpriteRenderer) ? val : null); if (val2 != null) { TextureSwapper.ReplaceSpriteRenderer(val2); continue; } Renderer val3 = (Renderer)(object)((val is Renderer) ? val : null); if (val3 != null) { TextureSwapper.ReplaceRendererMaterials(val3); } } catch (Exception ex) { Diagnostics.WarnOnce("renderer-net", "Renderer scan failed: " + ex.Message); } } } } public static class Sweeper { private static bool _inputBroken; public static void Tick() { FrameBudget.StartFrame(); FontProvider.EnsureFallback(); OsFileBackplateRefresher.Tick(); ObjectiveViewerRefresher.Tick(); ContextMenuRefresher.Tick(); HandleHotkeys(); if (TranslationWatcher.ConsumePendingReload()) { Debug.Log((object)"[WKLoc] Translation files changed on disk - auto-reloading."); ReloadAll(); } TextureSwapper.TickCleanup(); if (WkLocPlugin.Cfg.EnablePolling.Value) { float budgetMs = FrameBudget.BudgetMs(); RendererNet.LatencySeconds = WkLocPlugin.Cfg.RendererScanSeconds.Value; RendererNet.Tick(budgetMs); TextureScanner.LatencySeconds = WkLocPlugin.Cfg.RendererScanSeconds.Value; TextureScanner.Tick(budgetMs); } } public static void ReloadAll() { int currentLevel = WkLocPlugin.Store.CurrentLevel; WkLocPlugin.Store = TranslationLoader.ReadAll(WkLocPlugin.Instance.LangDir, WkLocPlugin.Instance.Log, WkLocPlugin.Cfg.EnableTranslationScoping.Value); WkLocPlugin.Store.CurrentLevel = currentLevel; TextPipeline.InvalidateTranslationContext(); TextureSwapper.Reload(WkLocPlugin.Instance.LangDir, WkLocPlugin.Instance.Log); UiResizer.Init(WkLocPlugin.Instance.LangDir, WkLocPlugin.Instance.Log); TextPipeline.InvalidateResizeRules(); if (TextPipeline.Enabled) { TextureSwapper.ReplaceAllInScene(); } ForceRefresh(); Debug.Log((object)"[WKLoc] Reloaded translation, textures and resize rules."); } private static void ForceRefresh() { TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>(); foreach (TMP_Text val in array) { if (((Component)val).gameObject.activeInHierarchy && !TextGuards.IsTAnim((Component)(object)val)) { TextPipeline.Reapply(val, !TextGuards.HasScrawl((Component)(object)val)); } } Text[] array2 = Resources.FindObjectsOfTypeAll<Text>(); foreach (Text val2 in array2) { if (((Component)val2).gameObject.activeInHierarchy && !TextGuards.IsInputChild((Graphic)(object)val2)) { TextPipeline.Reapply(val2, !TextGuards.HasScrawl((Component)(object)val2)); } } InputField[] array3 = Resources.FindObjectsOfTypeAll<InputField>(); foreach (InputField val3 in array3) { if (((Component)val3).gameObject.activeInHierarchy) { TextPipeline.Reapply(val3); } } TMP_InputField[] array4 = Resources.FindObjectsOfTypeAll<TMP_InputField>(); foreach (TMP_InputField val4 in array4) { if (((Component)val4).gameObject.activeInHierarchy) { TextPipeline.Reapply(val4, !TextGuards.HasScrawl((Component)(object)val4)); } } RefreshTouchedComponents(); } public static void RefreshTouchedComponents() { foreach (Object item in TouchedComponents.Snapshot()) { TMP_Text val = (TMP_Text)(object)((item is TMP_Text) ? item : null); if (val != null) { TextPipeline.Reapply(val, !TextGuards.HasScrawl((Component)(object)val)); continue; } Text val2 = (Text)(object)((item is Text) ? item : null); if (val2 != null) { if (!TextGuards.IsInputChild((Graphic)(object)val2)) { TextPipeline.Reapply(val2, !TextGuards.HasScrawl((Component)(object)val2)); } continue; } InputField val3 = (InputField)(object)((item is InputField) ? item : null); if (val3 != null) { TextPipeline.Reapply(val3); continue; } TMP_InputField val4 = (TMP_InputField)(object)((item is TMP_InputField) ? item : null); if (val4 != null) { TextPipeline.Reapply(val4, !TextGuards.HasScrawl((Component)(object)val4)); } } } private static void HandleHotkeys() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) if (!WkLocPlugin.Cfg.EnableHotkeys.Value || _inputBroken) { return; } try { if (Input.GetKeyDown(WkLocPlugin.Cfg.ToggleKey.Value)) { TextPipeline.Enabled = !TextPipeline.Enabled; if (TextPipeline.Enabled) { TextureSwapper.ReplaceAllInScene(); } ForceRefresh(); Debug.Log((object)("[WKLoc] Translation " + (TextPipeline.Enabled ? "ON" : "OFF"))); } if (Input.GetKeyDown(WkLocPlugin.Cfg.ReloadKey.Value)) { ReloadAll(); } } catch (Exception ex) { _inputBroken = true; Debug.LogWarning((object)("[WKLoc] Legacy Input unavailable, hotkeys disabled: " + ex.Message)); } } } public static class TextGuards { public static bool HasScrawl(Component c) { try { return (Object)(object)c != (Object)null && (Object)(object)c.GetComponent<UT_TextScrawl>() != (Object)null; } catch { return false; } } public static bool IsInputChild(Graphic g) { try { if ((Object)(object)g == (Object)null) { return false; } InputField componentInParent = ((Component)g).GetComponentInParent<InputField>(); if ((Object)(object)componentInParent != (Object)null && ((Object)(object)componentInParent.textComponent == (Object)(object)g || (Object)(object)componentInParent.placeholder == (Object)(object)g)) { return true; } TMP_InputField componentInParent2 = ((Component)g).GetComponentInParent<TMP_InputField>(); if ((Object)(object)componentInParent2 != (Object)null && ((Object)(object)componentInParent2.textComponent == (Object)(object)g || (Object)(object)componentInParent2.placeholder == (Object)(object)g)) { return true; } return false; } catch { return false; } } public static bool IsCommandConsole(Component c) { try { return (Object)(object)c != (Object)null && (Object)(object)c.GetComponentInParent<CommandConsole>() != (Object)null; } catch { return false; } } public static bool IsTAnim(Component c) { try { return (Object)(object)c != (Object)null && (Object)(object)c.GetComponent<TAnimCore>() != (Object)null; } catch { return false; } } } public static class TextPipeline { private sealed class ResizeComponentState { public readonly ReloadableScaleState Scale = new ReloadableScaleState(); public bool AlignmentInitialized; public int BaseAlignment; public int LastAppliedAlignment; } private sealed class TextDirectives { public float? TextSize; public bool NeedsAutoSize; } [ThreadStatic] public static bool SuppressSetterHooks; [ThreadStatic] public static int HookSuppressionDepth; [ThreadStatic] private static bool _needsAutoSize; [ThreadStatic] private static float? _explicitTextSize; public static bool Enabled = true; private static readonly ConditionalWeakTable<object, string> _sourceByComponent = new ConditionalWeakTable<object, string>(); private static readonly ConditionalWeakTable<object, VersionedTextValue> _assigned = new ConditionalWeakTable<object, VersionedTextValue>(); private static readonly ConditionalWeakTable<object, ResizeComponentState> _resized = new ConditionalWeakTable<object, ResizeComponentState>(); private static readonly ConditionalWeakTable<object, TextDirectives> _directivesByComponent = new ConditionalWeakTable<object, TextDirectives>(); private static readonly Regex _hasLetters = new Regex("[a-zA-Zа-яА-ЯёЁ]", RegexOptions.Compiled); private static readonly Regex _sizePct = new Regex("<size=(\\d+(?:\\.\\d+)?)%>", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static int _translationGeneration; private static int _resizeGeneration; public static void InvalidateTranslationContext() { _translationGeneration++; } public static void InvalidateResizeRules() { _resizeGeneration++; } public static string AdaptLegacyRichText(Text t, string s) { if ((Object)(object)t == (Object)null || string.IsNullOrEmpty(s) || s.IndexOf("%>", StringComparison.Ordinal) < 0) { return s; } float baseSize = t.fontSize; float result; return _sizePct.Replace(s, (Match m) => float.TryParse(m.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out result) ? ("<size=" + Mathf.Max(1, Mathf.RoundToInt(baseSize * result / 100f)) + ">") : m.Value); } public static void PrepareInputRichText(InputField input, ref string text) { if (!((Object)(object)input == (Object)null) && !((Object)(object)input.textComponent == (Object)null)) { input.textComponent.supportRichText = true; text = AdaptLegacyRichText(input.textComponent, text); } } public static string RecallSourceText(object c, string current) { if (c == null || !_sourceByComponent.TryGetValue(c, out string value)) { return current; } return value; } public static void ApplyFont(object component) { if (component == null) { return; } TMP_Text val = (TMP_Text)((component is TMP_Text) ? component : null); string text; if (val == null) { Text val2 = (Text)((component is Text) ? component : null); text = ((val2 == null) ? null : val2.text); } else { text = val.text; } string text2 = text; if (text2 == null) { return; } string font = WkLocPlugin.Store.GetFont(RecallSourceText(component, text2)); TMP_Text val3 = (TMP_Text)((component is TMP_Text) ? component : null); if (val3 != null) { FontProvider.TryApply(val3, FontProvider.GetTmp(font)); return; } Text val4 = (Text)((component is Text) ? component : null); if (val4 != null) { FontProvider.TryApply(val4, FontProvider.GetLegacy(font)); } } public static void ApplyAll(object component) { if (component == null) { return; } Component val = (Component)((component is Component) ? component : null); if (val != null && TextGuards.IsCommandConsole(val)) { return; } TMP_Text val2 = (TMP_Text)((component is TMP_Text) ? component : null); if (val2 == null) { Text val3 = (Text)((component is Text) ? component : null); if (val3 == null) { InputField val4 = (InputField)((component is InputField) ? component : null); if (val4 == null) { TMP_InputField val5 = (TMP_InputField)((component is TMP_InputField) ? component : null); if (val5 != null) { TranslateInput(val5); } } else { TranslateInput(val4); } } else { if (Enabled) { ApplyFont(val3); } ResizeOnce(val3); if (!TextGuards.HasScrawl((Component)(object)val3) && !TextGuards.IsInputChild((Graphic)(object)val3)) { TranslateInPlace(val3); } } } else { ResizeOnce(val2); bool flag = TextGuards.HasScrawl((Component)(object)val2); if (Enabled && !flag) { ApplyFont(val2); } if (!flag && !TextGuards.IsTAnim((Component)(object)val2) && !TextGuards.IsInputChild((Graphic)(object)val2)) { TranslateInPlace(val2); } } } public static void ApplyAllInChildren(Component root, bool includeInactive = true) { if ((Object)(object)root == (Object)null) { return; } try { TMP_Text[] componentsInChildren = root.GetComponentsInChildren<TMP_Text>(includeInactive); for (int i = 0; i < componentsInChildren.Length; i++) { ApplyAll(componentsInChildren[i]); } Text[] componentsInChildren2 = root.GetComponentsInChildren<Text>(includeInactive); for (int i = 0; i < componentsInChildren2.Length; i++) { ApplyAll(componentsInChildren2[i]); } InputField[] componentsInChildren3 = root.GetComponentsInChildren<InputField>(includeInactive); for (int i = 0; i < componentsInChildren3.Length; i++) { ApplyAll(componentsInChildren3[i]); } TMP_InputField[] componentsInChildren4 = root.GetComponentsInChildren<TMP_InputField>(includeInactive); for (int i = 0; i < componentsInChildren4.Length; i++) { ApplyAll(componentsInChildren4[i]); } } catch { } } private static void TranslateInPlace(object component) { TMP_Text val = (TMP_Text)((component is TMP_Text) ? component : null); string text; if (val == null) { Text val2 = (Text)((component is Text) ? component : null); text = ((val2 == null) ? null : val2.text); } else { text = val.text; } string text2 = text; if (text2 == null) { return; } string text3 = text2; Transform(component, ref text3); if (text3 != text2) { SuppressSetterHooks = true; try { TMP_Text val3 = (TMP_Text)((component is TMP_Text) ? component : null); if (val3 != null) { val3.text = text3; } else { Text val4 = (Text)((component is Text) ? component : null); if (val4 != null) { val4.text = text3; } } } finally { SuppressSetterHooks = false; } } PostTransform(component); } private static void TranslateInput(object component) { InputField val = (InputField)((component is InputField) ? component : null); string text; if (val == null) { TMP_InputField val2 = (TMP_InputField)((component is TMP_InputField) ? component : null); text = ((val2 == null) ? null : val2.text); } else { text = val.text; } string text2 = text; if (text2 == null) { return; } string text3 = text2; Transform(component, ref text3); if (text3 != text2) { SuppressSetterHooks = true; try { InputField val3 = (InputField)((component is InputField) ? component : null); if (val3 != null) { val3.text = text3; } else { TMP_InputField val4 = (TMP_InputField)((component is TMP_InputField) ? component : null); if (val4 != null) { val4.text = text3; } } } finally { SuppressSetterHooks = false; } } PostInputChanged(component); } private static string Trunc(string? s) { if (!string.IsNullOrEmpty(s)) { if (s.Length <= 40) { return s; } return s.Substring(0, 40) + "..."; } return "<empty>"; } private static string CharCodes(string s) { StringBuilder stringBuilder = new StringBuilder(); foreach (char value in s) { stringBuilder.Append((int)value); stringBuilder.Append(' '); } return stringBuilder.ToString().TrimEnd(); } public static void RememberSourceText(object component, string original) { if (component != null && !string.IsNullOrEmpty(original)) { _sourceByComponent.Remove(component); _sourceByComponent.Add(component, original); CacheTextDirectives(component, original); } } public static void Transform(object component, ref string text) { if (SuppressSetterHooks || component == null || string.IsNullOrWhiteSpace(text)) { return; } Component val = (Component)((component is Component) ? component : null); if ((val != null && TextGuards.IsCommandConsole(val)) || IsAssigned(component, text) || !Enabled) { return; } string text2 = text; string text3 = WkLocPlugin.Store.Resolve(text2); string value; if (text3 != null && text3 != text) { _sourceByComponent.Remove(component); _sourceByComponent.Add(component, text2); CacheTextDirectives(component, text2); } else if (!_sourceByComponent.TryGetValue(component, out value)) { _sourceByComponent.Add(component, text2); CacheTextDirectives(component, text2); } if (text3 == null || text3 == text) { SetPendingTextDirectives(component, text2); Text val2 = (Text)((component is Text) ? component : null); if (val2 != null) { string text4 = AdaptLegacyRichText(val2, text); if (!val2.supportRichText || text4 != text) { val2.supportRichText = true; text = text4; } } else { InputField val3 = (InputField)((component is InputField) ? component : null); if (val3 != null) { PrepareInputRichText(val3, ref text); } } Text val4 = (Text)((component is Text) ? component : null); if (val4 != null && WkLocPlugin.Cfg.DebugLog.Value) { Debug.Log((object)("[WKLoc] Process Text NO-MATCH '" + ((Object)((Component)val4).gameObject).name + "' orig='" + Trunc(text2) + "'")); } TMP_Text val5 = (TMP_Text)((component is TMP_Text) ? component : null); if (val5 != null && WkLocPlugin.Cfg.DebugLog.Value && _hasLetters.IsMatch(text2)) { Debug.Log((object)string.Format("[WKLoc] Process TMP NO-MATCH '{0}' orig=[{1}] len={2} codes={3}", ((Object)((Component)val5).gameObject).name, text2, text2.Length, (text2.Length <= 40) ? CharCodes(text2) : "(long)")); } if (_hasLetters.IsMatch(text2)) { TextCollector.Capture(text2); } SetAssigned(component, text); return; } if (WkLocPlugin.Cfg.DebugLog.Value) { string text5 = ((component is TMP_Text) ? "TMP" : "Text"); TMP_Text val6 = (TMP_Text)((component is TMP_Text) ? component : null); if (val6 == null) { Text val7 = (Text)((component is Text) ? component : null); value = ((val7 == null) ? "?" : ((Object)((Component)val7).gameObject).name); } else { value = ((Object)((Component)val6).gameObject).name; } string text6 = value; Debug.Log((object)("[WKLoc] Process " + text5 + " HIT '" + text6 + "' orig='" + Trunc(text2) + "' -> '" + Trunc(text3) + "'")); } text = string.Concat(text3); Text val8 = (Text)((component is Text) ? component : null); if (val8 != null) { val8.supportRichText = true; text = AdaptLegacyRichText(val8, text); } else { InputField val9 = (InputField)((component is InputField) ? component : null); if (val9 != null) { PrepareInputRichText(val9, ref text); } } SetAssigned(component, text); string font = WkLocPlugin.Store.GetFont(text2); TMP_Text val10 = (TMP_Text)((component is TMP_Text) ? component : null); if (val10 != null) { FontProvider.TryApply(val10, FontProvider.GetTmp(font)); } else { Text val11 = (Text)((component is Text) ? component : null); if (val11 != null) { FontProvider.TryApply(val11, FontProvider.GetLegacy(font)); } } ResizeOnce(component); SetPendingTextDirectives(component, text2); } public static void TransformDetached(ref string text) { if (SuppressSetterHooks || string.IsNullOrWhiteSpace(text) || !Enabled) { return; } string text2 = text; string text3 = WkLocPlugin.Store.Resolve(text2); if (text3 == null || text3 == text) { if (_hasLetters.IsMatch(text2)) { TextCollector.Capture(text2); } } else { text = string.Concat(text3); } } public static void Reapply(object component, bool applyFont = true) { if (component == null) { return; } Component val = (Component)((component is Component) ? component : null); if (val != null && TextGuards.IsCommandConsole(val)) { return; } TMP_Text val2 = (TMP_Text)((component is TMP_Text) ? component : null); string text; if (val2 == null) { Text val3 = (Text)((component is Text) ? component : null); if (val3 == null) { InputField val4 = (InputField)((component is InputField) ? component : null); if (val4 == null) { TMP_InputField val5 = (TMP_InputField)((component is TMP_InputField) ? component : null); text = ((val5 == null) ? null : val5.text); } else { text = val4.text; } } else { text = val3.text; } } else { text = val2.text; } string text2 = text; if (text2 == null) { return; } string text3 = RecallSourceText(component, text2); string text4 = (Enabled ? (WkLocPlugin.Store.Resolve(text3) ?? text3) : text3); Text val6 = (Text)((component is Text) ? component : null); if (val6 != null) { val6.supportRichText = true; text4 = AdaptLegacyRichText(val6, text4); } else { InputField val7 = (InputField)((component is InputField) ? component : null); if (val7 != null) { PrepareInputRichText(val7, ref text4); } } if (WkLocPlugin.Cfg.DebugLog.Value) { Component val8 = (Component)((component is Component) ? component : null); if (val8 != null && (component is Text || component is InputField || component is TMP_InputField)) { bool flag = ((component is InputField || component is TMP_InputField) ? true : false); Debug.Log((object)string.Format("[WKLoc] Reapply {0} '{1}' enabled={2} change={3} cur='{4}' orig='{5}' tgt='{6}'", flag ? "Input" : "Text", ((Object)val8.gameObject).name, Enabled, text2 != text4, Trunc(text2), Trunc(text3), Trunc(text4))); } } if (text2 != text4) { SuppressSetterHooks = true; try { TMP_Text val9 = (TMP_Text)((component is TMP_Text) ? component : null); if (val9 != null) { val9.text = text4; } else { Text val10 = (Text)((component is Text) ? component : null); if (val10 != null) { val10.text = text4; } else { InputField val11 = (InputField)((component is InputField) ? component : null); if (val11 != null) { val11.text = text4; } else { TMP_InputField val12 = (TMP_InputField)((component is TMP_InputField) ? component : null); if (val12 != null) { val12.text = text4; } } } } } finally { SuppressSetterHooks = false; } } SetAssigned(component, text4); CacheTextDirectives(component, text3); if (!Enabled) { return; } if ((component is TMP_Text || component is Text) ? true : false) { ResizeOnce(component); } else { InputField val13 = (InputField)((component is InputField) ? component : null); if (val13 != null && (Object)(object)val13.textComponent != (Object)null) { ResizeOnce(val13.textComponent); } else { TMP_InputField val14 = (TMP_InputField)((component is TMP_InputField) ? component : null); if (val14 != null && (Object)(object)val14.textComponent != (Object)null) { ResizeOnce(val14.textComponent); } } } string font = WkLocPlugin.Store.GetFont(text3); Text val15 = (Text)((component is Text) ? component : null); if (val15 != null) { FontProvider.TryApply(val15, FontProvider.GetLegacy(font)); } else if (applyFont) { TMP_Text val16 = (TMP_Text)((component is TMP_Text) ? component : null); if (val16 != null) { FontProvider.TryApply(val16, FontProvider.GetTmp(font)); } } InputField val17 = (InputField)((component is InputField) ? component : null); if (val17 != null) { OsFileBackplateRefresher.Queue(((Component)val17).GetComponentInParent<OS_File>()); } else { TMP_InputField val18 = (TMP_InputField)((component is TMP_InputField) ? component : null); if (val18 != null) { OsFileBackplateRefresher.Queue(((Component)val18).GetComponentInParent<OS_File>()); } } if (applyFont) { SetPendingTextDirectives(component, text3); PostTransform(component); } } public static void ResizeOnce(object component) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected I4, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Expected I4, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected I4, but got Unknown //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected I4, but got Unknown if (!Enabled || component == null || (!(component is TMP_Text) && !(component is Text))) { return; } ResizeComponentState orCreateValue = _resized.GetOrCreateValue(component); if (!orCreateValue.Scale.NeedsApply(_resizeGeneration)) { return; } bool suppressSetterHooks = SuppressSetterHooks; SuppressSetterHooks = true; try { TMP_Text val = (TMP_Text)((component is TMP_Text) ? component : null); if (val != null) { int num = (int)val.alignment; if (!orCreateValue.AlignmentInitialized || num != orCreateValue.LastAppliedAlignment) { orCreateValue.BaseAlignment = num; } val.alignment = (TextAlignmentOptions)orCreateValue.BaseAlignment; float baseSize = orCreateValue.Scale.BeginApply(_resizeGeneration, val.fontSize); UiResizer.Apply(val, baseSize); orCreateValue.Scale.CompleteApply(val.fontSize); orCreateValue.LastAppliedAlignment = (int)val.alignment; orCreateValue.AlignmentInitialized = true; return; } Text val2 = (Text)((component is Text) ? component : null); if (val2 != null) { int num2 = (int)val2.alignment; if (!orCreateValue.AlignmentInitialized || num2 != orCreateValue.LastAppliedAlignment) { orCreateValue.BaseAlignment = num2; } val2.alignment = (TextAnchor)orCreateValue.BaseAlignment; float baseSize2 = orCreateValue.Scale.BeginApply(_resizeGeneration, (float)val2.fontSize); UiResizer.Apply(val2, baseSize2); orCreateValue