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 Valheim Russian Voiceover v1.0.4
BepInEx/plugins/SwedishHouse-Valheim_Russian_Voiceover/ValheimRussianVoiceover.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.Audio; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace ValheimRussianVoiceover; [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("artem.valheim.russianvoiceover", "Valheim Russian Voiceover", "1.0.4")] public sealed class VoiceoverPlugin : BaseUnityPlugin { public const string PluginGuid = "artem.valheim.russianvoiceover"; public const string PluginName = "Valheim Russian Voiceover"; public const string PluginVersion = "1.0.4"; internal static ManualLogSource Log; internal static VoiceoverHost Host; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> Volume; internal static ConfigEntry<float> Reverb; internal static ConfigEntry<int> CacheSize; internal static ConfigEntry<float> DreamFadeIn; internal static ConfigEntry<bool> VoiceMessages; internal static ConfigEntry<bool> LogTokens; private Harmony _harmony; private void Awake() { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; BindConfig(); BogWitchLocalization.TryRegister(); string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string voicesFolder = VoiceBank.ResolveRoot(directoryName); if (!VoiceBank.Load(voicesFolder)) { Log.LogError((object)("Voice bank could not be loaded. Checked standard, flattened and nested Thunderstore/r2modman layouts under: " + directoryName)); return; } VoiceSwitches.Initialize(VoiceBank.AllEntries); if (!Enabled.Value) { Log.LogInfo((object)"Voice playback is disabled in configuration. Localization and line switches remain available."); return; } if (Compatibility.ExternalAudioDialoguesLoaded()) { Log.LogError((object)"Tchernobill AudioDialogues is also loaded. Own playback engine is standing down to prevent double voices. Remove/disable AudioDialogues and restart the game."); return; } GameObject val = new GameObject("ValheimRussianVoiceoverHost"); Object.DontDestroyOnLoad((Object)(object)val); ((Object)val).hideFlags = (HideFlags)61; Host = val.AddComponent<VoiceoverHost>(); ClipCache.Initialize((MonoBehaviour)(object)Host, CacheSize.Value); VoicePlayer.Initialize((MonoBehaviour)(object)Host); ClipCache.Preload(VoiceBank.PreloadKeys(CacheSize.Value / 2)); _harmony = new Harmony("artem.valheim.russianvoiceover"); int active = PatchManager.Apply(_harmony); LogCompatibilityStatus(active); } private static void LogCompatibilityStatus(int active) { string text = PatchManager.FailedGroupSummary(); if (active == 8) { Log.LogInfo((object)"[COMPATIBILITY OK] Valheim Russian Voiceover 1.0.4. Harmony coverage: 8/8. All required Valheim dialogue hooks are active. AudioDialogues is not required."); } else if (active > 0) { Log.LogWarning((object)("[COMPATIBILITY DEGRADED] Valheim Russian Voiceover 1.0.4. Harmony coverage: " + active + "/" + 8 + ". Working hooks remain active, but some voice categories may not play. Failed groups: " + text + ". This usually means Valheim changed one or more dialogue APIs; see the [patch] diagnostics above.")); } else { Log.LogError((object)("[COMPATIBILITY FAILED] Valheim Russian Voiceover 1.0.4. Harmony coverage: 0/" + 8 + ". No required dialogue hooks are active, so Russian voice playback cannot be triggered. Failed groups: " + text + ". Valheim likely changed the dialogue API; update this mod before relying on voice playback.")); } } private void BindConfig() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Expected O, but got Unknown Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "Enabled", true, "Master switch for Russian voice playback."); Volume = ((BaseUnityPlugin)this).Config.Bind<float>("Playback", "Volume", 1f, new ConfigDescription("Voice volume multiplier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), new object[0])); Reverb = ((BaseUnityPlugin)this).Config.Bind<float>("Playback", "Reverb", 1f, new ConfigDescription("3D voice reverb send multiplier. 0 = dry.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), new object[0])); CacheSize = ((BaseUnityPlugin)this).Config.Bind<int>("Performance", "CacheSize", 64, new ConfigDescription("Decoded OGG clips kept in memory.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(8, 512), new object[0])); DreamFadeIn = ((BaseUnityPlugin)this).Config.Bind<float>("Playback", "DreamFadeIn", 1.5f, new ConfigDescription("Dream voice fade-in time in seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), new object[0])); VoiceMessages = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "VoiceMessages", true, "Speak supported MessageHud lines."); LogTokens = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "LogTokens", false, "Log resolved/missing localization keys once per session."); } private void OnDestroy() { try { if (_harmony != null) { _harmony.UnpatchSelf(); } VoicePlayer.StopAll(); VoicePlayer.Stop2D(); ClipCache.Clear(); } catch (Exception ex) { if (Log != null) { Log.LogDebug((object)("Shutdown cleanup: " + ex.Message)); } } } } public sealed class VoiceoverHost : MonoBehaviour { private void Update() { VoiceSwitches.Tick(); VoicePlayer.Tick(); } } internal static class Compatibility { internal static bool ExternalAudioDialoguesLoaded() { Assembly executingAssembly = Assembly.GetExecutingAssembly(); Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (assembly == executingAssembly) { continue; } string a = assembly.GetName().Name ?? string.Empty; if (string.Equals(a, "AudioDialogues", StringComparison.OrdinalIgnoreCase)) { return true; } try { if (assembly.GetType("AudioDialogues.Audio.VoicePlayer", throwOnError: false) != null) { return true; } } catch { } } return false; } } internal sealed class VoiceEntry { internal string Key; internal string File; internal string Voice; internal string Root; internal string AbsoluteFile; } internal static class VoiceBank { private static readonly Dictionary<string, VoiceEntry> Entries = new Dictionary<string, VoiceEntry>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> Logged = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly Regex TokenPattern = new Regex("\\$([A-Za-z0-9_]+)", RegexOptions.Compiled); private static readonly Regex ObjectPattern = new Regex("\\{[^{}]*\\}", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex LanguagePattern = new Regex("\\\"language\\\"\\s*:\\s*\\\"(?<v>(?:\\\\.|[^\\\"])*)\\\"", RegexOptions.IgnoreCase | RegexOptions.Compiled); internal static ICollection<VoiceEntry> AllEntries => Entries.Values; internal static int Count => Entries.Count; internal static string Language { get; private set; } internal static string ResolveRoot(string pluginDirectory) { if (string.IsNullOrEmpty(pluginDirectory)) { return null; } List<string> list = new List<string>(); AddCandidate(list, Path.Combine(pluginDirectory, "voices")); AddCandidate(list, pluginDirectory); try { string[] directories = Directory.GetDirectories(pluginDirectory); for (int i = 0; i < directories.Length; i++) { AddCandidate(list, Path.Combine(directories[i], "voices")); AddCandidate(list, directories[i]); } } catch { } for (int j = 0; j < list.Count; j++) { if (LooksLikeVoiceRoot(list[j])) { string text = (string.Equals(list[j], Path.Combine(pluginDirectory, "voices"), StringComparison.OrdinalIgnoreCase) ? "standard" : (string.Equals(list[j], pluginDirectory, StringComparison.OrdinalIgnoreCase) ? "flattened" : "nested")); VoiceoverPlugin.Log.LogInfo((object)("[voicebank] Resolved " + text + " layout: " + list[j])); return list[j]; } } try { string[] files = Directory.GetFiles(pluginDirectory, "index.json", SearchOption.AllDirectories); for (int k = 0; k < files.Length; k++) { string directoryName = Path.GetDirectoryName(files[k]); if (LooksLikeVoiceRoot(directoryName)) { VoiceoverPlugin.Log.LogWarning((object)("[voicebank] Recovered non-standard package layout: " + directoryName)); return directoryName; } } } catch (Exception ex) { VoiceoverPlugin.Log.LogDebug((object)("Voice bank layout scan failed: " + ex.Message)); } VoiceoverPlugin.Log.LogError((object)("No valid voice bank root found under plugin directory: " + pluginDirectory)); return Path.Combine(pluginDirectory, "voices"); } private static void AddCandidate(List<string> candidates, string path) { if (string.IsNullOrEmpty(path)) { return; } for (int i = 0; i < candidates.Count; i++) { if (string.Equals(candidates[i], path, StringComparison.OrdinalIgnoreCase)) { return; } } candidates.Add(path); } private static bool LooksLikeVoiceRoot(string folder) { if (string.IsNullOrEmpty(folder) || !Directory.Exists(folder)) { return false; } string path = Path.Combine(folder, "index.json"); if (!File.Exists(path)) { return false; } try { string text = File.ReadAllText(path, Encoding.UTF8); return text.IndexOf("\"clips\"", StringComparison.OrdinalIgnoreCase) >= 0 && text.IndexOf("\"key\"", StringComparison.OrdinalIgnoreCase) >= 0 && text.IndexOf("\"file\"", StringComparison.OrdinalIgnoreCase) >= 0; } catch { return false; } } internal static bool Load(string voicesFolder) { Entries.Clear(); Logged.Clear(); Language = "Russian"; if (string.IsNullOrEmpty(voicesFolder) || !Directory.Exists(voicesFolder)) { VoiceoverPlugin.Log.LogError((object)("Missing voices folder: " + voicesFolder)); return false; } string text = Path.Combine(voicesFolder, "index.json"); if (!File.Exists(text)) { VoiceoverPlugin.Log.LogError((object)("Missing voice index: " + text)); return false; } try { string input = File.ReadAllText(text, Encoding.UTF8); Match match = LanguagePattern.Match(input); if (match.Success) { Language = JsonUnescape(match.Groups["v"].Value); } MatchCollection matchCollection = ObjectPattern.Matches(input); Dictionary<string, string> dictionary = BuildOggLookup(voicesFolder); int num = 0; int num2 = 0; for (int i = 0; i < matchCollection.Count; i++) { string value = matchCollection[i].Value; string text2 = ReadJsonField(value, "key"); string text3 = ReadJsonField(value, "file"); if (!string.IsNullOrEmpty(text2) && !string.IsNullOrEmpty(text3)) { text2 = Normalize(text2); string voice = ReadJsonField(value, "voice") ?? string.Empty; VoiceEntry voiceEntry = new VoiceEntry(); voiceEntry.Key = text2; voiceEntry.File = text3.Replace('/', Path.DirectorySeparatorChar); voiceEntry.Voice = voice; voiceEntry.Root = voicesFolder; string text4 = Path.Combine(voicesFolder, voiceEntry.File); string value2; if (File.Exists(text4)) { voiceEntry.AbsoluteFile = text4; } else if (dictionary.TryGetValue(Path.GetFileName(voiceEntry.File), out value2) && !string.IsNullOrEmpty(value2)) { voiceEntry.AbsoluteFile = value2; num2++; } else { num++; } Entries[text2] = voiceEntry; } } if (Entries.Count == 0) { VoiceoverPlugin.Log.LogError((object)"index.json was read but no clip entries were found."); return false; } VoiceoverPlugin.Log.LogInfo((object)("Voice bank ready: " + Entries.Count + " clips, language " + Language + ", missing OGG files: " + num + ", recovered paths: " + num2 + ".")); if (num2 > 0) { VoiceoverPlugin.Log.LogWarning((object)("[voicebank] Recovered " + num2 + " OGG path(s) from a flattened/non-standard package layout.")); } if (num > 0) { VoiceoverPlugin.Log.LogWarning((object)"Some entries point to missing OGG files. Those lines will be skipped instead of crashing."); } return true; } catch (Exception ex) { VoiceoverPlugin.Log.LogError((object)("Could not read voice index: " + ex)); return false; } } private static Dictionary<string, string> BuildOggLookup(string root) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); try { string[] files = Directory.GetFiles(root, "*.ogg", SearchOption.AllDirectories); for (int i = 0; i < files.Length; i++) { string fileName = Path.GetFileName(files[i]); if (!hashSet.Contains(fileName)) { if (dictionary.TryGetValue(fileName, out var _)) { dictionary.Remove(fileName); hashSet.Add(fileName); } else { dictionary[fileName] = files[i]; } } } } catch (Exception ex) { VoiceoverPlugin.Log.LogDebug((object)("Could not build OGG lookup: " + ex.Message)); } return dictionary; } private static string ReadJsonField(string block, string name) { Regex regex = new Regex("\\\"" + Regex.Escape(name) + "\\\"\\s*:\\s*\\\"(?<v>(?:\\\\.|[^\\\"])*)\\\"", RegexOptions.IgnoreCase | RegexOptions.Singleline); Match match = regex.Match(block); if (!match.Success) { return null; } return JsonUnescape(match.Groups["v"].Value); } private static string JsonUnescape(string s) { if (s == null || s.IndexOf('\\') < 0) { return s; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < s.Length; i++) { char c = s[i]; if (c != '\\' || i + 1 >= s.Length) { stringBuilder.Append(c); continue; } char c2 = s[++i]; switch (c2) { case 'n': stringBuilder.Append('\n'); continue; case 'r': stringBuilder.Append('\r'); continue; case 't': stringBuilder.Append('\t'); continue; case 'b': stringBuilder.Append('\b'); continue; case 'f': stringBuilder.Append('\f'); continue; case '\\': stringBuilder.Append('\\'); continue; case '/': stringBuilder.Append('/'); continue; case '"': stringBuilder.Append('"'); continue; case 'u': if (i + 4 < s.Length) { if (int.TryParse(s.Substring(i + 1, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { stringBuilder.Append((char)result); i += 4; } else { stringBuilder.Append(c2); } continue; } break; } stringBuilder.Append(c2); } return stringBuilder.ToString(); } internal static bool Contains(string key) { if (!string.IsNullOrEmpty(key)) { return Entries.ContainsKey(Normalize(key)); } return false; } internal static bool TryResolve(string raw, string source, out string key) { key = null; if (string.IsNullOrEmpty(raw)) { return false; } string text = raw.Trim(); if (text.Length == 0) { return false; } if (Lookup(text, out key)) { LogToken(key, source, hit: true); return true; } if (text[0] == '$' && Lookup(text.Substring(1), out key)) { LogToken(key, source, hit: true); return true; } MatchCollection matchCollection = TokenPattern.Matches(text); if (matchCollection.Count > 0) { string value = matchCollection[matchCollection.Count - 1].Groups[1].Value; if (Lookup(value, out key)) { LogToken(key, source, hit: true); return true; } LogToken(value, source, hit: false); } return false; } internal static string ForSpeaker(string key, GameObject talker) { string text = SpeakerVoice.For(talker); if (string.IsNullOrEmpty(text)) { return key; } string text2 = Normalize(key + "__" + text); if (!Entries.ContainsKey(text2)) { return key; } return text2; } internal static string GetPath(string key) { if (string.IsNullOrEmpty(key) || !Entries.TryGetValue(Normalize(key), out var value)) { return null; } if (!string.IsNullOrEmpty(value.AbsoluteFile) && File.Exists(value.AbsoluteFile)) { return value.AbsoluteFile; } string text = Path.Combine(value.Root, value.File); if (!File.Exists(text)) { return null; } return text; } internal static IEnumerable<string> PreloadKeys(int limit) { if (limit <= 0) { yield break; } string[] prefixes = new string[3] { "tutorial_", "npc_haldor_greet", "npc_hildir_greet" }; int count = 0; foreach (KeyValuePair<string, VoiceEntry> pair in Entries) { for (int i = 0; i < prefixes.Length; i++) { KeyValuePair<string, VoiceEntry> keyValuePair = pair; if (keyValuePair.Key.StartsWith(prefixes[i], StringComparison.OrdinalIgnoreCase)) { KeyValuePair<string, VoiceEntry> keyValuePair2 = pair; yield return keyValuePair2.Key; count++; break; } } if (count >= limit) { break; } } } private static bool Lookup(string candidate, out string key) { key = null; if (string.IsNullOrEmpty(candidate)) { return false; } string text = Normalize(candidate); if (!Entries.ContainsKey(text)) { return false; } key = text; return true; } private static string Normalize(string key) { return key.Trim().ToLowerInvariant(); } private static void LogToken(string token, string source, bool hit) { if (VoiceoverPlugin.LogTokens != null && VoiceoverPlugin.LogTokens.Value && !string.IsNullOrEmpty(token)) { string item = (hit ? "hit:" : "miss:") + token; if (Logged.Add(item)) { VoiceoverPlugin.Log.LogInfo((object)("[token " + (hit ? "hit" : "miss") + "] " + token + " (" + (source ?? "?") + ")")); } } } } internal sealed class LineRule { internal string Section; internal string Subcategory; internal bool Enabled; } internal static class VoiceSwitches { private static readonly Dictionary<string, bool> Values = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, LineRule> Rules = new Dictionary<string, LineRule>(StringComparer.OrdinalIgnoreCase); private static List<VoiceEntry> _entries = new List<VoiceEntry>(); private static string _path; private static DateTime _stamp; private static float _nextCheck; internal static void Initialize(ICollection<VoiceEntry> entries) { _entries = new List<VoiceEntry>(entries); _path = Path.Combine(Paths.ConfigPath, "ValheimVoiceControls.cfg"); EnsureCanonicalConfig(); Reload(announce: false); } internal static void Tick() { if (Time.realtimeSinceStartup < _nextCheck) { return; } _nextCheck = Time.realtimeSinceStartup + 1f; try { DateTime dateTime = (File.Exists(_path) ? File.GetLastWriteTimeUtc(_path) : DateTime.MinValue); if (dateTime != _stamp) { Reload(announce: true); } } catch { } } internal static bool IsEnabled(string key) { if (string.IsNullOrEmpty(key)) { return false; } if (!Rules.TryGetValue(key, out var value)) { return true; } if (!string.IsNullOrEmpty(value.Section) && Values.TryGetValue(value.Section, out var value2) && !value2) { return false; } if (!string.IsNullOrEmpty(value.Subcategory) && Values.TryGetValue(value.Subcategory, out value2) && !value2) { return false; } return value.Enabled; } private static void Reload(bool announce) { Values.Clear(); Rules.Clear(); if (!File.Exists(_path)) { EnsureCanonicalConfig(); } string text = null; string subcategory = null; string[] array = File.ReadAllLines(_path, Encoding.UTF8); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0 || text2[0] == '#' || text2[0] == ';') { continue; } if (text2[0] == '[' && text2[text2.Length - 1] == ']') { text = text2.Substring(1, text2.Length - 2).Trim(); subcategory = null; continue; } int num = text2.IndexOf('='); if (num <= 0) { continue; } string text3 = text2.Substring(0, num).Trim(); string value = text2.Substring(num + 1).Trim(); if (bool.TryParse(value, out var result)) { Values[text3] = result; if (VoiceBank.Contains(text3)) { LineRule lineRule = new LineRule(); lineRule.Section = text; lineRule.Subcategory = subcategory; lineRule.Enabled = result; Rules[text3] = lineRule; } else if (!string.Equals(text3, text, StringComparison.OrdinalIgnoreCase)) { subcategory = text3; } } } _stamp = File.GetLastWriteTimeUtc(_path); if (announce) { VoiceoverPlugin.Log.LogInfo((object)"Voice switches reloaded. Changes apply to new playback requests."); return; } VoiceoverPlugin.Log.LogInfo((object)("Voice switches ready: " + Rules.Count + "/" + VoiceBank.Count + " indexed lines. Configuration: " + _path)); } private static void EnsureCanonicalConfig() { Dictionary<string, bool> dictionary = ReadRawValues(_path); int num = 0; foreach (string key in dictionary.Keys) { if (VoiceBank.Contains(key)) { num++; } } if (File.Exists(_path) && num == VoiceBank.Count) { return; } try { Directory.CreateDirectory(Paths.ConfigPath); string text = _path + ".tmp"; using (StreamWriter streamWriter = new StreamWriter(text, append: false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))) { streamWriter.WriteLine("# Valheim Russian Voiceover — Valheim Voice Controls"); streamWriter.WriteLine("# true = enabled, false = disabled"); streamWriter.WriteLine("# A category set to false disables every subcategory and every individual line inside it."); streamWriter.WriteLine("# A subcategory set to false disables every individual line inside that subcategory."); streamWriter.WriteLine("# Individual line switches apply only when their category/subcategory parents are enabled."); streamWriter.WriteLine("# All settings are true by default."); streamWriter.WriteLine(); WriteSimple(streamWriter, "Haldor", "Haldor", dictionary); WriteSimple(streamWriter, "Hildir", "Hildir", dictionary); WriteSimple(streamWriter, "Hugin", "Hugin", dictionary); WriteSimple(streamWriter, "Munin", "Munin", dictionary); WriteGrouped(streamWriter, "Dvergers", new string[3] { "Dverger", "Dverger Rogue", "Dverger Mage" }, dictionary); WriteSimple(streamWriter, "Fallen Viking", "Fallen Viking", dictionary); WriteSimple(streamWriter, "Rune_stones", "Rune_stones", dictionary); WriteSimple(streamWriter, "Fallen Warrior of the North", "Fallen Warrior of the North", dictionary); WriteGrouped(streamWriter, "Dreams and Voices of the Dead", new string[2] { "Dreams", "Voices of the Dead" }, dictionary); WriteSimple(streamWriter, "Saga", "Saga", dictionary); WriteSimple(streamWriter, "The Bog Witch", "The Bog Witch", dictionary); } if (File.Exists(_path)) { File.Delete(_path); } File.Move(text, _path); VoiceoverPlugin.Log.LogInfo((object)("Voice switch configuration generated/updated for " + VoiceBank.Count + " indexed clips; existing boolean choices were preserved where possible.")); } catch (Exception ex) { VoiceoverPlugin.Log.LogError((object)("Could not generate voice switch configuration: " + ex)); } } private static Dictionary<string, bool> ReadRawValues(string path) { Dictionary<string, bool> dictionary = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); try { if (!File.Exists(path)) { return dictionary; } string[] array = File.ReadAllLines(path, Encoding.UTF8); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0 || text[0] == '#' || text[0] == ';' || text[0] == '[') { continue; } int num = text.IndexOf('='); if (num > 0) { string key = text.Substring(0, num).Trim(); if (bool.TryParse(text.Substring(num + 1).Trim(), out var result)) { dictionary[key] = result; } } } } catch { } return dictionary; } private static bool OldValue(Dictionary<string, bool> old, string key) { if (!old.TryGetValue(key, out var value)) { return true; } return value; } private static void WriteSimple(StreamWriter w, string section, string voice, Dictionary<string, bool> old) { w.WriteLine("[" + section + "]"); w.WriteLine(section + " = " + OldValue(old, section).ToString().ToLowerInvariant()); w.WriteLine(); WriteVoiceLines(w, voice, old); w.WriteLine(); } private static void WriteGrouped(StreamWriter w, string section, string[] voices, Dictionary<string, bool> old) { w.WriteLine("[" + section + "]"); w.WriteLine(section + " = " + OldValue(old, section).ToString().ToLowerInvariant()); w.WriteLine(); foreach (string text in voices) { w.WriteLine(text + " = " + OldValue(old, text).ToString().ToLowerInvariant()); WriteVoiceLines(w, text, old); w.WriteLine(); } w.WriteLine(); } private static void WriteVoiceLines(StreamWriter w, string voice, Dictionary<string, bool> old) { List<string> list = new List<string>(); for (int i = 0; i < _entries.Count; i++) { if (string.Equals(_entries[i].Voice, voice, StringComparison.OrdinalIgnoreCase)) { list.Add(_entries[i].Key); } } list.Sort(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < list.Count; i++) { w.WriteLine(list[i] + " = " + OldValue(old, list[i]).ToString().ToLowerInvariant()); } } } internal static class SpeakerVoice { private static Type _ravenType; private static FieldInfo _isMunin; internal static string For(GameObject talker) { if ((Object)(object)talker == (Object)null) { return null; } try { if (_ravenType == null) { _ravenType = ReflectionSafe.TypeByName("Raven"); } if (_ravenType == null) { return null; } Component component = talker.GetComponent(_ravenType); if ((Object)(object)component == (Object)null) { return null; } if (_isMunin == null) { _isMunin = ReflectionSafe.Field(_ravenType, "m_isMunin", isStatic: false); } if (_isMunin == null) { return null; } object value = _isMunin.GetValue(component); return (value is bool && (bool)value) ? "munin" : "hugin"; } catch { return null; } } } internal sealed class CacheEntry { internal AudioClip Clip; internal UnityWebRequest Request; internal float LastUsed; } internal static class ClipCache { private static readonly Dictionary<string, CacheEntry> Loaded = new Dictionary<string, CacheEntry>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, List<Action<AudioClip>>> InFlight = new Dictionary<string, List<Action<AudioClip>>>(StringComparer.OrdinalIgnoreCase); private static MonoBehaviour _host; private static int _capacity; internal static void Initialize(MonoBehaviour host, int capacity) { _host = host; _capacity = Mathf.Max(8, capacity); } internal static void Request(string key, Action<AudioClip> ready) { if (string.IsNullOrEmpty(key) || ready == null) { return; } if (Loaded.TryGetValue(key, out var value) && (Object)(object)value.Clip != (Object)null) { value.LastUsed = Time.realtimeSinceStartup; ready(value.Clip); return; } if (InFlight.TryGetValue(key, out var value2)) { value2.Add(ready); return; } string path = VoiceBank.GetPath(key); if (path == null) { VoiceoverPlugin.Log.LogWarning((object)("No OGG file for key '" + key + "'.")); } else if (!((Object)(object)_host == (Object)null)) { InFlight[key] = new List<Action<AudioClip>>(); InFlight[key].Add(ready); _host.StartCoroutine(LoadRoutine(key, path)); } } internal static void Preload(IEnumerable<string> keys) { int num = 0; foreach (string key2 in keys) { string key = key2; Request(key, delegate { }); num++; } if (num > 0) { VoiceoverPlugin.Log.LogInfo((object)("Preloading " + num + " voice clips.")); } } private static IEnumerator LoadRoutine(string key, string path) { UnityWebRequest request = null; UnityWebRequestAsyncOperation sendOperation = null; AudioClip clip = null; try { request = UnityWebRequestMultimedia.GetAudioClip(new Uri(path).AbsoluteUri, (AudioType)14); DownloadHandler downloadHandler = request.downloadHandler; DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null); if (val != null) { val.streamAudio = true; } sendOperation = request.SendWebRequest(); } catch (Exception ex) { VoiceoverPlugin.Log.LogWarning((object)("Could not start loading '" + key + "': " + ex.Message)); } if (sendOperation != null) { yield return sendOperation; try { if ((int)request.result == 1) { clip = DownloadHandlerAudioClip.GetContent(request); if ((Object)(object)clip != (Object)null) { ((Object)clip).name = key; } } else { VoiceoverPlugin.Log.LogWarning((object)("Could not load '" + key + "': " + request.error)); } } catch (Exception ex2) { VoiceoverPlugin.Log.LogWarning((object)("Could not finish loading '" + key + "': " + ex2.Message)); } } if ((Object)(object)clip != (Object)null) { CacheEntry cacheEntry = new CacheEntry(); cacheEntry.Clip = clip; cacheEntry.Request = request; cacheEntry.LastUsed = Time.realtimeSinceStartup; Loaded[key] = cacheEntry; Trim(); } else if (request != null) { request.Dispose(); } if (!InFlight.TryGetValue(key, out var callbacks)) { yield break; } InFlight.Remove(key); if ((Object)(object)clip == (Object)null) { yield break; } for (int i = 0; i < callbacks.Count; i++) { try { callbacks[i](clip); } catch (Exception ex3) { VoiceoverPlugin.Log.LogError((object)("Voice callback for '" + key + "' failed: " + ex3)); } } } private static void Trim() { while (Loaded.Count > _capacity) { string text = null; float num = float.MaxValue; foreach (KeyValuePair<string, CacheEntry> item in Loaded) { if (!(item.Value.LastUsed >= num) && !VoicePlayer.IsInUse(item.Value.Clip)) { text = item.Key; num = item.Value.LastUsed; } } if (text == null) { break; } Evict(text); } } private static void Evict(string key) { if (!Loaded.TryGetValue(key, out var value)) { return; } Loaded.Remove(key); if ((Object)(object)value.Clip != (Object)null) { try { value.Clip.UnloadAudioData(); } catch { } Object.Destroy((Object)(object)value.Clip); } if (value.Request != null) { value.Request.Dispose(); } } internal static void Clear() { List<string> list = new List<string>(Loaded.Keys); for (int i = 0; i < list.Count; i++) { Evict(list[i]); } InFlight.Clear(); } } internal sealed class SpeakerSource { internal AudioSource Source; internal int Token; internal string LastKey; internal float LastAt; } internal static class VoicePlayer { private static readonly Dictionary<GameObject, SpeakerSource> Voices = new Dictionary<GameObject, SpeakerSource>(); private static readonly List<GameObject> Dead = new List<GameObject>(); private static MonoBehaviour _host; private static AudioSource _source2D; private static int _token2D; private static float _volume2D; private static string _last2DKey; private static float _last2DAt; private static AudioMixerGroup _sfx; private static AudioMixerGroup _gui; internal static void Initialize(MonoBehaviour host) { _host = host; } internal static void PlayAt(GameObject talker, string key, float cullDistance, Vector3 offset) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) if (!VoiceSwitches.IsEnabled(key)) { return; } if ((Object)(object)talker == (Object)null) { Play2D(key, fadeIn: false); return; } PruneDead(); SpeakerSource voice = GetOrCreate(talker); if (voice == null || (voice.LastKey == key && Time.realtimeSinceStartup - voice.LastAt < 0.05f)) { return; } voice.LastKey = key; voice.LastAt = Time.realtimeSinceStartup; Place(((Component)voice.Source).transform, talker.transform, offset); int token = ++voice.Token; voice.Source.Stop(); ClipCache.Request(key, delegate(AudioClip clip) { if (voice.Token == token && !((Object)(object)voice.Source == (Object)null)) { ConfigureSpatial(voice.Source, cullDistance); voice.Source.clip = clip; voice.Source.volume = VoiceoverPlugin.Volume.Value; voice.Source.Play(); } }); } internal static void Play2D(string key, bool fadeIn) { if (!VoiceSwitches.IsEnabled(key)) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (_last2DKey == key && realtimeSinceStartup - _last2DAt < 0.05f) { return; } _last2DKey = key; _last2DAt = realtimeSinceStartup; AudioSource source = GetOrCreate2D(); if ((Object)(object)source == (Object)null) { return; } int token = ++_token2D; source.Stop(); ClipCache.Request(key, delegate(AudioClip clip) { if (_token2D == token && !((Object)(object)source == (Object)null)) { source.spatialBlend = 0f; source.bypassReverbZones = true; source.reverbZoneMix = 0f; source.clip = clip; source.outputAudioMixerGroup = GetMixerGroup("GUI") ?? GetMixerGroup("SFX"); float value = VoiceoverPlugin.Volume.Value; float num = (fadeIn ? VoiceoverPlugin.DreamFadeIn.Value : 0f); _volume2D = ((num > 0f) ? 0f : value); Apply2DVolume(); source.Play(); if (num > 0f && (Object)(object)_host != (Object)null) { _host.StartCoroutine(FadeIn(source, value, num, token)); } } }); } internal static void Stop(GameObject talker) { if (!((Object)(object)talker == (Object)null) && Voices.TryGetValue(talker, out var value)) { value.Token++; if ((Object)(object)value.Source != (Object)null) { value.Source.Stop(); } } } internal static void StopAll() { foreach (KeyValuePair<GameObject, SpeakerSource> voice in Voices) { voice.Value.Token++; if ((Object)(object)voice.Value.Source != (Object)null) { voice.Value.Source.Stop(); } } } internal static void Stop2D() { _token2D++; if ((Object)(object)_source2D != (Object)null) { _source2D.Stop(); } } internal static void Tick() { if ((Object)(object)_source2D != (Object)null && _source2D.isPlaying) { Apply2DVolume(); } } internal static bool IsInUse(AudioClip clip) { if ((Object)(object)clip == (Object)null) { return false; } if ((Object)(object)_source2D != (Object)null && _source2D.isPlaying && (Object)(object)_source2D.clip == (Object)(object)clip) { return true; } foreach (KeyValuePair<GameObject, SpeakerSource> voice in Voices) { if ((Object)(object)voice.Value.Source != (Object)null && voice.Value.Source.isPlaying && (Object)(object)voice.Value.Source.clip == (Object)(object)clip) { return true; } } return false; } private static IEnumerator FadeIn(AudioSource source, float target, float duration, int token) { float elapsed = 0f; while (true) { if (elapsed < duration) { if (!((Object)(object)source == (Object)null) && _token2D == token) { elapsed += Time.unscaledDeltaTime; _volume2D = Mathf.Lerp(0f, target, elapsed / duration); Apply2DVolume(); yield return null; continue; } break; } if ((Object)(object)source != (Object)null && _token2D == token) { _volume2D = target; Apply2DVolume(); } break; } } private static void Apply2DVolume() { if ((Object)(object)_source2D != (Object)null) { _source2D.volume = _volume2D * (ReflectionGame.MenuVisible() ? 0.178f : 1f); } } private static SpeakerSource GetOrCreate(GameObject talker) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (Voices.TryGetValue(talker, out var value) && (Object)(object)value.Source != (Object)null) { return value; } GameObject val = new GameObject("RussianVoice_Speaker"); val.transform.SetParent(talker.transform, false); val.transform.localPosition = Vector3.zero; AudioSource val2 = val.AddComponent<AudioSource>(); val2.playOnAwake = false; val2.loop = false; val2.priority = 64; value = new SpeakerSource(); value.Source = val2; value.Token = 0; Voices[talker] = value; return value; } private static AudioSource GetOrCreate2D() { if ((Object)(object)_source2D != (Object)null) { return _source2D; } if ((Object)(object)_host == (Object)null) { return null; } _source2D = ((Component)_host).gameObject.AddComponent<AudioSource>(); _source2D.playOnAwake = false; _source2D.loop = false; _source2D.spatialBlend = 0f; _source2D.priority = 32; _source2D.bypassReverbZones = true; _source2D.reverbZoneMix = 0f; return _source2D; } private static void Place(Transform source, Transform talker, Vector3 offset) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (offset == Vector3.zero) { source.localPosition = Vector3.zero; } else { source.localPosition = talker.InverseTransformPoint(talker.position + offset); } } private static void ConfigureSpatial(AudioSource source, float cullDistance) { source.spatialBlend = 1f; source.dopplerLevel = 0f; source.rolloffMode = (AudioRolloffMode)0; float num = Mathf.Max(5f, cullDistance); source.minDistance = Mathf.Max(1f, num * 0.5f); source.maxDistance = num; source.outputAudioMixerGroup = GetMixerGroup("SFX"); ApplyReverb(source, num); } private static void ApplyReverb(AudioSource source, float max) { //IL_0042: 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) float value = VoiceoverPlugin.Reverb.Value; if (value <= 0f) { source.bypassReverbZones = true; source.reverbZoneMix = 0f; return; } Camera val = ReflectionGame.MainCamera(); float num = (((Object)(object)val != (Object)null) ? Vector3.Distance(((Component)val).transform.position, ((Component)source).transform.position) : 0f); float num2 = Mathf.Clamp01(num / 64f); float num3 = ((max > 0f) ? (Mathf.Clamp01(max / 64f) * Mathf.Clamp01(num / max)) : 0f); source.bypassReverbZones = false; source.reverbZoneMix = Mathf.Clamp(Mathf.Max(num2, num3) * value, 0f, 1.1f); } private static AudioMixerGroup GetMixerGroup(string name) { if (name == "SFX" && (Object)(object)_sfx != (Object)null) { return _sfx; } if (name == "GUI" && (Object)(object)_gui != (Object)null) { return _gui; } if (name == "GUI") { _gui = ReflectionGame.GuiMixerGroup(); if ((Object)(object)_gui != (Object)null) { return _gui; } } AudioMixer val = ReflectionGame.MasterMixer(); if ((Object)(object)val != (Object)null) { AudioMixerGroup[] array = val.FindMatchingGroups(name); if (array != null) { for (int i = 0; i < array.Length; i++) { if (!((Object)(object)array[i] == (Object)null) && !(((Object)array[i]).name != name)) { if (name == "SFX") { _sfx = array[i]; } else if (name == "GUI") { _gui = array[i]; } return array[i]; } } } } if (name == "SFX") { _sfx = ReflectionGame.FallbackSfxGroup(); return _sfx; } if (name == "GUI") { return GetMixerGroup("SFX"); } return null; } private static void PruneDead() { Dead.Clear(); foreach (KeyValuePair<GameObject, SpeakerSource> voice in Voices) { if ((Object)(object)voice.Key == (Object)null || (Object)(object)voice.Value.Source == (Object)null) { Dead.Add(voice.Key); } } for (int i = 0; i < Dead.Count; i++) { Voices.Remove(Dead[i]); } } } internal static class ReflectionSafe { private const BindingFlags InstanceFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private const BindingFlags StaticFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; internal static Type TypeByName(string name) { if (string.IsNullOrEmpty(name)) { return null; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { try { Type type = assemblies[i].GetType(name, throwOnError: false, ignoreCase: false); if (type != null) { return type; } } catch { } } return null; } internal static FieldInfo Field(Type type, string name, bool isStatic) { BindingFlags bindingAttr = (BindingFlags)((isStatic ? 56 : 52) | 2); Type type2 = type; while (type2 != null) { try { FieldInfo field = type2.GetField(name, bindingAttr); if (field != null) { return field; } } catch { } type2 = type2.BaseType; } return null; } internal static PropertyInfo Property(Type type, string name, bool isStatic) { BindingFlags bindingAttr = (BindingFlags)((isStatic ? 56 : 52) | 2); Type type2 = type; while (type2 != null) { try { PropertyInfo property = type2.GetProperty(name, bindingAttr); if (property != null && property.GetIndexParameters().Length == 0) { return property; } } catch { } type2 = type2.BaseType; } return null; } internal static MethodInfo Method(Type type, string name, bool isStatic) { BindingFlags bindingAttr = (BindingFlags)((isStatic ? 56 : 52) | 2); Type type2 = type; while (type2 != null) { try { MethodInfo[] methods = type2.GetMethods(bindingAttr); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == name && methodInfo.GetParameters().Length == 0) { return methodInfo; } } } catch { } type2 = type2.BaseType; } return null; } internal static bool HasMember(Type type, string name) { if (!(Field(type, name, isStatic: false) != null)) { return Property(type, name, isStatic: false) != null; } return true; } internal static object Read(object instance, string name) { if (instance == null) { return null; } Type type = instance.GetType(); FieldInfo fieldInfo = Field(type, name, isStatic: false); if (fieldInfo != null) { return fieldInfo.GetValue(instance); } PropertyInfo propertyInfo = Property(type, name, isStatic: false); if (propertyInfo != null && propertyInfo.CanRead) { return propertyInfo.GetValue(instance, null); } return null; } internal static object StaticValue(Type type, string name) { if (type == null) { return null; } FieldInfo fieldInfo = Field(type, name, isStatic: true); if (fieldInfo != null) { return fieldInfo.GetValue(null); } PropertyInfo propertyInfo = Property(type, name, isStatic: true); if (propertyInfo != null && propertyInfo.CanRead) { return propertyInfo.GetValue(null, null); } return null; } internal static object Singleton(Type type) { if (type == null) { return null; } string[] array = new string[4] { "instance", "Instance", "m_instance", "s_instance" }; for (int i = 0; i < array.Length; i++) { try { object obj = StaticValue(type, array[i]); if (obj != null && type.IsInstanceOfType(obj)) { return obj; } } catch { } } return null; } } internal static class ReflectionGame { private static Type _menuType; private static MethodInfo _menuVisible; private static Type _audioManType; private static FieldInfo _masterMixer; private static FieldInfo _guiMixer; private static FieldInfo _ambientMixer; private static Type _zsfxType; private static MemberInfo _zsfxInstances; private static Type _utilsType; private static MethodInfo _mainCamera; private static Type _hudType; private static MethodInfo _hudIsUserHidden; private static FieldInfo _hudHidden; private static PropertyInfo _hudHiddenProperty; private static Type _textViewerType; private static MethodInfo _isShowingIntro; internal static bool MenuVisible() { try { if (_menuType == null) { _menuType = ReflectionSafe.TypeByName("Menu"); } if (_menuType == null) { return false; } if (_menuVisible == null) { _menuVisible = ReflectionSafe.Method(_menuType, "IsVisible", isStatic: true); } if (_menuVisible == null) { return false; } object obj = _menuVisible.Invoke(null, null); return obj is bool && (bool)obj; } catch { return false; } } internal static bool HudUserHidden() { try { if (_hudType == null) { _hudType = ReflectionSafe.TypeByName("Hud"); } if (_hudType == null) { return false; } if (_hudIsUserHidden == null) { _hudIsUserHidden = ReflectionSafe.Method(_hudType, "IsUserHidden", isStatic: true); } if (!(_hudIsUserHidden != null) || !(_hudIsUserHidden.Invoke(null, null) is bool result)) { object obj = ReflectionSafe.Singleton(_hudType); if (obj == null) { return false; } if (_hudHidden == null) { _hudHidden = ReflectionSafe.Field(_hudType, "m_userHidden", isStatic: false); } if (_hudHidden != null) { object value = _hudHidden.GetValue(obj); return value is bool && (bool)value; } if (_hudHiddenProperty == null) { _hudHiddenProperty = ReflectionSafe.Property(_hudType, "m_userHidden", isStatic: false); } if (_hudHiddenProperty != null && _hudHiddenProperty.CanRead) { object value2 = _hudHiddenProperty.GetValue(obj, null); return value2 is bool && (bool)value2; } return false; } return result; } catch { return false; } } internal static bool TextViewerShowingIntro() { try { if (_textViewerType == null) { _textViewerType = ReflectionSafe.TypeByName("TextViewer"); } if (_textViewerType == null) { return false; } if (_isShowingIntro == null) { _isShowingIntro = ReflectionSafe.Method(_textViewerType, "IsShowingIntro", isStatic: true); } if (_isShowingIntro == null) { return false; } object obj = _isShowingIntro.Invoke(null, null); return obj is bool && (bool)obj; } catch { return false; } } private static object AudioManInstance() { if (_audioManType == null) { _audioManType = ReflectionSafe.TypeByName("AudioMan"); } return ReflectionSafe.Singleton(_audioManType); } internal static AudioMixer MasterMixer() { try { object obj = AudioManInstance(); if (obj == null) { return null; } if (_masterMixer == null) { _masterMixer = ReflectionSafe.Field(_audioManType, "m_masterMixer", isStatic: false); } return (AudioMixer)((_masterMixer != null) ? /*isinst with value type is only supported in some contexts*/: null); } catch { return null; } } internal static AudioMixerGroup GuiMixerGroup() { try { object obj = AudioManInstance(); if (obj == null) { return null; } if (_guiMixer == null) { _guiMixer = ReflectionSafe.Field(_audioManType, "m_guiMixer", isStatic: false); } return (AudioMixerGroup)((_guiMixer != null) ? /*isinst with value type is only supported in some contexts*/: null); } catch { return null; } } internal static AudioMixerGroup FallbackSfxGroup() { try { if (_zsfxType == null) { _zsfxType = ReflectionSafe.TypeByName("ZSFX"); } if (_zsfxType != null) { if (_zsfxInstances == null) { PropertyInfo propertyInfo = ReflectionSafe.Property(_zsfxType, "Instances", isStatic: true); if (propertyInfo != null) { _zsfxInstances = propertyInfo; } else { FieldInfo fieldInfo = ReflectionSafe.Field(_zsfxType, "Instances", isStatic: true); if (fieldInfo != null) { _zsfxInstances = fieldInfo; } } } object obj = null; PropertyInfo propertyInfo2 = _zsfxInstances as PropertyInfo; FieldInfo fieldInfo2 = _zsfxInstances as FieldInfo; if (propertyInfo2 != null) { obj = propertyInfo2.GetValue(null, null); } else if (fieldInfo2 != null) { obj = fieldInfo2.GetValue(null); } IEnumerable enumerable = obj as IEnumerable; AudioMixerGroup val = null; if (enumerable != null) { foreach (object item in enumerable) { Component val2 = (Component)((item is Component) ? item : null); if ((Object)(object)val2 == (Object)null) { continue; } AudioSource component = val2.GetComponent<AudioSource>(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.outputAudioMixerGroup == (Object)null)) { if (((Object)component.outputAudioMixerGroup).name == "SFX") { return component.outputAudioMixerGroup; } if ((Object)(object)val == (Object)null) { val = component.outputAudioMixerGroup; } } } } if ((Object)(object)val != (Object)null) { return val; } } object obj2 = AudioManInstance(); if (obj2 == null) { return null; } if (_ambientMixer == null) { _ambientMixer = ReflectionSafe.Field(_audioManType, "m_ambientMixer", isStatic: false); } return (AudioMixerGroup)((_ambientMixer != null) ? /*isinst with value type is only supported in some contexts*/: null); } catch { return null; } } internal static Camera MainCamera() { try { if (_utilsType == null) { _utilsType = ReflectionSafe.TypeByName("Utils"); } if (_utilsType != null) { if (_mainCamera == null) { _mainCamera = ReflectionSafe.Method(_utilsType, "GetMainCamera", isStatic: true); } if (_mainCamera != null) { object? obj = _mainCamera.Invoke(null, null); Camera val = (Camera)((obj is Camera) ? obj : null); if ((Object)(object)val != (Object)null) { return val; } } } } catch { } return Camera.main; } } internal static class PatchManager { private enum TargetKind { Any, NoArgs, StringArg, NpcSet, ClearNpc, DreamResult } private static readonly List<string> FailedGroups = new List<string>(); private static readonly MethodInfo NpcPostfix = AccessTools.Method(typeof(GameHooks), "NpcTextPostfix", (Type[])null, (Type[])null); private static readonly MethodInfo ClearNpcPrefix = AccessTools.Method(typeof(GameHooks), "ClearNpcPrefix", (Type[])null, (Type[])null); private static readonly MethodInfo ClearAllPrefix = AccessTools.Method(typeof(GameHooks), "ClearAllPrefix", (Type[])null, (Type[])null); private static readonly MethodInfo DreamPostfix = AccessTools.Method(typeof(GameHooks), "DreamPostfix", (Type[])null, (Type[])null); private static readonly MethodInfo MessagePostfix = AccessTools.Method(typeof(GameHooks), "MessagePostfix", (Type[])null, (Type[])null); private static readonly MethodInfo TextPostfix = AccessTools.Method(typeof(GameHooks), "TextPostfix", (Type[])null, (Type[])null); private static readonly MethodInfo HidePostfix = AccessTools.Method(typeof(GameHooks), "HidePostfix", (Type[])null, (Type[])null); private static readonly MethodInfo HideIntroPostfix = AccessTools.Method(typeof(GameHooks), "HideIntroPostfix", (Type[])null, (Type[])null); internal static int Apply(Harmony harmony) { LogRuntimeIdentity(); FailedGroups.Clear(); int num = 0; if (PatchGroup(harmony, "Chat.SetNpcText", "Chat", "SetNpcText", NpcPostfix, prefix: false, TargetKind.NpcSet)) { num++; } if (PatchGroup(harmony, "Chat.ClearNpcText", "Chat", "ClearNpcText", ClearNpcPrefix, prefix: true, TargetKind.ClearNpc)) { num++; } if (PatchGroup(harmony, "Chat.ClearAllNpcTexts", "Chat", "ClearAllNpcTexts", ClearAllPrefix, prefix: true, TargetKind.NoArgs)) { num++; } if (PatchGroup(harmony, "DreamTexts.GetRandomDreamText", "DreamTexts", "GetRandomDreamText", DreamPostfix, prefix: false, TargetKind.DreamResult)) { num++; } if (PatchGroup(harmony, "MessageHud.ShowMessage", "MessageHud", "ShowMessage", MessagePostfix, prefix: false, TargetKind.StringArg)) { num++; } if (PatchGroup(harmony, "TextViewer.ShowText", "TextViewer", "ShowText", TextPostfix, prefix: false, TargetKind.StringArg)) { num++; } if (PatchGroup(harmony, "TextViewer.Hide", "TextViewer", "Hide", HidePostfix, prefix: false, TargetKind.NoArgs)) { num++; } if (PatchGroup(harmony, "TextViewer.HideIntro", "TextViewer", "HideIntro", HideIntroPostfix, prefix: false, TargetKind.NoArgs)) { num++; } return num; } private static bool PatchGroup(Harmony harmony, string groupName, string typeName, string methodName, MethodInfo hook, bool prefix, TargetKind kind) { bool flag = Patch(harmony, typeName, methodName, hook, prefix, kind); if (!flag) { FailedGroups.Add(groupName); } return flag; } internal static string FailedGroupSummary() { if (FailedGroups.Count == 0) { return "none"; } return string.Join(", ", FailedGroups.ToArray()); } private static void LogRuntimeIdentity() { try { Type type = ReflectionSafe.TypeByName("Chat"); if (!(type == null)) { Assembly assembly = type.Assembly; Guid moduleVersionId = assembly.ManifestModule.ModuleVersionId; VoiceoverPlugin.Log.LogInfo((object)string.Concat("[compat] Valheim dialogue assembly: ", assembly.GetName().Name, ", MVID ", moduleVersionId, ".")); } } catch (Exception ex) { VoiceoverPlugin.Log.LogDebug((object)("[compat] Runtime identity unavailable: " + ex.Message)); } } private static bool Patch(Harmony harmony, string typeName, string methodName, MethodInfo hook, bool prefix, TargetKind kind) { //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Expected O, but got Unknown Type type = ReflectionSafe.TypeByName(typeName); if (type == null) { VoiceoverPlugin.Log.LogError((object)("[patch] type not found: " + typeName + ". Game update may have renamed it.")); return false; } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); List<MethodInfo> list = new List<MethodInfo>(); foreach (MethodInfo methodInfo in methods) { if (!string.Equals(methodInfo.Name, methodName, StringComparison.Ordinal)) { continue; } ParameterInfo[] parameters = methodInfo.GetParameters(); if ((kind == TargetKind.NoArgs && parameters.Length != 0) || (kind == TargetKind.StringArg && !HasType(parameters, typeof(string))) || (kind == TargetKind.NpcSet && (!HasType(parameters, typeof(GameObject)) || !HasType(parameters, typeof(Vector3)) || !HasType(parameters, typeof(string))))) { continue; } if (kind == TargetKind.ClearNpc) { if (parameters.Length != 1) { continue; } Type parameterType = parameters[0].ParameterType; if (!ReflectionSafe.HasMember(parameterType, "m_go")) { continue; } } if (kind != TargetKind.DreamResult || (!(methodInfo.ReturnType == typeof(void)) && ReflectionSafe.HasMember(methodInfo.ReturnType, "m_text"))) { list.Add(methodInfo); } } if (list.Count == 0) { VoiceoverPlugin.Log.LogError((object)("[patch] method not found/compatible: " + typeName + "." + methodName + ".")); return false; } for (int i = 0; i < list.Count; i++) { try { HarmonyMethod val = new HarmonyMethod(hook); if (prefix) { harmony.Patch((MethodBase)list[i], val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { harmony.Patch((MethodBase)list[i], (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } VoiceoverPlugin.Log.LogInfo((object)("[patch] " + Signature(list[i]))); } catch (Exception ex) { VoiceoverPlugin.Log.LogError((object)("[patch] failed " + Signature(list[i]) + ": " + ex.Message)); return false; } } return true; } private static bool HasType(ParameterInfo[] p, Type t) { for (int i = 0; i < p.Length; i++) { if (p[i].ParameterType == t) { return true; } } return false; } private static string Signature(MethodInfo m) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append((m.DeclaringType != null) ? m.DeclaringType.Name : "?").Append('.').Append(m.Name) .Append('('); ParameterInfo[] parameters = m.GetParameters(); for (int i = 0; i < parameters.Length; i++) { if (i > 0) { stringBuilder.Append(", "); } stringBuilder.Append(parameters[i].ParameterType.Name).Append(' ').Append(parameters[i].Name); } return stringBuilder.Append(')').ToString(); } } internal static class GameHooks { public static void NpcTextPostfix(MethodBase __originalMethod, object[] __args) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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) try { if (!ReflectionGame.HudUserHidden()) { GameObject talker = FindArg<GameObject>(__originalMethod, __args, "talker"); Vector3 offset = FindVector(__originalMethod, __args, "offset"); float cullDistance = FindFloat(__originalMethod, __args, "cullDistance", 20f); if (ResolveFromStringArgs(__originalMethod, __args, "text", "Chat.SetNpcText", out var key)) { key = VoiceBank.ForSpeaker(key, talker); VoicePlayer.PlayAt(talker, key, cullDistance, offset); } } } catch (Exception ex) { VoiceoverPlugin.Log.LogError((object)("NPC voice hook failed: " + ex)); } } public static void ClearNpcPrefix(MethodBase __originalMethod, object[] __args) { try { if (__args != null && __args.Length != 0 && __args[0] != null) { object obj = ReadMember(__args[0], "m_go"); GameObject talker = (GameObject)((obj is GameObject) ? obj : null); VoicePlayer.Stop(talker); } } catch (Exception ex) { VoiceoverPlugin.Log.LogDebug((object)("ClearNpc hook: " + ex.Message)); } } public static void ClearAllPrefix() { VoicePlayer.StopAll(); } public static void DreamPostfix(object __result) { try { if (__result != null) { string raw = ReadMember(__result, "m_text") as string; if (VoiceBank.TryResolve(raw, "DreamTexts.GetRandomDreamText", out var key)) { VoicePlayer.Play2D(key, fadeIn: true); } } } catch (Exception ex) { VoiceoverPlugin.Log.LogError((object)("Dream voice hook failed: " + ex)); } } public static void MessagePostfix(MethodBase __originalMethod, object[] __args) { try { if (VoiceoverPlugin.VoiceMessages.Value && ResolveFromStringArgs(__originalMethod, __args, "text", "MessageHud.ShowMessage", out var key)) { VoicePlayer.Play2D(key, fadeIn: false); } } catch (Exception ex) { VoiceoverPlugin.Log.LogError((object)("Message voice hook failed: " + ex)); } } public static void TextPostfix(MethodBase __originalMethod, object[] __args) { try { if (ResolveFromStringArgs(__originalMethod, __args, "textId", "TextViewer.ShowText", out var key)) { VoicePlayer.Play2D(key, fadeIn: false); } } catch (Exception ex) { VoiceoverPlugin.Log.LogError((object)("TextViewer voice hook failed: " + ex)); } } public static void HidePostfix() { if (!ReflectionGame.TextViewerShowingIntro()) { VoicePlayer.Stop2D(); } } public static void HideIntroPostfix() { VoicePlayer.Stop2D(); } private static T FindArg<T>(MethodBase m, object[] args, string preferred) where T : class { object obj = FindNamed(m, args, preferred); if (obj is T result) { return result; } if (args != null) { for (int i = 0; i < args.Length; i++) { if (args[i] is T result2) { return result2; } } } return null; } private static bool ResolveFromStringArgs(MethodBase m, object[] args, string preferred, string source, out string key) { key = null; object obj = FindNamed(m, args, preferred); if (obj is string && VoiceBank.TryResolve((string)obj, source, out key)) { return true; } if (args == null) { return false; } for (int num = args.Length - 1; num >= 0; num--) { if (args[num] is string text && !object.ReferenceEquals(text, obj) && VoiceBank.TryResolve(text, source, out key)) { return true; } } return false; } private static Vector3 FindVector(MethodBase m, object[] args, string preferred) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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) object obj = FindNamed(m, args, preferred); if (obj is Vector3) { return (Vector3)obj; } if (args != null) { for (int i = 0; i < args.Length; i++) { if (args[i] is Vector3) { return (Vector3)args[i]; } } } return Vector3.zero; } private static float FindFloat(MethodBase m, object[] args, string preferred, float fallback) { object obj = FindNamed(m, args, preferred); if (obj is float) { return (float)obj; } ParameterInfo[] array = ((m != null) ? m.GetParameters() : null); if (args != null && array != null) { for (int i = 0; i < array.Length && i < args.Length; i++) { if (array[i].ParameterType == typeof(float) && args[i] is float) { return (float)args[i]; } } } return fallback; } private static object FindNamed(MethodBase m, object[] args, string name) { if (m == null || args == null) { return null; } ParameterInfo[] parameters = m.GetParameters(); for (int i = 0; i < parameters.Length && i < args.Length; i++) { if (string.Equals(parameters[i].Name, name, StringComparison.OrdinalIgnoreCase)) { return args[i]; } } return null; } private static object ReadMember(object obj, string name) { try { return ReflectionSafe.Read(obj, name); } catch { return null; } } } internal static class BogWitchLocalization { internal static void TryRegister() { try { Type type = Type.GetType("Jotunn.Managers.LocalizationManager, Jotunn", throwOnError: false); if (type == null) { VoiceoverPlugin.Log.LogWarning((object)"Jotunn LocalizationManager was not found; Bog Witch Russian voice still works, but custom Russian text was not registered."); return; } PropertyInfo property = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public); object obj = ((property != null) ? property.GetValue(null, null) : null); MethodInfo method = type.GetMethod("GetLocalization", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); object obj2 = ((obj != null && method != null) ? method.Invoke(obj, null) : null); if (obj2 == null) { throw new InvalidOperationException("GetLocalization returned null"); } Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); dictionary["npc_bogwitch_random_buy1"] = "Хм!"; dictionary["npc_bogwitch_random_buy2"] = "Мхм... крх... тьфу."; dictionary["npc_bogwitch_random_buy3"] = "Кхе-кхе!"; dictionary["npc_bogwitch_random_goodbye1"] = "Хмк."; dictionary["npc_bogwitch_random_goodbye2"] = "Хмк-хмк."; dictionary["npc_bogwitch_random_goodbye3"] = "Мг... кр-р!"; dictionary["npc_bogwitch_random_greet1"] = "Кх!"; dictionary["npc_bogwitch_random_greet2"] = "Кхм."; dictionary["npc_bogwitch_random_greet3"] = "Кр-рхм."; dictionary["npc_bogwitch_random_sell1"] = "М-м... хр-р..."; dictionary["npc_bogwitch_random_sell2"] = "Лхм."; dictionary["npc_bogwitch_random_sell3"] = "Кхнг."; dictionary["npc_bogwitch_random_starttrade1"] = "Мг... хм?"; dictionary["npc_bogwitch_random_starttrade2"] = "Пхе... мгг."; dictionary["npc_bogwitch_random_starttrade3"] = "Кх! Мг... мг."; dictionary["npc_bogwitch_random_talk1"] = "Хм-м..."; dictionary["npc_bogwitch_random_talk2"] = "М-мхм..."; dictionary["npc_bogwitch_random_talk3"] = "Нгх..."; MethodInfo[] methods = obj2.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public); MethodInfo methodInfo = null; for (int i = 0; i < methods.Length; i++) { if (!(methods[i].Name != "AddTranslation")) { ParameterInfo[] parameters = methods[i].GetParameters(); if (parameters.Length == 2 && parameters[1].ParameterType.IsAssignableFrom(typeof(Dictionary<string, string>))) { methodInfo = methods[i]; break; } } } if (methodInfo == null) { throw new MissingMethodException("CustomLocalization.AddTranslation(language, Dictionary<string,string>)"); } methodInfo.Invoke(obj2, new object[2] { "Russian", dictionary }); VoiceoverPlugin.Log.LogInfo((object)"Registered 18 Russian Bog Witch text lines through Jotunn."); } catch (Exception ex) { VoiceoverPlugin.Log.LogWarning((object)("Bog Witch Russian text registration failed: " + ex.Message)); } } }