Decompiled source of Valheim Russian Voiceover v1.0.4

BepInEx/plugins/SwedishHouse-Valheim_Russian_Voiceover/ValheimRussianVoiceover.dll

Decompiled 3 weeks ago
using 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));
		}
	}
}