Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Sweet Russian Translate v0.2.3
SweetRussianTranslate.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Localization.Settings; using UnityEngine.Localization.Tables; using UnityEngine.TextCore; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 SweetRussianTranslate { internal static class ChatPhrases { private static readonly List<string> Empty = new List<string>(); private static Dictionary<string, string> replacements; private static Dictionary<string, List<string>> lists; internal static Dictionary<string, string> Replacements { get { Load(); return replacements; } } internal static List<string> List(string key) { Load(); if (lists.TryGetValue(key, out var value)) { return value; } return Empty; } internal static string Pick(string key) { List<string> list = List(key); if (list.Count == 0) { return string.Empty; } return list[Random.Range(0, list.Count)]; } private static void Load() { if (replacements != null) { return; } replacements = new Dictionary<string, string>(); lists = new Dictionary<string, List<string>>(); foreach (KeyValuePair<string, string> item in Tsv.Read("Chat.tsv")) { if (item.Key.StartsWith("[") && item.Key.EndsWith("]")) { if (!lists.TryGetValue(item.Key, out var value)) { value = new List<string>(); lists[item.Key] = value; } value.Add(item.Value); } else { replacements[item.Key] = item.Value; } } int num = 0; foreach (KeyValuePair<string, List<string>> list in lists) { num += list.Value.Count; } Plugin.Log.LogInfo((object)("Loaded " + replacements.Count + " chat phrases and " + num + " listed lines in " + lists.Count + " lists.")); } } [HarmonyPatch(typeof(ChatManager), "PossessChat")] internal static class ChatPossessPatch { [HarmonyPrefix] private static void Prefix(ref string message) { if (message != null && ChatPhrases.Replacements.TryGetValue(message, out var value)) { message = value; } } } [HarmonyPatch(typeof(SemiFunc), "MessageGeneratedGetLeftBehind")] internal static class LeftBehindMessagePatch { [HarmonyPostfix] private static void Postfix(ref string __result) { string text = ChatPhrases.Pick("[LEFT_BEHIND]"); if (text.Length > 0) { __result = text; } } } [HarmonyPatch(typeof(ValuableLovePotion), "GenerateAffectionateSentence")] internal static class LovePotionPatch { private const string NoPlayerNearby = "this potion"; [HarmonyPostfix] private static void Postfix(ref string __result, string ___playerName) { string text = ChatPhrases.Pick("[LOVE_TEMPLATE]"); if (text.Length != 0) { string newValue = ((___playerName == "this potion") ? "это зелье" : ___playerName); string text2 = text.Replace("{playerName}", newValue).Replace("{adjective}", ChatPhrases.Pick("[LOVE_ADJECTIVE]")).Replace("{intensifier}", ChatPhrases.Pick("[LOVE_INTENSIFIER]")) .Replace("{adverb}", ChatPhrases.Pick("[LOVE_ADVERB]")) .Replace("{noun}", ChatPhrases.Pick("[LOVE_NOUN]")) .Replace("{transitiveVerb}", ChatPhrases.Pick("[LOVE_TRANSITIVE_VERB]")) .Replace("{intransitiveVerb}", ChatPhrases.Pick("[LOVE_INTRANSITIVE_VERB]")); __result = char.ToUpper(text2[0]) + text2.Substring(1); } } } internal static class HardcodedLabels { private static Dictionary<string, string> labels; internal static Dictionary<string, string> All { get { if (labels == null) { labels = new Dictionary<string, string>(); foreach (KeyValuePair<string, string> item in Tsv.Read("Hardcoded.tsv")) { labels[item.Key] = item.Value; } Plugin.Log.LogInfo((object)("Loaded " + labels.Count + " hardcoded label replacements.")); } return labels; } } internal static bool Translate(ref string text) { if (text != null && All.TryGetValue(text, out var value)) { text = value; return true; } return false; } } [HarmonyPatch(typeof(SemiUI), "Start")] internal static class HardcodedLabelPatch { [HarmonyPostfix] private static void Postfix(SemiUI __instance) { if (HardcodedLabels.All.Count == 0) { return; } TMP_Text[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<TMP_Text>(true); foreach (TMP_Text val in componentsInChildren) { string text = val.text; if (HardcodedLabels.Translate(ref text)) { val.text = text; } } } } [HarmonyPatch(typeof(BigMessageUI), "BigMessage")] internal static class BigMessagePatch { [HarmonyPrefix] private static void Prefix(ref string message) { HardcodedLabels.Translate(ref message); } } [HarmonyPatch(typeof(ItemTracker), "DisplayColorOverride")] internal static class ItemTrackerDisplayPatch { [HarmonyPrefix] private static void Prefix(ref string _text) { HardcodedLabels.Translate(ref _text); } } [HarmonyPatch(typeof(MenuBigButton), "StateEdit")] internal static class MenuBigButtonEditPatch { [HarmonyPostfix] private static void Postfix(MenuBigButton __instance) { TMP_Text value = Traverse.Create((object)__instance.menuButton).Field("buttonText").GetValue<TMP_Text>(); if (!((Object)(object)value == (Object)null)) { string text = value.text; if (HardcodedLabels.Translate(ref text)) { value.text = text; } } } } [HarmonyPatch(typeof(LocalizationManager), "Awake")] internal static class LocalizationOverridePatch { private static readonly string[] TableNames = new string[3] { "HUD", "Menu", "Game" }; private static bool fontLoaded; [HarmonyPostfix] private static void Postfix() { LoadCyrillicFallbackFont(); string[] tableNames = TableNames; foreach (string text in tableNames) { try { ApplyTable(text); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to apply " + text + ".tsv: " + ex.Message)); } } } private static void ApplyTable(string tableName) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) List<KeyValuePair<string, string>> list = Tsv.Read(tableName + ".tsv"); if (list.Count == 0) { return; } StringTable table = ((LocalizedDatabase<StringTable, StringTableEntry>)(object)LocalizationSettings.StringDatabase).GetTable(TableReference.op_Implicit(tableName), LocalizationSettings.ProjectLocale); if ((Object)(object)table == (Object)null) { Plugin.Log.LogError((object)("String table not found: " + tableName)); return; } foreach (KeyValuePair<string, string> item in list) { StringTableEntry entry = ((DetailedLocalizationTable<StringTableEntry>)(object)table).GetEntry(item.Key); if (entry != null) { entry.Value = item.Value; } else { ((DetailedLocalizationTable<StringTableEntry>)(object)table).AddEntry(item.Key, item.Value); } } Plugin.Log.LogInfo((object)("Applied " + list.Count + " strings to table " + tableName + ".")); } private static void LoadCyrillicFallbackFont() { //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown if (fontLoaded) { return; } fontLoaded = true; string text = Path.Combine(Plugin.ModFolder, "Teko-Cyrillic.ttf"); if (!File.Exists(text)) { Plugin.Log.LogError((object)("Font not found: " + text)); return; } try { TMP_FontAsset val = FindGameTekoAsset(); TMP_FontAsset val3; if ((Object)(object)val != (Object)null) { Font val2 = new Font(text); FaceInfo faceInfo = val.faceInfo; val3 = TMP_FontAsset.CreateFontAsset(val2, ((FaceInfo)(ref faceInfo)).pointSize, val.atlasPadding, val.atlasRenderMode, val.atlasWidth, val.atlasHeight, (AtlasPopulationMode)1, true); } else { val3 = TMP_FontAsset.CreateFontAsset(new Font(text)); } ((Object)val3).name = "Teko-Cyrillic SDF"; ((Object)val3).hideFlags = (HideFlags)32; if ((Object)(object)val != (Object)null) { CopyVerticalMetrics(val, val3); } TMP_Settings.fallbackFontAssets?.Insert(0, val3); TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>(); foreach (TMP_FontAsset val4 in array) { if ((Object)(object)val4 != (Object)(object)val3 && ((Object)val4).name.Contains("Teko")) { if (val4.fallbackFontAssetTable == null) { val4.fallbackFontAssetTable = new List<TMP_FontAsset>(); } val4.fallbackFontAssetTable.Insert(0, val3); } } Plugin.Log.LogInfo((object)("Cyrillic fallback font registered" + (((Object)(object)val != (Object)null) ? (" (matched to " + ((Object)val).name + ").") : " (no Teko reference found)."))); } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to load Cyrillic font: " + ex)); } } private static void CopyVerticalMetrics(TMP_FontAsset from, TMP_FontAsset to) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) FaceInfo faceInfo = from.faceInfo; FaceInfo faceInfo2 = to.faceInfo; ((FaceInfo)(ref faceInfo2)).scale = ((FaceInfo)(ref faceInfo)).scale; ((FaceInfo)(ref faceInfo2)).lineHeight = ((FaceInfo)(ref faceInfo)).lineHeight; ((FaceInfo)(ref faceInfo2)).ascentLine = ((FaceInfo)(ref faceInfo)).ascentLine; ((FaceInfo)(ref faceInfo2)).capLine = ((FaceInfo)(ref faceInfo)).capLine; ((FaceInfo)(ref faceInfo2)).meanLine = ((FaceInfo)(ref faceInfo)).meanLine; ((FaceInfo)(ref faceInfo2)).baseline = ((FaceInfo)(ref faceInfo)).baseline; ((FaceInfo)(ref faceInfo2)).descentLine = ((FaceInfo)(ref faceInfo)).descentLine; ((FaceInfo)(ref faceInfo2)).superscriptOffset = ((FaceInfo)(ref faceInfo)).superscriptOffset; ((FaceInfo)(ref faceInfo2)).superscriptSize = ((FaceInfo)(ref faceInfo)).superscriptSize; ((FaceInfo)(ref faceInfo2)).subscriptOffset = ((FaceInfo)(ref faceInfo)).subscriptOffset; ((FaceInfo)(ref faceInfo2)).subscriptSize = ((FaceInfo)(ref faceInfo)).subscriptSize; ((FaceInfo)(ref faceInfo2)).underlineOffset = ((FaceInfo)(ref faceInfo)).underlineOffset; ((FaceInfo)(ref faceInfo2)).underlineThickness = ((FaceInfo)(ref faceInfo)).underlineThickness; ((FaceInfo)(ref faceInfo2)).strikethroughOffset = ((FaceInfo)(ref faceInfo)).strikethroughOffset; ((FaceInfo)(ref faceInfo2)).strikethroughThickness = ((FaceInfo)(ref faceInfo)).strikethroughThickness; ((FaceInfo)(ref faceInfo2)).tabWidth = ((FaceInfo)(ref faceInfo)).tabWidth; to.faceInfo = faceInfo2; Plugin.Log.LogInfo((object)$"Metrics aligned to {((Object)from).name}: pointSize {((FaceInfo)(ref faceInfo)).pointSize}, lineHeight {((FaceInfo)(ref faceInfo)).lineHeight}, baseline {((FaceInfo)(ref faceInfo)).baseline}, ascent {((FaceInfo)(ref faceInfo)).ascentLine}, descent {((FaceInfo)(ref faceInfo)).descentLine}."); } private static TMP_FontAsset FindGameTekoAsset() { TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>(); foreach (TMP_FontAsset val in array) { if (((Object)val).name.Contains("Teko")) { return val; } } return null; } } [BepInPlugin("sweet.russiantranslate", "Sweet Russian Translate", "0.2.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string Guid = "sweet.russiantranslate"; private const string EspeakGuid = "Lavighju.espeakTTS"; private const string EspeakDefaultLanguage = "en"; private const int EspeakDefaultSpeed = 175; private const int RussianSpeed = 100; internal static ManualLogSource Log; internal static string ModFolder; internal static Plugin Instance; private void Awake() { //IL_002b: 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) Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModFolder = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location); Harmony val = new Harmony("sweet.russiantranslate"); val.PatchAll(Assembly.GetExecutingAssembly()); int num = 0; foreach (MethodBase patchedMethod in val.GetPatchedMethods()) { _ = patchedMethod; num++; } Log.LogInfo((object)("Sweet Russian Translate loaded from " + ModFolder + ", patched methods: " + num)); TuneEspeak(); TuneTextures(); } private void TuneEspeak() { if (((BaseUnityPlugin)this).Config.Bind<bool>("Speech", "Tune espeakTTS for Russian speech", true, "Настроить espeakTTS под русский, чтобы реплики персонажа читались вслух внятно: язык en -> ru, скорость 175 -> 100 слов в минуту. Трогаются только значения по умолчанию: то, что ты выбрал сам, мод не перебивает. Выключи, если хочешь настраивать espeakTTS сам.").Value) { if (!Chainloader.PluginInfos.TryGetValue("Lavighju.espeakTTS", out var value) || (Object)(object)value.Instance == (Object)null) { Log.LogInfo((object)"espeakTTS is not installed, Russian speech will stay silent."); return; } ConfigFile config = value.Instance.Config; Retune(config, "Language", "en", "ru"); Retune(config, "Speed", 175, 100); } } private void TuneTextures() { if (!((BaseUnityPlugin)this).Config.Bind<bool>("Textures", "Translate textures", true, "Переводить надписи, нарисованные на текстурах: вывески комнат, плакаты, экраны. Выключи, если хочешь видеть их по-английски или подозреваешь их в тормозах. Меняется только при перезапуске игры.").Value) { Log.LogInfo((object)"Textures translation is off by config, signs stay English."); } else { TexturePatch.Init(ModFolder); } } private static void Retune<T>(ConfigFile config, string key, T untouched, T wanted) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown ConfigEntry<T> val = default(ConfigEntry<T>); if (!config.TryGetEntry<T>(new ConfigDefinition("General", key), ref val)) { Log.LogWarning((object)("espeakTTS has no General/" + key + " setting, leaving it alone.")); return; } if (!object.Equals(val.Value, untouched)) { Log.LogInfo((object)("espeakTTS " + key + " is already " + val.Value?.ToString() + ", leaving it alone.")); return; } val.Value = wanted; ManualLogSource log = Log; string[] obj = new string[7] { "espeakTTS ", key, " switched from ", null, null, null, null }; T val2 = untouched; obj[3] = val2?.ToString(); obj[4] = " to "; val2 = wanted; obj[5] = val2?.ToString(); obj[6] = "."; log.LogInfo((object)string.Concat(obj)); } } internal static class TexturePatch { private static readonly float[] ScanAt = new float[3] { 0f, 3f, 10f }; private static readonly Dictionary<string, string> Files = new Dictionary<string, string>(); private static readonly Dictionary<string, Texture2D> Loaded = new Dictionary<string, Texture2D>(); private static readonly Dictionary<Shader, string[]> Properties = new Dictionary<Shader, string[]>(); private static readonly HashSet<string> Done = new HashSet<string>(); private static int replaced; internal static void Init(string modFolder) { string[] files = Directory.GetFiles(modFolder, "resources_*.png", SearchOption.AllDirectories); if (files.Length == 0) { Plugin.Log.LogWarning((object)("Картинок resources_*.png нет, надписи на текстурах останутся английскими: " + modFolder)); return; } string[] array = files; foreach (string text in array) { string text2 = Key(Path.GetFileNameWithoutExtension(text)); if (text2.Length != 0) { if (Files.ContainsKey(text2)) { Plugin.Log.LogWarning((object)("Два файла на одно имя текстуры, беру первый: " + text)); } else { Files[text2] = text; } } } Plugin.Log.LogInfo((object)("Текстур на подмену: " + Files.Count)); } internal static IEnumerator AfterLevel() { if (Files.Count != 0) { float num = 0f; float[] scanAt = ScanAt; foreach (float at in scanAt) { yield return (object)new WaitForSeconds(at - num); num = at; Scan(); } } } private static void Scan() { Material[] array = Resources.FindObjectsOfTypeAll<Material>(); int num = replaced; Material[] array2 = array; foreach (Material val in array2) { if ((Object)(object)val == (Object)null) { continue; } Shader shader = val.shader; if ((Object)(object)shader == (Object)null) { continue; } string[] array3 = TextureProperties(shader); foreach (string text in array3) { Texture texture = val.GetTexture(text); if (!((Object)(object)texture == (Object)null)) { Texture2D val2 = Ours(texture); if (!((Object)(object)val2 == (Object)null)) { val.SetTexture(text, (Texture)(object)val2); replaced++; Done.Add(((Object)texture).name); Plugin.Log.LogInfo((object)("Текстура заменена: " + ((Object)texture).name + " (материал " + ((Object)val).name + ", " + text + ")")); } } } } if (replaced != num) { Plugin.Log.LogInfo((object)("Всего замен: " + replaced + ", разных картинок: " + Done.Count + " из " + Files.Count)); } } private static string[] TextureProperties(Shader shader) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 if (Properties.TryGetValue(shader, out var value)) { return value; } List<string> list = new List<string>(); int propertyCount = shader.GetPropertyCount(); for (int i = 0; i < propertyCount; i++) { if ((int)shader.GetPropertyType(i) == 4) { list.Add(shader.GetPropertyName(i)); } } string[] array = list.ToArray(); Properties[shader] = array; return array; } private static Texture2D Ours(Texture original) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) string text = Key(((Object)original).name); if (text.Length == 0 || !Files.TryGetValue(text, out var value)) { return null; } if (Loaded.TryGetValue(text, out var value2)) { return value2; } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true, false); if (!ImageConversion.LoadImage(val, File.ReadAllBytes(value), true)) { Plugin.Log.LogWarning((object)("PNG не читается, пропускаю: " + value)); Object.Destroy((Object)(object)val); Loaded[text] = null; return null; } ((Object)val).name = ((Object)original).name + " RU"; ((Texture)val).wrapMode = original.wrapMode; ((Texture)val).filterMode = original.filterMode; ((Texture)val).anisoLevel = original.anisoLevel; ((Object)val).hideFlags = (HideFlags)32; Loaded[text] = val; return val; } private static string Key(string name) { if (string.IsNullOrEmpty(name)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(name.Length); foreach (char c in name) { if (char.IsLetterOrDigit(c)) { stringBuilder.Append(char.ToLowerInvariant(c)); } } string text = stringBuilder.ToString(); if (!text.StartsWith("resources", StringComparison.Ordinal)) { return text; } int j; for (j = "resources".Length; j < text.Length && char.IsDigit(text[j]); j++) { } if (j <= "resources".Length) { return text; } return text.Substring(j); } } [HarmonyPatch(typeof(LevelGenerator), "GenerateDone")] internal static class LevelDonePatch { [HarmonyPostfix] private static void Postfix(LevelGenerator __instance) { if (__instance.Generated) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(TexturePatch.AfterLevel()); } } } internal static class Tsv { internal static List<KeyValuePair<string, string>> Read(string fileName) { List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); string text = Path.Combine(Plugin.ModFolder, fileName); if (!File.Exists(text)) { Plugin.Log.LogWarning((object)("Missing file: " + text)); return list; } try { string[] array = File.ReadAllLines(text, Encoding.UTF8); foreach (string text2 in array) { if (!string.IsNullOrWhiteSpace(text2) && !text2.StartsWith("#")) { string[] array2 = text2.Split('\t'); if (array2.Length == 2 && !string.IsNullOrWhiteSpace(array2[1])) { list.Add(new KeyValuePair<string, string>(array2[0], array2[1].Replace("\\n", "\n"))); } } } } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to read " + fileName + ": " + ex.Message)); } return list; } } }