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 Allspeak v0.2.1
BepInEx/plugins/Allspeak.dll
Decompiled 2 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Splatform; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Allspeak")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+1b8ad2e3c4f43e7ef2f22003d122ed592a4189c3")] [assembly: AssemblyProduct("Allspeak")] [assembly: AssemblyTitle("Allspeak")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Allspeak { [BepInPlugin("com.iyon.allspeak", "Allspeak", "0.2.1")] public class Plugin : BaseUnityPlugin { public const string Guid = "com.iyon.allspeak"; public const string Name = "Allspeak"; internal static Plugin Instance; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<string> TargetLanguage; internal static ConfigEntry<string> IgnoredLanguages; internal static ConfigEntry<double> DetectionConfidence; internal static ConfigEntry<bool> IncludePlayerName; internal static ConfigEntry<bool> HideOriginal; internal static ConfigEntry<string> AlteredPrefix; internal static ConfigEntry<Color> NameColor; internal static ConfigEntry<Color> NormalColor; internal static ConfigEntry<Color> ShoutColor; internal static ConfigEntry<Color> WhisperColor; private const string ActiveText = "Allspeak is active"; private const string InactiveText = "Allspeak inactive, check logs"; private Harmony _harmony; private static readonly Dictionary<string, string> GameLanguageCodes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { ["Arabic"] = "ar", ["Bulgarian"] = "bg", ["Chinese"] = "zh-CN", ["Chinese_Trad"] = "zh-TW", ["Croatian"] = "hr", ["Czech"] = "cs", ["Danish"] = "da", ["Dutch"] = "nl", ["English"] = "en", ["Finnish"] = "fi", ["French"] = "fr", ["Georgian"] = "ka", ["German"] = "de", ["Greek"] = "el", ["Hindi"] = "hi", ["Hungarian"] = "hu", ["Icelandic"] = "is", ["Indonesian"] = "id", ["Italian"] = "it", ["Japanese"] = "ja", ["Korean"] = "ko", ["Latvian"] = "lv", ["Lithuanian"] = "lt", ["Macedonian"] = "mk", ["Norwegian"] = "no", ["Polish"] = "pl", ["Portuguese_Brazilian"] = "pt", ["Portuguese_European"] = "pt-PT", ["Romanian"] = "ro", ["Russian"] = "ru", ["Serbian"] = "sr", ["Slovak"] = "sk", ["Spanish"] = "es", ["Swedish"] = "sv", ["Thai"] = "th", ["Turkish"] = "tr", ["Ukrainian"] = "uk", ["Vietnamese"] = "vi" }; private const int MaxWordsForFallback = 4; private void Awake() { //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch. false = no translations."); TargetLanguage = ((BaseUnityPlugin)this).Config.Bind<string>("General", "TargetLanguage", "auto", "Language to translate into. 'auto' = game's UI language (fallback en). Otherwise a Google Translate code, e.g. en, de, no, pt-PT, zh-TW. Full list in README."); IgnoredLanguages = ((BaseUnityPlugin)this).Config.Bind<string>("General", "IgnoredLanguages", "", "Comma-separated Google Translate codes you understand; messages detected as these are not translated. E.g. de,fr"); DetectionConfidence = ((BaseUnityPlugin)this).Config.Bind<double>("General", "DetectionConfidence", 0.9, "Minimum auto-detect confidence (0-1) to trust Google's language detection. Below this, a player's remembered language is used instead."); IncludePlayerName = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "IncludePlayerName", true, "Show sender's name on translated lines."); HideOriginal = ((BaseUnityPlugin)this).Config.Bind<bool>("Display", "HideOriginal", true, "Replace the original chat line with the translation. Original still shown if not translated (understood language or failure)."); AlteredPrefix = ((BaseUnityPlugin)this).Config.Bind<string>("Display", "AlteredPrefix", "[\ud83d\udcac]", "Prefix on translated lines when HideOriginal = true. Empty = none. Emoji may render as a box if the game font lacks it."); NameColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Display", "NameColor", new Color(1f, 0.647f, 0f), "Player name color (default orange)."); NormalColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Display", "NormalColor", Color.white, "Normal (proximity) chat text color."); ShoutColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Display", "ShoutColor", Color.yellow, "Shout text color."); WhisperColor = ((BaseUnityPlugin)this).Config.Bind<Color>("Display", "WhisperColor", new Color(1f, 1f, 1f, 0.75f), "Whisper text color."); PlayerLanguages.Load(Path.Combine(Paths.ConfigPath, "com.iyon.allspeak.players.tsv")); _harmony = new Harmony("com.iyon.allspeak"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Allspeak 0.2.1 loaded"); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } internal static HashSet<string> IgnoredSet() { return new HashSet<string>(from s in IgnoredLanguages.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries) select s.Trim().ToLowerInvariant(), StringComparer.OrdinalIgnoreCase); } internal static string ResolveTargetLanguage() { string text = TargetLanguage.Value.Trim(); if (!text.Equals("auto", StringComparison.OrdinalIgnoreCase)) { return text; } Localization instance = Localization.instance; string text2 = ((instance != null) ? instance.GetSelectedLanguage() : null); if (text2 == null || !GameLanguageCodes.TryGetValue(text2, out var value)) { return "en"; } return value; } internal void Translate(string text, string playerId, string playerName, Type type, Action showOriginal) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) string fallbackLang = ((text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length <= 4) ? PlayerLanguages.Get(playerId) : null); ((MonoBehaviour)this).StartCoroutine(TranslateRoutine(text, fallbackLang, delegate(string source, string target, string translated, double? confidence) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) if (confidence.HasValue) { PlayerLanguages.Observe(playerId, playerName, source, confidence.Value); } if (IsUnderstood(source) || string.IsNullOrWhiteSpace(translated)) { showOriginal?.Invoke(); } else { AddChatLine(FormatLine((showOriginal != null) ? AlteredPrefix.Value : ("[" + source + "→" + target + "]"), playerName, translated, type)); } }, showOriginal)); } private static string FormatLine(string tag, string name, string text, Type type) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_0081: Unknown result type (might be due to invalid IL or missing references) Color value = NormalColor.Value; if ((int)type == 2) { value = ShoutColor.Value; text = text.ToUpper(); } else if ((int)type == 0) { value = WhisperColor.Value; text = text.ToLowerInvariant(); } StringBuilder stringBuilder = new StringBuilder(); if (!string.IsNullOrEmpty(tag)) { stringBuilder.Append(tag).Append(' '); } if (IncludePlayerName.Value && !string.IsNullOrEmpty(name)) { stringBuilder.Append("<color=#" + ColorUtility.ToHtmlStringRGBA(NameColor.Value) + ">" + name + "</color>: "); } stringBuilder.Append("<color=#" + ColorUtility.ToHtmlStringRGBA(value) + "><noparse>" + text + "</noparse></color>"); return stringBuilder.ToString(); } private static void AddChatLine(string line) { if (!((Object)(object)Chat.instance == (Object)null)) { Chat.instance.m_hideTimer = 0f; ((Terminal)Chat.instance).AddString(line); } } private static bool IsUnderstood(string lang) { if (!string.Equals(lang, ResolveTargetLanguage(), StringComparison.OrdinalIgnoreCase)) { return IgnoredSet().Contains(lang); } return true; } internal void Announce() { ((MonoBehaviour)this).StartCoroutine(TranslateRoutine("Allspeak is active", null, delegate(string source, string target, string translated, double? confidence) { Chat instance = Chat.instance; if (instance != null) { ((Terminal)instance).AddString(string.IsNullOrWhiteSpace(translated) ? "Allspeak is active" : translated); } }, delegate { Chat instance = Chat.instance; if (instance != null) { ((Terminal)instance).AddString("Allspeak inactive, check logs"); } })); } private IEnumerator TranslateRoutine(string text, string fallbackLang, Action<string, string, string, double?> onSuccess, Action onFailure) { string target = ResolveTargetLanguage(); string source = null; string translated = null; double? confidence = null; yield return Request(text, "auto", target, delegate(string src, string text2, double? conf, bool flag2) { if (flag2) { source = src; translated = text2; confidence = conf; } }); if (source != null) { bool flag = confidence >= DetectionConfidence.Value; if (flag || fallbackLang == null || string.Equals(fallbackLang, source, StringComparison.OrdinalIgnoreCase)) { onSuccess(source, target, translated, flag ? confidence : ((double?)null)); yield break; } } if (fallbackLang != null) { string tr2 = null; bool ok2 = false; yield return Request(text, fallbackLang, target, delegate(string src, string text2, double? conf, bool flag2) { if (flag2) { tr2 = text2; ok2 = true; } }); if (ok2) { onSuccess(fallbackLang, target, tr2, null); yield break; } } onFailure?.Invoke(); } private IEnumerator Request(string text, string sourceLang, string target, Action<string, string, double?, bool> done) { string url = "https://clients5.google.com/translate_a/single?client=dict-chrome-ex&dt=t&sl=" + UnityWebRequest.EscapeURL(sourceLang) + "&tl=" + UnityWebRequest.EscapeURL(target) + "&q=" + UnityWebRequest.EscapeURL(text); for (int attempt = 0; attempt < 3; attempt++) { UnityWebRequest req = UnityWebRequest.Get(url); try { yield return req.SendWebRequest(); if ((int)req.result == 1) { if (TryParse(req.downloadHandler.text, sourceLang, out var source, out var translated, out var confidence)) { done(source, translated, confidence, arg4: true); yield break; } ((BaseUnityPlugin)this).Logger.LogWarning((object)"Translate: unexpected response"); } else { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Translate attempt {attempt + 1}/3 failed: {req.error}"); if (req.responseCode == 429 && attempt != 2) { yield return (object)new WaitForSeconds(2f * (float)(attempt + 1)); continue; } } } finally { ((IDisposable)req)?.Dispose(); } break; } done(null, null, null, arg4: false); } private static bool TryParse(string json, string sourceLang, out string source, out string translated, out double? confidence) { source = null; translated = null; confidence = null; try { if (!(MiniJson.Parse(json) is List<object> { Count: not 0 } list)) { return false; } StringBuilder stringBuilder = new StringBuilder(); if (list[0] is List<object> list2) { foreach (object item in list2) { if (item is List<object> { Count: >0 } list3 && list3[0] is string value) { stringBuilder.Append(value); } else if (item is string value2) { stringBuilder.Append(value2); } } } translated = stringBuilder.ToString(); if (list.Count > 2 && list[2] is string text && !string.IsNullOrEmpty(text)) { source = text; } else if (!string.Equals(sourceLang, "auto", StringComparison.OrdinalIgnoreCase)) { source = sourceLang; } if (list.Count > 6 && list[6] is double value3) { confidence = value3; } return source != null; } catch (Exception) { return false; } } } [HarmonyPatch(typeof(Terminal), "AddString", new Type[] { typeof(PlatformUserID), typeof(string), typeof(Type), typeof(bool) })] internal static class ChatLinePatch { private static bool _bypass; private unsafe static bool Prefix(Terminal __instance, PlatformUserID user, string text, Type type, bool timestamp) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Invalid comparison between Unknown and I4 //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) if (_bypass || !(__instance is Chat)) { return true; } if (!Plugin.Enabled.Value) { return true; } if ((int)type == 3 || string.IsNullOrWhiteSpace(text)) { return true; } if (user == UserInfo.GetLocalUser().UserId) { return true; } PlayerInfo val = default(PlayerInfo); if (!ZNet.TryGetPlayerByPlatformUserID(user, ref val)) { return true; } string playerName = CensorShittyWords.FilterUGC(val.m_name, (UGCType)2, user, 0L, (GetUserProfileCompletedHandler)null); bool value = Plugin.HideOriginal.Value; Action showOriginal = null; if (value) { showOriginal = delegate { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } _bypass = true; try { __instance.AddString(user, text, type, timestamp); } finally { _bypass = false; } }; } Plugin.Instance.Translate(text, ((PlatformUserID)(ref user)).IsValid ? ((object)(*(PlatformUserID*)(&user))/*cast due to .constrained prefix*/).ToString() : null, playerName, type, showOriginal); return !value; } } [HarmonyPatch(typeof(Player), "OnSpawned")] internal static class SpawnPatch { private static ZNet _announcedFor; private static void Postfix(Player __instance) { if (Plugin.Enabled.Value && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !((Object)(object)ZNet.instance == (Object)null) && !((Object)(object)_announcedFor == (Object)(object)ZNet.instance)) { _announcedFor = ZNet.instance; Plugin.Instance.Announce(); } } } internal static class PlayerLanguages { private class Entry { public string Lang; public double Confidence; public string Name; } private static readonly Dictionary<string, Entry> Entries = new Dictionary<string, Entry>(); private static string _path; public static void Load(string path) { _path = path; if (!File.Exists(path)) { return; } try { string[] array = File.ReadAllLines(path); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { '\t' }); if (array2.Length >= 3 && double.TryParse(array2[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { if (result > 1.0) { result = 1.0; } Entries[array2[0]] = new Entry { Lang = array2[1], Confidence = result, Name = ((array2.Length > 3) ? array2[3] : "") }; } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to load " + path + ": " + ex.Message)); } } public static string Get(string id) { if (id == null || !Entries.TryGetValue(id, out var value)) { return null; } return value.Lang; } public static void Observe(string id, string name, string lang, double confidence) { if (id != null && !string.IsNullOrEmpty(lang) && (!Entries.TryGetValue(id, out var value) || (!(confidence < value.Confidence) && !string.Equals(value.Lang, lang, StringComparison.OrdinalIgnoreCase)))) { Entries[id] = new Entry { Lang = lang, Confidence = confidence, Name = (name ?? "") }; Save(); } } private static void Save() { try { File.WriteAllLines(_path, Entries.Select((KeyValuePair<string, Entry> kv) => kv.Key + "\t" + kv.Value.Lang + "\t" + kv.Value.Confidence.ToString(CultureInfo.InvariantCulture) + "\t" + kv.Value.Name.Replace('\t', ' '))); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Failed to save " + _path + ": " + ex.Message)); } } } internal static class MiniJson { public static object Parse(string json) { int i = 0; return ParseValue(json, ref i); } private static void SkipWs(string s, ref int i) { while (i < s.Length && char.IsWhiteSpace(s[i])) { i++; } } private static object ParseValue(string s, ref int i) { SkipWs(s, ref i); if (i >= s.Length) { throw new FormatException("eof"); } switch (s[i]) { case '[': return ParseArray(s, ref i); case '{': return ParseObject(s, ref i); case '"': return ParseString(s, ref i); case 't': i += 4; return true; case 'f': i += 5; return false; case 'n': i += 4; return null; default: return ParseNumber(s, ref i); } } private static List<object> ParseArray(string s, ref int i) { List<object> list = new List<object>(); i++; SkipWs(s, ref i); if (s[i] == ']') { i++; return list; } while (true) { SkipWs(s, ref i); if (s[i] == ',' || s[i] == ']') { list.Add(null); } else { list.Add(ParseValue(s, ref i)); } SkipWs(s, ref i); if (s[i] != ',') { break; } i++; } if (s[i] == ']') { i++; return list; } throw new FormatException("array"); } private static Dictionary<string, object> ParseObject(string s, ref int i) { Dictionary<string, object> dictionary = new Dictionary<string, object>(); i++; SkipWs(s, ref i); if (s[i] == '}') { i++; return dictionary; } while (true) { SkipWs(s, ref i); string key = ParseString(s, ref i); SkipWs(s, ref i); i++; dictionary[key] = ParseValue(s, ref i); SkipWs(s, ref i); if (s[i] != ',') { break; } i++; } if (s[i] == '}') { i++; return dictionary; } throw new FormatException("object"); } private static string ParseString(string s, ref int i) { StringBuilder stringBuilder = new StringBuilder(); i++; while (i < s.Length) { char c = s[i++]; switch (c) { case '"': return stringBuilder.ToString(); default: stringBuilder.Append(c); break; case '\\': { char c2 = s[i++]; switch (c2) { case 'n': stringBuilder.Append('\n'); break; case 't': stringBuilder.Append('\t'); break; case 'r': stringBuilder.Append('\r'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'u': stringBuilder.Append((char)Convert.ToInt32(s.Substring(i, 4), 16)); i += 4; break; default: stringBuilder.Append(c2); break; } break; } } } throw new FormatException("string"); } private static double ParseNumber(string s, ref int i) { int num = i; while (i < s.Length && "+-0123456789.eE".IndexOf(s[i]) >= 0) { i++; } return double.Parse(s.Substring(num, i - num), CultureInfo.InvariantCulture); } } internal static class BuildInfo { public const string Version = "0.2.1"; } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }