Decompiled source of envyRU v2.1.2

plugins/envyRU/RoundsRussianLocalization.dll

Decompiled 3 days ago
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<string, string> translations = new Dictionary<string, string>(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<string, string> 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<string> lines)
	{
		TranslationDictionary translationDictionary = new TranslationDictionary();
		foreach (string item in lines ?? Enumerable.Empty<string>())
		{
			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+(?:[^}]*)?\\}|</?[A-Za-z][^>]*>|\\\\n", RegexOptions.Compiled | RegexOptions.CultureInvariant);

	public static bool HasSameTokens(string source, string target)
	{
		return Tokens(source).SequenceEqual<string>(Tokens(target), StringComparer.Ordinal);
	}

	private static IEnumerable<string> Tokens(string value)
	{
		return (from Match match in TokenPattern.Matches(value ?? string.Empty)
			select NormalizeRichTextToken(match.Value)).OrderBy<string, string>((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("</?size(?:=[^>]+)?>", 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<TMP_FontAsset>("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<TMP_Text>();
		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<string> list = new List<string>();
			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<TMP_Text>(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);
	}
}