using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Logging; using HarmonyLib; using TMPro; using UnityEngine; using UnityEngine.SceneManagement; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace RoundsRussianLocalization; public static class EmbeddedByteReader { public static byte[] ReadAllAndDispose(Stream stream) { if (stream == null) { throw new ArgumentNullException("stream"); } using (stream) { using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); return memoryStream.ToArray(); } } } public sealed class TranslationDictionary { private readonly Dictionary translations = new Dictionary(StringComparer.Ordinal); public int Count { [CompilerGenerated] get { return translations.Count; } } public void Add(string source, string target) { if (!string.IsNullOrEmpty(source) && !string.IsNullOrEmpty(target) && TokenValidator.HasSameTokens(source, target)) { translations[source] = target; } } public void Merge(TranslationDictionary other) { if (other == null) { return; } foreach (KeyValuePair translation in other.translations) { translations[translation.Key] = translation.Value; } } public string Translate(string source) { if (string.IsNullOrEmpty(source)) { return source; } string value; return (!translations.TryGetValue(source, out value)) ? source : value; } public string TranslateTemplate(string source) { return Translate(source); } public static TranslationDictionary Parse(IEnumerable lines) { TranslationDictionary translationDictionary = new TranslationDictionary(); foreach (string item in lines ?? Enumerable.Empty()) { string text = item.Trim(); if (text.Length != 0 && !text.StartsWith("#", StringComparison.Ordinal)) { int num = FindUnescapedEquals(text); if (num > 0 && num != text.Length - 1) { translationDictionary.Add(Unescape(text.Substring(0, num)), Unescape(text.Substring(num + 1))); } } } return translationDictionary; } private static int FindUnescapedEquals(string value) { bool flag = false; for (int i = 0; i < value.Length; i++) { if (flag) { flag = false; } else if (value[i] == '\\') { flag = true; } else if (value[i] == '=') { return i; } } return -1; } private static string Unescape(string value) { StringBuilder stringBuilder = new StringBuilder(value.Length); bool flag = false; foreach (char c in value) { if (flag) { stringBuilder.Append((c != 'n') ? c : '\n'); flag = false; } else if (c == '\\') { flag = true; } else { stringBuilder.Append(c); } } if (flag) { stringBuilder.Append('\\'); } return stringBuilder.ToString(); } } public static class TokenValidator { private static readonly Regex TokenPattern = new Regex("\\{\\d+(?:[^}]*)?\\}|]*>|\\\\n", RegexOptions.Compiled | RegexOptions.CultureInvariant); public static bool HasSameTokens(string source, string target) { return Tokens(source).SequenceEqual(Tokens(target), StringComparer.Ordinal); } private static IEnumerable Tokens(string value) { return (from Match match in TokenPattern.Matches(value ?? string.Empty) select NormalizeRichTextToken(match.Value)).OrderBy((string token) => token, StringComparer.Ordinal); } private static string NormalizeRichTextToken(string token) { if (!token.StartsWith("<", StringComparison.Ordinal)) { return token; } Match match = Regex.Match(token, "^<\\s*(/?)\\s*([A-Za-z]+)"); return (!match.Success) ? token : ("<" + match.Groups[1].Value + match.Groups[2].Value.ToLowerInvariant() + ">"); } } public static class CardTextSanitizer { private static readonly Regex AbsoluteSizeTag = new Regex("]+)?>", RegexOptions.IgnoreCase | RegexOptions.Compiled); public static string Normalize(string value) { return (!string.IsNullOrEmpty(value)) ? AbsoluteSizeTag.Replace(value, string.Empty) : value; } } public enum TextRole { Title, Description, Stat, Other } public enum OverflowPolicy { Truncate } public struct TextLayout { public bool AutoSize; public bool WordWrapping; public float MinFontSize; public float MaxFontSize; public int MaxVisibleLines; public OverflowPolicy Overflow; } public struct CardRegion { public float Bottom; public float Top; public OverflowPolicy Overflow; } public static class CardLayoutPlanner { public static CardRegion Plan(TextRole role, float cardHeight) { float num = Math.Max(1f, cardHeight); return role switch { TextRole.Title => new CardRegion { Bottom = num * 0.8f, Top = num * 0.98f, Overflow = OverflowPolicy.Truncate }, TextRole.Description => new CardRegion { Bottom = num * 0.36f, Top = num * 0.77f, Overflow = OverflowPolicy.Truncate }, TextRole.Stat => new CardRegion { Bottom = num * 0.03f, Top = num * 0.33f, Overflow = OverflowPolicy.Truncate }, _ => new CardRegion { Bottom = num * 0.03f, Top = num * 0.98f, Overflow = OverflowPolicy.Truncate }, }; } } public static class TextLayoutPolicy { public static TextLayout For(TextRole role, int textLength, float prefabFontSize) { float num = role switch { TextRole.Title => 34f, TextRole.Description => 22f, _ => 20f, }; float num2 = ((!(prefabFontSize > 0f)) ? num : prefabFontSize); float num3; int maxVisibleLines; switch (role) { case TextRole.Title: num3 = ((textLength > 30) ? 0.55f : ((textLength <= 20) ? 0.7f : 0.62f)); maxVisibleLines = 3; break; case TextRole.Description: num3 = ((textLength > 140) ? 0.5f : ((textLength <= 100) ? 0.62f : 0.55f)); maxVisibleLines = 7; break; case TextRole.Stat: num3 = ((textLength <= 35) ? 0.65f : 0.55f); maxVisibleLines = 2; break; default: num3 = 0.6f; maxVisibleLines = 5; break; } return new TextLayout { AutoSize = true, WordWrapping = true, MinFontSize = Math.Max(1f, num2 * num3), MaxFontSize = num2, MaxVisibleLines = maxVisibleLines, Overflow = OverflowPolicy.Truncate }; } } internal static class EmbeddedResources { private const string TranslationsName = "RoundsRussianLocalization.Resources.ru.txt"; private const string FontBundleName = "RoundsRussianLocalization.Resources.arialuni_sdf_u2018"; private const string FontAssetName = "ARIALUNI SDF"; internal static Stream OpenTranslations() { return Open("RoundsRussianLocalization.Resources.ru.txt"); } internal static AssetBundle LoadFontBundle() { byte[] array = EmbeddedByteReader.ReadAllAndDispose(Open("RoundsRussianLocalization.Resources.arialuni_sdf_u2018")); return AssetBundle.LoadFromMemory(array); } internal static TMP_FontAsset LoadFont(AssetBundle bundle) { return (!((Object)(object)bundle == (Object)null)) ? bundle.LoadAsset("ARIALUNI SDF") : null; } private static Stream Open(string name) { Stream manifestResourceStream = typeof(EmbeddedResources).Assembly.GetManifestResourceStream(name); if (manifestResourceStream == null) { throw new InvalidOperationException("Missing embedded resource: " + name); } return manifestResourceStream; } } [BepInPlugin("thehell.rounds.russianlocalization", "ROUNDS Russian Localization", "2.1.2")] public sealed class LocalizationPlugin : BaseUnityPlugin { public const string PluginId = "thehell.rounds.russianlocalization"; public const string PluginName = "ROUNDS Russian Localization"; public const string PluginVersion = "2.1.2"; private Harmony harmony; private AssetBundle fontBundle; private float nextUiScan; internal static TranslationDictionary Dictionary { get; private set; } internal static TMP_FontAsset RussianFont { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Dictionary = LoadTranslations(); fontBundle = EmbeddedResources.LoadFontBundle(); RussianFont = EmbeddedResources.LoadFont(fontBundle); if ((Object)(object)RussianFont == (Object)null) { throw new InvalidOperationException("Embedded Cyrillic TMP font was not found."); } harmony = new Harmony("thehell.rounds.russianlocalization"); harmony.PatchAll(typeof(LocalizationPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)$"Loaded {Dictionary.Count} embedded Russian translations and Cyrillic font."); } private void OnDestroy() { if (harmony != null) { harmony.UnpatchSelf(); } if ((Object)(object)fontBundle != (Object)null) { fontBundle.Unload(false); } } private void Update() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (Time.unscaledTime < nextUiScan) { return; } nextUiScan = Time.unscaledTime + 0.25f; TMP_Text[] array = Resources.FindObjectsOfTypeAll(); foreach (TMP_Text val in array) { if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null) { continue; } Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).IsValid()) { string text = val.text; string text2 = Dictionary.TranslateTemplate(text); if (!(text2 == text)) { val.font = RussianFont; val.text = text2; } } } } private static TranslationDictionary LoadTranslations() { return ParseResource(EmbeddedResources.OpenTranslations()); } private static TranslationDictionary ParseResource(Stream stream) { using (stream) { using StreamReader streamReader = new StreamReader(stream); List list = new List(); string item; while ((item = streamReader.ReadLine()) != null) { list.Add(item); } return TranslationDictionary.Parse(list); } } } [HarmonyPatch(typeof(CardInfoDisplayer), "DrawCard")] internal static class DrawCardPatch { private static bool firstSuccessfulDrawLogged; private static void Prefix(ref CardInfoStat[] stats, ref string cardName, ref string description) { TranslationDictionary dictionary = LocalizationPlugin.Dictionary; if (dictionary == null) { return; } cardName = CardTextSanitizer.Normalize(dictionary.Translate(cardName)); description = CardTextSanitizer.Normalize(dictionary.Translate(description)); if (stats == null) { return; } CardInfoStat[] array = stats; foreach (CardInfoStat val in array) { if (val != null) { val.amount = CardTextSanitizer.Normalize(dictionary.Translate(val.amount)); val.stat = CardTextSanitizer.Normalize(dictionary.Translate(val.stat)); } } } private static void Postfix(CardInfoDisplayer __instance) { CardTextFitter.Fit(__instance); if (!firstSuccessfulDrawLogged) { firstSuccessfulDrawLogged = true; ManualLogSource log = LocalizationPlugin.Log; if (log != null) { log.LogInfo((object)"Localized and fitted the first rendered card successfully."); } } } } internal static class CardTextFitter { public static void Fit(CardInfoDisplayer displayer) { if ((Object)(object)displayer == (Object)null) { return; } if ((Object)(object)displayer.nameText != (Object)null) { Apply((TMP_Text)(object)displayer.nameText, TextRole.Title); } if ((Object)(object)displayer.effectText != (Object)null) { Apply((TMP_Text)(object)displayer.effectText, TextRole.Description); } if ((Object)(object)displayer.grid == (Object)null) { return; } TMP_Text[] componentsInChildren = displayer.grid.GetComponentsInChildren(true); foreach (TMP_Text val in componentsInChildren) { if ((Object)(object)val != (Object)null) { Apply(val, TextRole.Stat); } } } private static void Apply(TMP_Text text, TextRole role) { //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00af: 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_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)LocalizationPlugin.RussianFont != (Object)null) { text.font = LocalizationPlugin.RussianFont; } TextLayout textLayout = TextLayoutPolicy.For(role, (text.text ?? string.Empty).Length, text.fontSize); text.enableAutoSizing = textLayout.AutoSize; text.enableWordWrapping = textLayout.WordWrapping; text.fontSizeMin = textLayout.MinFontSize; text.fontSizeMax = textLayout.MaxFontSize; text.maxVisibleLines = textLayout.MaxVisibleLines; text.overflowMode = (TextOverflowModes)1; text.enableKerning = true; text.extraPadding = true; text.margin = new Vector4(Math.Max(text.margin.x, 2f), Math.Max(text.margin.y, 2f), Math.Max(text.margin.z, 2f), Math.Max(text.margin.w, 2f)); text.ForceMeshUpdate(true); } }