Decompiled source of SoundReplacer v1.6.0

SoundReplacer.dll

Decompiled 8 minutes ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SoundReplacer.Audio;
using SoundReplacer.Catalog;
using SoundReplacer.Logging;
using SoundReplacer.Shared;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("SoundReplacer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Runtime sound replacement for R.E.P.O. (BepInEx 5 / Harmony)")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+f98919c188327baa412aa3d267715bc6c1166480")]
[assembly: AssemblyProduct("SoundReplacer")]
[assembly: AssemblyTitle("SoundReplacer")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SoundReplacer
{
	[BepInPlugin("com.soundreplacer.repo", "SoundReplacer", "1.6.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.soundreplacer.repo";

		public const string PluginName = "SoundReplacer";

		public const string PluginVersion = "1.6.0";

		private ISoundBackend _backend;

		private ReplacerConfig _config;

		private bool _ready;

		internal static Plugin Instance { get; private set; }

		internal static string PluginDirectory { get; private set; }

		internal static string ConfigFilePath => Path.Combine(PluginDirectory, "config.json");

		internal static string CatalogFilePath => Path.Combine(PluginDirectory, "catalog.json");

		internal static string SoundsDirectory => Path.Combine(PluginDirectory, "Sounds");

		internal static string LogFilePath => Path.Combine(PluginDirectory, "SoundReplacer.log");

		internal SoundReplacementManager Replacements { get; private set; }

		internal CatalogCollector Catalog { get; private set; }

		private void Awake()
		{
			Instance = this;
			try
			{
				Initialize();
			}
			catch (Exception ex)
			{
				_ready = false;
				ModLog.Error("инициализация SoundReplacer провалилась, мод отключён: " + ex.Message, ex);
				((BaseUnityPlugin)this).Logger.LogError((object)("[SoundReplacer] " + ex));
			}
		}

		private void Initialize()
		{
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			string text = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
			if (string.IsNullOrEmpty(text))
			{
				text = Paths.PluginPath;
			}
			PluginDirectory = text;
			Directory.CreateDirectory(PluginDirectory);
			Directory.CreateDirectory(SoundsDirectory);
			ModLog.Initialize(LogFilePath, ((BaseUnityPlugin)this).Logger);
			ModLog.Info("SoundReplacer 1.6.0 стартует из " + PluginDirectory);
			ModLog.Info("игра " + Application.version + ", Unity " + Application.unityVersion + ", платформа " + ((object)Application.platform/*cast due to .constrained prefix*/).ToString());
			if (SoundApiProbe.IsLoaded(out var details))
			{
				ModLog.Info("в процессе найдена SoundAPI-сборка (" + details + "), используется собственный Harmony-бэкенд");
			}
			_config = LoadConfig();
			Replacements = new SoundReplacementManager(_config, PluginDirectory);
			_backend = new HarmonyAudioBackend();
			if (!_backend.Install(Replacements))
			{
				ModLog.Error("Harmony-бэкенд не установлен, звуки останутся оригинальными");
			}
			Catalog = new CatalogCollector(CatalogFilePath, _config.ExportCatalog);
			Catalog.ScanLoadedClips();
			SceneManager.sceneLoaded += OnSceneLoaded;
			_ready = true;
			((MonoBehaviour)this).StartCoroutine(StartupRoutine());
		}

		private IEnumerator StartupRoutine()
		{
			yield return null;
			yield return null;
			if (_config != null)
			{
				if (!_config.Enabled)
				{
					ModLog.Warn("замена звуков отключена в config.json (enabled = false)");
					yield break;
				}
				yield return Replacements.LoadAllCoroutine();
				ModLog.Info("активных замен: " + Replacements.Count);
				Catalog.WriteNow();
			}
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			if (_ready)
			{
				Catalog.ScanLoadedClips();
			}
		}

		private void Update()
		{
			if (_ready)
			{
				Catalog.Tick();
			}
		}

		private void OnApplicationQuit()
		{
			if (_ready)
			{
				Catalog.WriteNow();
			}
		}

		private void OnDestroy()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
			try
			{
				if (Catalog != null)
				{
					Catalog.WriteNow();
				}
			}
			catch (Exception ex)
			{
				ModLog.Warn("не удалось сохранить catalog.json при выходе: " + ex.Message);
			}
			if (_backend != null)
			{
				_backend.Uninstall();
			}
			if (Replacements != null)
			{
				Replacements.UnloadAll();
			}
			ModLog.Info("SoundReplacer выгружен");
			ModLog.Shutdown();
		}

		private ReplacerConfig LoadConfig()
		{
			string configFilePath = ConfigFilePath;
			if (!File.Exists(configFilePath))
			{
				ModLog.Warn("config.json не найден: " + configFilePath);
				ModLog.Warn("пока звуки оригинальные. Запустите SoundReplacer.App, выберите звуки и нажмите «Применить».");
				return new ReplacerConfig();
			}
			if (!ReplacerConfig.TryLoad(configFilePath, out var config, out var error))
			{
				ModLog.Error("config.json не прочитан: " + error);
				ModLog.Error("играют оригинальные звуки; исправьте файл и перезапустите игру");
				return new ReplacerConfig();
			}
			ModLog.Info("конфиг загружен: " + config.Describe());
			return config;
		}
	}
	internal static class SoundApiProbe
	{
		private static readonly string[] Markers = new string[3] { "SoundAPI", "SoundApi", "Loaforc" };

		internal static bool IsLoaded(out string details)
		{
			List<string> list = new List<string>();
			try
			{
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				for (int i = 0; i < assemblies.Length; i++)
				{
					string name = assemblies[i].GetName().Name;
					if (string.IsNullOrEmpty(name))
					{
						continue;
					}
					for (int j = 0; j < Markers.Length; j++)
					{
						if (name.IndexOf(Markers[j], StringComparison.OrdinalIgnoreCase) >= 0)
						{
							list.Add(name);
							break;
						}
					}
				}
			}
			catch (Exception ex)
			{
				ModLog.Debug("SoundAPI probe: " + ex.Message);
			}
			details = ((list.Count == 0) ? null : string.Join(", ", list.ToArray()));
			return list.Count > 0;
		}
	}
}
namespace SoundReplacer.Shared
{
	public static class Json
	{
		private sealed class Parser
		{
			private readonly string _text;

			private int _position;

			internal bool AtEnd => _position >= _text.Length;

			internal Parser(string text)
			{
				_text = text;
			}

			internal FormatException Error(string message)
			{
				return new FormatException("JSON, позиция " + _position + ": " + message);
			}

			internal void SkipWhitespace()
			{
				while (_position < _text.Length)
				{
					char c = _text[_position];
					if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
					{
						_position++;
						continue;
					}
					break;
				}
			}

			internal object ParseValue(int depth)
			{
				if (depth > 64)
				{
					throw Error("слишком большая вложенность");
				}
				SkipWhitespace();
				if (AtEnd)
				{
					throw Error("неожиданный конец документа");
				}
				char c = _text[_position];
				switch (c)
				{
				case '{':
					return ParseObject(depth);
				case '[':
					return ParseArray(depth);
				case '"':
					return ParseString();
				case 't':
					ExpectLiteral("true");
					return true;
				case 'f':
					ExpectLiteral("false");
					return false;
				case 'n':
					ExpectLiteral("null");
					return null;
				default:
					switch (c)
					{
					case '-':
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						return ParseNumber();
					default:
						throw Error("неожиданный символ '" + c + "'");
					}
				}
			}

			private void ExpectLiteral(string literal)
			{
				if (_position + literal.Length > _text.Length || string.CompareOrdinal(_text, _position, literal, 0, literal.Length) != 0)
				{
					throw Error("ожидалось '" + literal + "'");
				}
				_position += literal.Length;
			}

			private IDictionary<string, object> ParseObject(int depth)
			{
				_position++;
				Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
				SkipWhitespace();
				if (AtEnd)
				{
					throw Error("незакрытый объект");
				}
				if (_text[_position] == '}')
				{
					_position++;
					return dictionary;
				}
				while (true)
				{
					SkipWhitespace();
					if (AtEnd)
					{
						throw Error("незакрытый объект");
					}
					if (_text[_position] != '"')
					{
						throw Error("ожидалось имя свойства в кавычках");
					}
					string key = ParseString();
					SkipWhitespace();
					if (AtEnd || _text[_position] != ':')
					{
						throw Error("ожидалось ':'");
					}
					_position++;
					object value = ParseValue(depth + 1);
					dictionary[key] = value;
					SkipWhitespace();
					if (AtEnd)
					{
						break;
					}
					switch (_text[_position++])
					{
					case ',':
						break;
					case '}':
						return dictionary;
					default:
						throw Error("ожидалось ',' или '}'");
					}
				}
				throw Error("незакрытый объект");
			}

			private IList<object> ParseArray(int depth)
			{
				_position++;
				List<object> list = new List<object>();
				SkipWhitespace();
				if (AtEnd)
				{
					throw Error("незакрытый массив");
				}
				if (_text[_position] == ']')
				{
					_position++;
					return list;
				}
				while (true)
				{
					object item = ParseValue(depth + 1);
					list.Add(item);
					SkipWhitespace();
					if (AtEnd)
					{
						break;
					}
					switch (_text[_position++])
					{
					case ',':
						break;
					case ']':
						return list;
					default:
						throw Error("ожидалось ',' или ']'");
					}
				}
				throw Error("незакрытый массив");
			}

			private string ParseString()
			{
				_position++;
				StringBuilder stringBuilder = new StringBuilder(32);
				while (!AtEnd)
				{
					char c = _text[_position++];
					switch (c)
					{
					case '"':
						return stringBuilder.ToString();
					default:
						stringBuilder.Append(c);
						break;
					case '\\':
					{
						if (AtEnd)
						{
							throw Error("незакрытая escape-последовательность");
						}
						char c2 = _text[_position++];
						switch (c2)
						{
						case '"':
							stringBuilder.Append('"');
							break;
						case '\\':
							stringBuilder.Append('\\');
							break;
						case '/':
							stringBuilder.Append('/');
							break;
						case 'b':
							stringBuilder.Append('\b');
							break;
						case 'f':
							stringBuilder.Append('\f');
							break;
						case 'n':
							stringBuilder.Append('\n');
							break;
						case 'r':
							stringBuilder.Append('\r');
							break;
						case 't':
							stringBuilder.Append('\t');
							break;
						case 'u':
						{
							if (_position + 4 > _text.Length)
							{
								throw Error("некорректный \\u escape");
							}
							int num = 0;
							for (int i = 0; i < 4; i++)
							{
								int num2 = HexValue(_text[_position++]);
								if (num2 < 0)
								{
									throw Error("некорректный \\u escape");
								}
								num = (num << 4) | num2;
							}
							stringBuilder.Append((char)num);
							break;
						}
						default:
							throw Error("недопустимый escape '\\" + c2 + "'");
						}
						break;
					}
					}
				}
				throw Error("незакрытая строка");
			}

			private object ParseNumber()
			{
				int position = _position;
				if (_text[_position] == '-')
				{
					_position++;
				}
				while (_position < _text.Length && char.IsDigit(_text[_position]))
				{
					_position++;
				}
				if (_position < _text.Length && _text[_position] == '.')
				{
					_position++;
					while (_position < _text.Length && char.IsDigit(_text[_position]))
					{
						_position++;
					}
				}
				if (_position < _text.Length && (_text[_position] == 'e' || _text[_position] == 'E'))
				{
					_position++;
					if (_position < _text.Length && (_text[_position] == '+' || _text[_position] == '-'))
					{
						_position++;
					}
					while (_position < _text.Length && char.IsDigit(_text[_position]))
					{
						_position++;
					}
				}
				string text = _text.Substring(position, _position - position);
				if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					throw Error("некорректное число '" + text + "'");
				}
				return result;
			}

			private static int HexValue(char c)
			{
				if (c >= '0' && c <= '9')
				{
					return c - 48;
				}
				if (c >= 'a' && c <= 'f')
				{
					return c - 97 + 10;
				}
				if (c >= 'A' && c <= 'F')
				{
					return c - 65 + 10;
				}
				return -1;
			}
		}

		private const int MaxDepth = 64;

		public static object Parse(string text)
		{
			if (!TryParse(text, out var value, out var error))
			{
				throw new FormatException(error);
			}
			return value;
		}

		public static bool TryParse(string text, out object value, out string error)
		{
			value = null;
			error = null;
			if (text == null)
			{
				error = "JSON: пустой ввод (null)";
				return false;
			}
			try
			{
				Parser parser = new Parser(text);
				value = parser.ParseValue(0);
				parser.SkipWhitespace();
				if (!parser.AtEnd)
				{
					throw parser.Error("лишние символы после конца документа");
				}
				return true;
			}
			catch (FormatException ex)
			{
				error = ex.Message;
				return false;
			}
		}

		public static string Serialize(object value, bool pretty = true)
		{
			StringBuilder stringBuilder = new StringBuilder(512);
			WriteValue(stringBuilder, value, pretty, 0);
			if (pretty)
			{
				stringBuilder.Append('\n');
			}
			return stringBuilder.ToString();
		}

		public static IDictionary<string, object> AsObject(object value)
		{
			return value as IDictionary<string, object>;
		}

		public static IList<object> AsArray(object value)
		{
			return value as IList<object>;
		}

		public static object Get(IDictionary<string, object> obj, string key)
		{
			if (obj != null && obj.TryGetValue(key, out var value))
			{
				return value;
			}
			return null;
		}

		public static string GetString(IDictionary<string, object> obj, string key, string fallback = null)
		{
			return (Get(obj, key) as string) ?? fallback;
		}

		public static bool GetBool(IDictionary<string, object> obj, string key, bool fallback)
		{
			object obj2 = Get(obj, key);
			if (obj2 is bool)
			{
				return (bool)obj2;
			}
			if (obj2 is string && bool.TryParse((string)obj2, out var result))
			{
				return result;
			}
			return fallback;
		}

		public static int GetInt(IDictionary<string, object> obj, string key, int fallback)
		{
			object obj2 = Get(obj, key);
			if (obj2 == null)
			{
				return fallback;
			}
			try
			{
				return Convert.ToInt32(obj2, CultureInfo.InvariantCulture);
			}
			catch
			{
				return fallback;
			}
		}

		public static double GetDouble(IDictionary<string, object> obj, string key, double fallback)
		{
			object obj2 = Get(obj, key);
			if (obj2 == null)
			{
				return fallback;
			}
			try
			{
				return Convert.ToDouble(obj2, CultureInfo.InvariantCulture);
			}
			catch
			{
				return fallback;
			}
		}

		private static void WriteValue(StringBuilder sb, object value, bool pretty, int depth)
		{
			if (value == null)
			{
				sb.Append("null");
			}
			else if (value is string)
			{
				WriteString(sb, (string)value);
			}
			else if (value is bool)
			{
				sb.Append(((bool)value) ? "true" : "false");
			}
			else if (value is char)
			{
				WriteString(sb, value.ToString());
			}
			else if (value is IDictionary<string, object> dict)
			{
				WriteObject(sb, dict, pretty, depth);
			}
			else if (value is IEnumerable items)
			{
				WriteArray(sb, items, pretty, depth);
			}
			else if (IsNumber(value))
			{
				WriteNumber(sb, value);
			}
			else
			{
				WriteString(sb, Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty);
			}
		}

		private static bool IsNumber(object value)
		{
			if (!(value is sbyte) && !(value is byte) && !(value is short) && !(value is ushort) && !(value is int) && !(value is uint) && !(value is long) && !(value is ulong) && !(value is float) && !(value is double))
			{
				return value is decimal;
			}
			return true;
		}

		private static void WriteNumber(StringBuilder sb, object value)
		{
			double num = Convert.ToDouble(value, CultureInfo.InvariantCulture);
			if (double.IsNaN(num) || double.IsInfinity(num))
			{
				sb.Append("null");
			}
			else if (num == Math.Floor(num) && Math.Abs(num) < 9000000000000000.0)
			{
				sb.Append(((long)num).ToString(CultureInfo.InvariantCulture));
			}
			else
			{
				sb.Append(num.ToString("R", CultureInfo.InvariantCulture));
			}
		}

		private static void WriteObject(StringBuilder sb, IDictionary<string, object> dict, bool pretty, int depth)
		{
			if (dict.Count == 0)
			{
				sb.Append("{}");
				return;
			}
			sb.Append('{');
			bool flag = true;
			foreach (KeyValuePair<string, object> item in dict)
			{
				if (!flag)
				{
					sb.Append(',');
				}
				flag = false;
				if (pretty)
				{
					sb.Append('\n');
					AppendIndent(sb, depth + 1);
				}
				WriteString(sb, item.Key);
				sb.Append(pretty ? ": " : ":");
				WriteValue(sb, item.Value, pretty, depth + 1);
			}
			if (pretty)
			{
				sb.Append('\n');
				AppendIndent(sb, depth);
			}
			sb.Append('}');
		}

		private static void WriteArray(StringBuilder sb, IEnumerable items, bool pretty, int depth)
		{
			StringBuilder stringBuilder = new StringBuilder();
			int num = 0;
			foreach (object item in items)
			{
				if (num > 0)
				{
					stringBuilder.Append(',');
				}
				if (pretty)
				{
					stringBuilder.Append('\n');
					AppendIndent(stringBuilder, depth + 1);
				}
				WriteValue(stringBuilder, item, pretty, depth + 1);
				num++;
			}
			if (num == 0)
			{
				sb.Append("[]");
				return;
			}
			sb.Append('[');
			sb.Append(stringBuilder);
			if (pretty)
			{
				sb.Append('\n');
				AppendIndent(sb, depth);
			}
			sb.Append(']');
		}

		private static void AppendIndent(StringBuilder sb, int depth)
		{
			sb.Append(' ', depth * 2);
		}

		private static void WriteString(StringBuilder sb, string value)
		{
			sb.Append('"');
			foreach (char c in value)
			{
				switch (c)
				{
				case '"':
					sb.Append("\\\"");
					continue;
				case '\\':
					sb.Append("\\\\");
					continue;
				case '\b':
					sb.Append("\\b");
					continue;
				case '\f':
					sb.Append("\\f");
					continue;
				case '\n':
					sb.Append("\\n");
					continue;
				case '\r':
					sb.Append("\\r");
					continue;
				case '\t':
					sb.Append("\\t");
					continue;
				}
				if (c < ' ')
				{
					sb.Append("\\u");
					int num = c;
					sb.Append(num.ToString("x4", CultureInfo.InvariantCulture));
				}
				else
				{
					sb.Append(c);
				}
			}
			sb.Append('"');
		}
	}
	public sealed class ReplacerConfig
	{
		public const int CurrentVersion = 1;

		public const string FileName = "config.json";

		public int Version { get; set; }

		public bool Enabled { get; set; }

		public bool PatchClipSetter { get; set; }

		public bool ExportCatalog { get; set; }

		public bool MatchLength { get; set; }

		public bool MatchVolume { get; set; }

		public bool MatchChannels { get; set; }

		public bool LogPlays { get; set; }

		public List<string> NoTrim { get; private set; }

		public bool OncePerFile { get; set; }

		public double OnceWindowSeconds { get; set; }

		public double FadeOutSeconds { get; set; }

		public List<string> Spatial { get; private set; }

		public double MinDistance { get; set; }

		public double MaxDistance { get; set; }

		public double TrackVolume { get; set; }

		public bool Echo { get; set; }

		public double EchoDelayMs { get; set; }

		public double EchoDecayRatio { get; set; }

		public double EchoWetMix { get; set; }

		public double EchoDryMix { get; set; }

		public Dictionary<string, string> Sounds { get; private set; }

		public ReplacerConfig()
		{
			Version = 1;
			Enabled = true;
			PatchClipSetter = true;
			ExportCatalog = true;
			MatchLength = true;
			MatchVolume = true;
			MatchChannels = true;
			LogPlays = false;
			NoTrim = new List<string>();
			OncePerFile = false;
			OnceWindowSeconds = 2.0;
			FadeOutSeconds = 0.0;
			Spatial = new List<string>();
			MinDistance = 3.0;
			MaxDistance = 40.0;
			TrackVolume = 1.0;
			Echo = false;
			EchoDelayMs = 500.0;
			EchoDecayRatio = 0.5;
			EchoWetMix = 0.5;
			EchoDryMix = 1.0;
			Sounds = new Dictionary<string, string>(StringComparer.Ordinal);
		}

		public string ToJson()
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
			dictionary["version"] = Version;
			dictionary["enabled"] = Enabled;
			dictionary["patchClipSetter"] = PatchClipSetter;
			dictionary["exportCatalog"] = ExportCatalog;
			dictionary["matchLength"] = MatchLength;
			dictionary["matchVolume"] = MatchVolume;
			dictionary["matchChannels"] = MatchChannels;
			dictionary["logPlays"] = LogPlays;
			dictionary["oncePerFile"] = OncePerFile;
			dictionary["onceWindowSeconds"] = OnceWindowSeconds;
			dictionary["fadeOutSeconds"] = FadeOutSeconds;
			dictionary["spatial"] = new List<object>(Spatial.Count);
			foreach (string item in Spatial)
			{
				((List<object>)dictionary["spatial"]).Add(item);
			}
			dictionary["minDistance"] = MinDistance;
			dictionary["maxDistance"] = MaxDistance;
			dictionary["trackVolume"] = TrackVolume;
			dictionary["echo"] = Echo;
			dictionary["echoDelayMs"] = EchoDelayMs;
			dictionary["echoDecayRatio"] = EchoDecayRatio;
			dictionary["echoWetMix"] = EchoWetMix;
			dictionary["echoDryMix"] = EchoDryMix;
			List<object> list = new List<object>(NoTrim.Count);
			foreach (string item2 in NoTrim)
			{
				list.Add(item2);
			}
			dictionary["noTrim"] = list;
			Dictionary<string, object> dictionary2 = new Dictionary<string, object>(StringComparer.Ordinal);
			foreach (KeyValuePair<string, string> item3 in Sounds.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> p) => p.Key, StringComparer.Ordinal))
			{
				dictionary2[item3.Key] = item3.Value;
			}
			dictionary["sounds"] = dictionary2;
			return Json.Serialize(dictionary);
		}

		public static ReplacerConfig FromJson(string json, out string error)
		{
			error = null;
			if (!Json.TryParse(json, out var value, out var error2))
			{
				error = error2;
				return null;
			}
			IDictionary<string, object> dictionary = Json.AsObject(value);
			if (dictionary == null)
			{
				error = "корень config.json должен быть JSON-объектом";
				return null;
			}
			ReplacerConfig replacerConfig = new ReplacerConfig();
			replacerConfig.Version = Json.GetInt(dictionary, "version", 1);
			replacerConfig.Enabled = Json.GetBool(dictionary, "enabled", fallback: true);
			replacerConfig.PatchClipSetter = Json.GetBool(dictionary, "patchClipSetter", fallback: true);
			replacerConfig.ExportCatalog = Json.GetBool(dictionary, "exportCatalog", fallback: true);
			replacerConfig.MatchLength = Json.GetBool(dictionary, "matchLength", fallback: true);
			replacerConfig.MatchVolume = Json.GetBool(dictionary, "matchVolume", fallback: true);
			replacerConfig.MatchChannels = Json.GetBool(dictionary, "matchChannels", fallback: true);
			replacerConfig.LogPlays = Json.GetBool(dictionary, "logPlays", fallback: false);
			replacerConfig.OncePerFile = Json.GetBool(dictionary, "oncePerFile", fallback: false);
			replacerConfig.OnceWindowSeconds = Json.GetDouble(dictionary, "onceWindowSeconds", 2.0);
			replacerConfig.FadeOutSeconds = Json.GetDouble(dictionary, "fadeOutSeconds", 0.0);
			replacerConfig.MinDistance = Json.GetDouble(dictionary, "minDistance", 3.0);
			replacerConfig.MaxDistance = Json.GetDouble(dictionary, "maxDistance", 40.0);
			replacerConfig.TrackVolume = Json.GetDouble(dictionary, "trackVolume", 1.0);
			replacerConfig.Echo = Json.GetBool(dictionary, "echo", fallback: false);
			replacerConfig.EchoDelayMs = Json.GetDouble(dictionary, "echoDelayMs", 500.0);
			replacerConfig.EchoDecayRatio = Json.GetDouble(dictionary, "echoDecayRatio", 0.5);
			replacerConfig.EchoWetMix = Json.GetDouble(dictionary, "echoWetMix", 0.5);
			replacerConfig.EchoDryMix = Json.GetDouble(dictionary, "echoDryMix", 1.0);
			IList<object> list = Json.AsArray(Json.Get(dictionary, "noTrim"));
			if (list != null)
			{
				foreach (object item in list)
				{
					string text = item as string;
					if (!string.IsNullOrEmpty(text))
					{
						replacerConfig.NoTrim.Add(text.Trim());
					}
				}
			}
			IList<object> list2 = Json.AsArray(Json.Get(dictionary, "spatial"));
			if (list2 != null)
			{
				foreach (object item2 in list2)
				{
					string text2 = item2 as string;
					if (!string.IsNullOrEmpty(text2))
					{
						replacerConfig.Spatial.Add(text2.Trim());
					}
				}
			}
			object obj = Json.Get(dictionary, "sounds");
			if (obj != null)
			{
				IDictionary<string, object> dictionary2 = Json.AsObject(obj);
				if (dictionary2 == null)
				{
					error = "поле \"sounds\" должно быть объектом { \"имя\": \"путь\" }";
					return null;
				}
				foreach (KeyValuePair<string, object> item3 in dictionary2)
				{
					string text3 = ((item3.Key == null) ? null : item3.Key.Trim());
					string text4 = item3.Value as string;
					if (!string.IsNullOrEmpty(text3) && !string.IsNullOrEmpty(text4))
					{
						replacerConfig.Sounds[text3] = text4.Trim();
					}
				}
			}
			if (replacerConfig.Version > 1)
			{
				error = null;
			}
			return replacerConfig;
		}

		public static bool TryLoad(string path, out ReplacerConfig config, out string error)
		{
			config = null;
			error = null;
			if (!File.Exists(path))
			{
				error = "файл не найден: " + path;
				return false;
			}
			try
			{
				string json = File.ReadAllText(path, Encoding.UTF8);
				config = FromJson(json, out error);
				return config != null;
			}
			catch (Exception ex)
			{
				error = ex.Message;
				return false;
			}
		}

		public bool Save(string path, out string error)
		{
			error = null;
			try
			{
				string directoryName = Path.GetDirectoryName(path);
				if (!string.IsNullOrEmpty(directoryName))
				{
					Directory.CreateDirectory(directoryName);
				}
				string text = path + ".tmp";
				File.WriteAllText(text, ToJson(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				if (File.Exists(path))
				{
					try
					{
						File.Replace(text, path, null);
					}
					catch (Exception)
					{
						File.Delete(path);
						File.Move(text, path);
					}
				}
				else
				{
					File.Move(text, path);
				}
				return true;
			}
			catch (Exception ex2)
			{
				error = ex2.Message;
				return false;
			}
		}

		public static bool TryResolvePath(string pluginDirectory, string configValue, out string absolutePath, out string error)
		{
			absolutePath = null;
			error = null;
			if (string.IsNullOrEmpty(configValue))
			{
				error = "пустой путь";
				return false;
			}
			try
			{
				string text = configValue.Trim().Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar);
				absolutePath = (Path.IsPathRooted(text) ? Path.GetFullPath(text) : Path.GetFullPath(Path.Combine(pluginDirectory, text)));
				if (!File.Exists(absolutePath))
				{
					error = "файл не найден: " + absolutePath;
					return false;
				}
				return true;
			}
			catch (Exception ex)
			{
				error = ex.Message;
				return false;
			}
		}

		public string Describe()
		{
			return string.Format(CultureInfo.InvariantCulture, "version={0}, enabled={1}, patchClipSetter={2}, matchLength={3}, matchVolume={4}, matchChannels={5}, logPlays={6}, noTrim={7}, oncePerFile={8}, fadeOut={9}, spatial={10}, sounds={11}", Version, Enabled, PatchClipSetter, MatchLength, MatchVolume, MatchChannels, LogPlays, NoTrim.Count, OncePerFile, FadeOutSeconds, Spatial.Count, Sounds.Count);
		}
	}
	public sealed class SoundCatalog
	{
		public const int CurrentVersion = 1;

		public const string FileName = "catalog.json";

		public int Version { get; set; }

		public string GeneratedUtc { get; set; }

		public string GameVersion { get; set; }

		public string UnityVersion { get; set; }

		public string ModVersion { get; set; }

		public List<SoundCatalogEntry> Sounds { get; private set; }

		public SoundCatalog()
		{
			Version = 1;
			Sounds = new List<SoundCatalogEntry>();
		}

		public string ToJson()
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
			dictionary["version"] = Version;
			dictionary["generatedUtc"] = GeneratedUtc ?? DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture);
			dictionary["gameVersion"] = GameVersion ?? string.Empty;
			dictionary["unityVersion"] = UnityVersion ?? string.Empty;
			dictionary["modVersion"] = ModVersion ?? string.Empty;
			List<object> list = new List<object>(Sounds.Count);
			foreach (SoundCatalogEntry sound in Sounds)
			{
				Dictionary<string, object> dictionary2 = new Dictionary<string, object>(StringComparer.Ordinal);
				dictionary2["name"] = sound.Name;
				dictionary2["plays"] = sound.Plays;
				dictionary2["length"] = sound.Length;
				dictionary2["channels"] = sound.Channels;
				dictionary2["lastSeenUtc"] = sound.LastSeenUtc ?? string.Empty;
				list.Add(dictionary2);
			}
			dictionary["sounds"] = list;
			return Json.Serialize(dictionary);
		}

		public static SoundCatalog FromJson(string json, out string error)
		{
			error = null;
			if (!Json.TryParse(json, out var value, out var error2))
			{
				error = error2;
				return null;
			}
			IDictionary<string, object> dictionary = Json.AsObject(value);
			if (dictionary == null)
			{
				error = "корень catalog.json должен быть JSON-объектом";
				return null;
			}
			SoundCatalog soundCatalog = new SoundCatalog();
			soundCatalog.Version = Json.GetInt(dictionary, "version", 1);
			soundCatalog.GeneratedUtc = Json.GetString(dictionary, "generatedUtc", string.Empty);
			soundCatalog.GameVersion = Json.GetString(dictionary, "gameVersion", string.Empty);
			soundCatalog.UnityVersion = Json.GetString(dictionary, "unityVersion", string.Empty);
			soundCatalog.ModVersion = Json.GetString(dictionary, "modVersion", string.Empty);
			IList<object> list = Json.AsArray(Json.Get(dictionary, "sounds"));
			if (list != null)
			{
				foreach (object item in list)
				{
					IDictionary<string, object> dictionary2 = Json.AsObject(item);
					if (dictionary2 != null)
					{
						string text = Json.GetString(dictionary2, "name");
						if (!string.IsNullOrEmpty(text))
						{
							soundCatalog.Sounds.Add(new SoundCatalogEntry
							{
								Name = text,
								Plays = Json.GetInt(dictionary2, "plays", 0),
								Length = (float)Json.GetDouble(dictionary2, "length", 0.0),
								Channels = Json.GetInt(dictionary2, "channels", 0),
								LastSeenUtc = Json.GetString(dictionary2, "lastSeenUtc", string.Empty)
							});
						}
					}
				}
			}
			soundCatalog.Sounds = soundCatalog.Sounds.OrderByDescending((SoundCatalogEntry s) => s.Plays).ThenBy<SoundCatalogEntry, string>((SoundCatalogEntry s) => s.Name, StringComparer.OrdinalIgnoreCase).ToList();
			return soundCatalog;
		}

		public static bool TryLoad(string path, out SoundCatalog catalog, out string error)
		{
			catalog = null;
			error = null;
			if (!File.Exists(path))
			{
				error = "файл не найден: " + path;
				return false;
			}
			try
			{
				catalog = FromJson(File.ReadAllText(path, Encoding.UTF8), out error);
				return catalog != null;
			}
			catch (Exception ex)
			{
				error = ex.Message;
				return false;
			}
		}

		public bool Save(string path, out string error)
		{
			error = null;
			try
			{
				string directoryName = Path.GetDirectoryName(path);
				if (!string.IsNullOrEmpty(directoryName))
				{
					Directory.CreateDirectory(directoryName);
				}
				string text = path + ".tmp";
				File.WriteAllText(text, ToJson(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				if (File.Exists(path))
				{
					try
					{
						File.Replace(text, path, null);
					}
					catch (Exception)
					{
						File.Delete(path);
						File.Move(text, path);
					}
				}
				else
				{
					File.Move(text, path);
				}
				return true;
			}
			catch (Exception ex2)
			{
				error = ex2.Message;
				return false;
			}
		}
	}
	public sealed class SoundCatalogEntry
	{
		public string Name { get; set; }

		public int Plays { get; set; }

		public float Length { get; set; }

		public int Channels { get; set; }

		public string LastSeenUtc { get; set; }
	}
}
namespace SoundReplacer.Logging
{
	public enum ModLogLevel
	{
		Debug,
		Info,
		Warn,
		Error
	}
	public static class ModLog
	{
		private const long MaxFileBytes = 4194304L;

		private static readonly object Sync = new object();

		private static string _filePath;

		private static StreamWriter _writer;

		private static ManualLogSource _bepInEx;

		private static ModLogLevel _minimumLevel = ModLogLevel.Info;

		public static void Initialize(string filePath, ManualLogSource bepInExLogger)
		{
			lock (Sync)
			{
				_filePath = filePath;
				_bepInEx = bepInExLogger;
				try
				{
					string directoryName = Path.GetDirectoryName(filePath);
					if (!string.IsNullOrEmpty(directoryName))
					{
						Directory.CreateDirectory(directoryName);
					}
					OpenWriterLocked();
				}
				catch (Exception ex)
				{
					_writer = null;
					if (_bepInEx != null)
					{
						_bepInEx.LogWarning((object)("[SoundReplacer] не удалось открыть SoundReplacer.log: " + ex.Message));
					}
				}
			}
		}

		public static void SetMinimumLevel(ModLogLevel level)
		{
			lock (Sync)
			{
				_minimumLevel = level;
			}
		}

		public static void Debug(string message)
		{
			Write(ModLogLevel.Debug, message, null);
		}

		public static void Info(string message, Exception exception = null)
		{
			Write(ModLogLevel.Info, message, exception);
		}

		public static void Warn(string message, Exception exception = null)
		{
			Write(ModLogLevel.Warn, message, exception);
		}

		public static void Error(string message, Exception exception = null)
		{
			Write(ModLogLevel.Error, message, exception);
		}

		public static void Shutdown()
		{
			lock (Sync)
			{
				try
				{
					if (_writer != null)
					{
						_writer.Flush();
						_writer.Dispose();
					}
				}
				catch
				{
				}
				finally
				{
					_writer = null;
				}
			}
		}

		private static void Write(ModLogLevel level, string message, Exception exception)
		{
			if (string.IsNullOrEmpty(message) && exception == null)
			{
				return;
			}
			string text = string.Format(CultureInfo.InvariantCulture, "{0} [{1,-5}] {2}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture), level.ToString().ToUpperInvariant(), message ?? string.Empty);
			if (exception != null)
			{
				text = text + Environment.NewLine + "    " + exception.GetType().Name + ": " + exception.Message + Environment.NewLine + exception.StackTrace;
			}
			lock (Sync)
			{
				if (level < _minimumLevel)
				{
					return;
				}
				try
				{
					if (_writer == null && !string.IsNullOrEmpty(_filePath))
					{
						OpenWriterLocked();
					}
					if (_writer != null)
					{
						RotateIfNeededLocked();
						_writer.WriteLine(text);
					}
				}
				catch
				{
				}
				WriteToBepInExLocked(level, message ?? string.Empty);
			}
		}

		private static void WriteToBepInExLocked(ModLogLevel level, string message)
		{
			if (_bepInEx == null)
			{
				return;
			}
			try
			{
				switch (level)
				{
				case ModLogLevel.Debug:
					_bepInEx.Log((LogLevel)32, (object)message);
					break;
				case ModLogLevel.Warn:
					_bepInEx.Log((LogLevel)4, (object)message);
					break;
				case ModLogLevel.Error:
					_bepInEx.Log((LogLevel)2, (object)message);
					break;
				default:
					_bepInEx.Log((LogLevel)16, (object)message);
					break;
				}
			}
			catch
			{
			}
		}

		private static void OpenWriterLocked()
		{
			if (_writer != null)
			{
				try
				{
					_writer.Dispose();
				}
				catch
				{
				}
				_writer = null;
			}
			_writer = new StreamWriter(new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false))
			{
				AutoFlush = true
			};
		}

		private static void RotateIfNeededLocked()
		{
			if (_writer == null || _writer.BaseStream.Length < 4194304)
			{
				return;
			}
			try
			{
				_writer.Dispose();
			}
			catch
			{
			}
			_writer = null;
			string text = _filePath + ".1";
			try
			{
				if (File.Exists(text))
				{
					File.Delete(text);
				}
				File.Move(_filePath, text);
			}
			catch
			{
			}
			OpenWriterLocked();
		}
	}
}
namespace SoundReplacer.Catalog
{
	public sealed class CatalogCollector
	{
		private sealed class Entry
		{
			public int Plays;

			public float Length;

			public int Channels;

			public DateTime LastSeenUtc;
		}

		private const int MaxEntries = 20000;

		private readonly string _path;

		private readonly bool _enabled;

		private readonly object _sync = new object();

		private readonly Dictionary<string, Entry> _entries = new Dictionary<string, Entry>(StringComparer.Ordinal);

		private DateTime _nextWriteUtc = DateTime.UtcNow.AddSeconds(5.0);

		private DateTime _nextScanUtc = DateTime.MinValue;

		private bool _dirty;

		private bool _limitLogged;

		public int Count
		{
			get
			{
				lock (_sync)
				{
					return _entries.Count;
				}
			}
		}

		public CatalogCollector(string path, bool enabled)
		{
			_path = path;
			_enabled = enabled;
		}

		public void Observe(AudioClip clip)
		{
			if (!_enabled || (Object)(object)clip == (Object)null)
			{
				return;
			}
			string name = ((Object)clip).name;
			if (string.IsNullOrEmpty(name))
			{
				return;
			}
			lock (_sync)
			{
				if (!_entries.TryGetValue(name, out var value))
				{
					if (_entries.Count >= 20000)
					{
						if (!_limitLogged)
						{
							_limitLogged = true;
							ModLog.Warn("каталог достиг лимита " + 20000 + " имён, новые имена игнорируются");
						}
						return;
					}
					value = new Entry();
					_entries[name] = value;
				}
				value.Plays++;
				value.LastSeenUtc = DateTime.UtcNow;
				if (value.Length <= 0f)
				{
					value.Length = clip.length;
					value.Channels = clip.channels;
				}
				_dirty = true;
			}
		}

		public void ScanLoadedClips()
		{
			if (!_enabled)
			{
				return;
			}
			DateTime utcNow = DateTime.UtcNow;
			if (utcNow < _nextScanUtc)
			{
				return;
			}
			_nextScanUtc = utcNow.AddSeconds(10.0);
			try
			{
				AudioClip[] array = Resources.FindObjectsOfTypeAll<AudioClip>();
				int num = 0;
				AudioClip[] array2 = array;
				foreach (AudioClip val in array2)
				{
					if (!((Object)(object)val == (Object)null))
					{
						string name = ((Object)val).name;
						if (!string.IsNullOrEmpty(name) && Register(name, val))
						{
							num++;
						}
					}
				}
				if (num > 0)
				{
					ModLog.Info("каталог: добавлено " + num + " новых имён, всего " + Count);
				}
			}
			catch (Exception ex)
			{
				ModLog.Warn("сканирование AudioClip не удалось: " + ex.Message);
			}
		}

		public void Tick()
		{
			if (_enabled && !(DateTime.UtcNow < _nextWriteUtc))
			{
				_nextWriteUtc = DateTime.UtcNow.AddSeconds(10.0);
				WriteNow();
			}
		}

		public void WriteNow()
		{
			if (!_enabled)
			{
				return;
			}
			SoundCatalog soundCatalog;
			lock (_sync)
			{
				if (_entries.Count == 0 || (!_dirty && File.Exists(_path)))
				{
					return;
				}
				_dirty = false;
				soundCatalog = BuildLocked();
			}
			if (!soundCatalog.Save(_path, out var error))
			{
				ModLog.Warn("не удалось записать catalog.json: " + error);
				lock (_sync)
				{
					_dirty = true;
					return;
				}
			}
			ModLog.Debug("catalog.json обновлён: " + soundCatalog.Sounds.Count + " записей");
		}

		private bool Register(string name, AudioClip clip)
		{
			lock (_sync)
			{
				if (_entries.ContainsKey(name))
				{
					return false;
				}
				if (_entries.Count >= 20000)
				{
					return false;
				}
				_entries[name] = new Entry
				{
					Length = (((Object)(object)clip == (Object)null) ? 0f : clip.length),
					Channels = ((!((Object)(object)clip == (Object)null)) ? clip.channels : 0),
					LastSeenUtc = DateTime.UtcNow
				};
				_dirty = true;
				return true;
			}
		}

		private SoundCatalog BuildLocked()
		{
			SoundCatalog soundCatalog = new SoundCatalog
			{
				GeneratedUtc = DateTime.UtcNow.ToString("o"),
				GameVersion = Application.version,
				UnityVersion = Application.unityVersion,
				ModVersion = "1.6.0"
			};
			foreach (KeyValuePair<string, Entry> item in _entries.OrderByDescending((KeyValuePair<string, Entry> pair) => pair.Value.Plays).ThenBy<KeyValuePair<string, Entry>, string>((KeyValuePair<string, Entry> pair) => pair.Key, StringComparer.OrdinalIgnoreCase))
			{
				soundCatalog.Sounds.Add(new SoundCatalogEntry
				{
					Name = item.Key,
					Plays = item.Value.Plays,
					Length = item.Value.Length,
					Channels = item.Value.Channels,
					LastSeenUtc = item.Value.LastSeenUtc.ToString("o")
				});
			}
			return soundCatalog;
		}
	}
}
namespace SoundReplacer.Audio
{
	public static class AudioFileLoader
	{
		public const int MaxDurationSeconds = 120;

		public const long MaxFileBytes = 209715200L;

		public static bool IsSupportedExtension(string path)
		{
			string extension = Path.GetExtension(path);
			if (string.IsNullOrEmpty(extension))
			{
				return false;
			}
			extension = extension.ToLowerInvariant();
			if (!(extension == ".wav"))
			{
				return extension == ".ogg";
			}
			return true;
		}

		public static AudioClip LoadWav(string path)
		{
			FileInfo fileInfo = new FileInfo(path);
			if (!fileInfo.Exists)
			{
				throw new InvalidDataException("файл не найден");
			}
			if (fileInfo.Length > 209715200)
			{
				throw new InvalidDataException("файл больше 200 МБ");
			}
			byte[] array = File.ReadAllBytes(path);
			if (array.Length < 44)
			{
				throw new InvalidDataException("файл короче 44 байт — это не WAV");
			}
			if (array[0] != 82 || array[1] != 73 || array[2] != 70 || array[3] != 70)
			{
				throw new InvalidDataException("нет сигнатуры RIFF");
			}
			if (array[8] != 87 || array[9] != 65 || array[10] != 86 || array[11] != 69)
			{
				throw new InvalidDataException("нет сигнатуры WAVE");
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = -1;
			int num6 = 0;
			int num7 = 12;
			while (num7 + 8 <= array.Length)
			{
				string text = Encoding.ASCII.GetString(array, num7, 4);
				int num8 = BitConverter.ToInt32(array, num7 + 4);
				int num9 = num7 + 8;
				if (num8 < 0 || num9 > array.Length)
				{
					break;
				}
				if (text == "fmt ")
				{
					if (num8 < 16 || num9 + 16 > array.Length)
					{
						throw new InvalidDataException("некорректный fmt-чанк");
					}
					num = BitConverter.ToUInt16(array, num9);
					num2 = BitConverter.ToUInt16(array, num9 + 2);
					num3 = (int)BitConverter.ToUInt32(array, num9 + 4);
					num4 = BitConverter.ToUInt16(array, num9 + 14);
					if (num == 65534 && num8 >= 40 && num9 + 26 <= array.Length)
					{
						num = BitConverter.ToUInt16(array, num9 + 24);
					}
				}
				else if (text == "data")
				{
					num5 = num9;
					num6 = Math.Min(num8, array.Length - num9);
				}
				long num10 = (long)num9 + (long)num8 + (num8 & 1);
				if (num10 <= num7)
				{
					break;
				}
				num7 = (int)Math.Min(num10, array.Length);
			}
			if (num != 1 && num != 3)
			{
				throw new InvalidDataException("поддерживается только PCM или IEEE-float WAV (format=" + num + ")");
			}
			if (num2 < 1 || num2 > 2)
			{
				throw new InvalidDataException("поддерживается 1 или 2 канала, найдено " + num2);
			}
			if (num3 < 4000 || num3 > 192000)
			{
				throw new InvalidDataException("неподдерживаемая частота дискретизации: " + num3 + " Гц");
			}
			if (num4 != 8 && num4 != 16 && num4 != 24 && num4 != 32)
			{
				throw new InvalidDataException("неподдерживаемая разрядность: " + num4 + " бит");
			}
			if (num == 3 && num4 != 32)
			{
				throw new InvalidDataException("IEEE-float WAV поддерживается только с 32 битами");
			}
			if (num5 < 0 || num6 <= 0)
			{
				throw new InvalidDataException("в файле нет data-чанка со звуком");
			}
			int num11 = num4 / 8;
			int num12 = num11 * num2;
			int num13 = num6 / num12;
			if (num13 <= 0)
			{
				throw new InvalidDataException("в файле нет сэмплов");
			}
			if (num13 > num3 * 120)
			{
				throw new InvalidDataException("звук длиннее " + 120 + " секунд");
			}
			float[] array2 = new float[num13 * num2];
			int num14 = num5;
			for (int i = 0; i < array2.Length; i++)
			{
				if (num == 3)
				{
					array2[i] = Clamp(BitConverter.ToSingle(array, num14));
				}
				else
				{
					switch (num4)
					{
					case 8:
						array2[i] = (float)(array[num14] - 128) / 128f;
						break;
					case 16:
						array2[i] = (float)BitConverter.ToInt16(array, num14) / 32768f;
						break;
					case 24:
					{
						int num15 = array[num14] | (array[num14 + 1] << 8) | (array[num14 + 2] << 16);
						if ((num15 & 0x800000) != 0)
						{
							num15 |= -16777216;
						}
						array2[i] = (float)num15 / 8388608f;
						break;
					}
					default:
						array2[i] = (float)BitConverter.ToInt32(array, num14) / 2.1474836E+09f;
						break;
					}
				}
				num14 += num11;
			}
			AudioClip obj = AudioClip.Create(Path.GetFileNameWithoutExtension(path), num13, num2, num3, false);
			if ((Object)(object)obj == (Object)null)
			{
				throw new InvalidDataException("Unity не смог создать AudioClip (возможно, закончилась память)");
			}
			if (!obj.SetData(array2, 0))
			{
				throw new InvalidDataException("Unity не принял аудиоданные (SetData вернул false)");
			}
			return obj;
		}

		private static float Clamp(float value)
		{
			if (value > 1f)
			{
				return 1f;
			}
			if (value < -1f)
			{
				return -1f;
			}
			return value;
		}

		internal static void LogFailure(string originalName, string file, string reason)
		{
			ModLog.Warn("звук '" + originalName + "' не заменён: " + reason + " (файл: " + file + ")");
		}
	}
	internal static class ClipFitter
	{
		private const float PeakFloor = 0.0001f;

		private const float GainTolerance = 0.02f;

		public static AudioClip Fit(AudioClip original, AudioClip custom, bool matchLength, bool matchVolume, bool matchChannels, double fadeOutSeconds)
		{
			if ((Object)(object)original == (Object)null || (Object)(object)custom == (Object)null)
			{
				return null;
			}
			try
			{
				int channels = custom.channels;
				int samples = custom.samples;
				if (samples <= 0 || channels <= 0)
				{
					return null;
				}
				bool flag = matchChannels && original.channels == 1 && channels == 2;
				int num = samples;
				bool flag2 = false;
				if (matchLength && original.length > 0f && custom.length > original.length + 0.01f)
				{
					int num2 = (int)Math.Floor(original.length * (float)custom.frequency);
					if (num2 > 0 && num2 < samples)
					{
						num = num2;
						flag2 = true;
					}
				}
				float[] array = new float[custom.samples * channels];
				if (!custom.GetData(array, 0))
				{
					ModLog.Warn("подгонка '" + ((Object)original).name + "': Unity не отдал сэмплы файла, оставляю как есть");
					return null;
				}
				float num3 = Peak(array, array.Length);
				float num4 = 1f;
				bool flag3 = false;
				if (matchVolume && num3 > 0.0001f)
				{
					float num5 = TryPeak(original);
					if (num5 > 0.0001f)
					{
						num4 = num5 / num3;
						if (Math.Abs(num4 - 1f) > 0.02f)
						{
							flag3 = true;
						}
						else
						{
							num4 = 1f;
						}
					}
				}
				if (!flag2 && !flag && !flag3 && fadeOutSeconds <= 0.0)
				{
					return null;
				}
				int num6 = (flag ? 1 : channels);
				float[] array2 = new float[num * num6];
				if (flag)
				{
					for (int i = 0; i < num; i++)
					{
						array2[i] = (array[i * 2] + array[i * 2 + 1]) * 0.5f;
					}
				}
				else
				{
					Array.Copy(array, 0, array2, 0, array2.Length);
				}
				if (flag3)
				{
					for (int j = 0; j < array2.Length; j++)
					{
						array2[j] = Mathf.Clamp(array2[j] * num4, -1f, 1f);
					}
				}
				int num7 = 0;
				if (fadeOutSeconds > 0.0)
				{
					num7 = (int)Math.Round(fadeOutSeconds * (double)custom.frequency);
					if (num7 > num)
					{
						num7 = num;
					}
					if (num7 > 0)
					{
						for (int k = 0; k < num7; k++)
						{
							float num8 = 1f - (float)(k + 1) / (float)num7;
							int num9 = (num - num7 + k) * num6;
							for (int l = 0; l < num6; l++)
							{
								array2[num9 + l] *= num8;
							}
						}
					}
				}
				AudioClip val = AudioClip.Create(((Object)custom).name + "_fit", num, num6, custom.frequency, false);
				if (!val.SetData(array2, 0))
				{
					Object.Destroy((Object)(object)val);
					ModLog.Warn("подгонка '" + ((Object)original).name + "': SetData не сработал, оставляю оригинальный файл");
					return null;
				}
				ModLog.Info(string.Format("подгонка '{0}': длина {1:0.00} c -> {2:0.00} c{3}, громкость пик {4:0.000} -> {5:0.000}{6}, каналы {7} -> {8}{9}", ((Object)original).name, custom.length, (float)num / (float)custom.frequency, flag2 ? " (обрезано)" : string.Empty, num3, flag3 ? (num3 * num4) : num3, flag3 ? " (нормализовано)" : string.Empty, channels, num6, (num7 > 0) ? (", затухание " + fadeOutSeconds.ToString("0.0") + " c") : string.Empty));
				return val;
			}
			catch (Exception ex)
			{
				ModLog.Warn("подгонка '" + (((Object)(object)original != (Object)null) ? ((Object)original).name : "?") + "' не удалась: " + ex.Message);
				return null;
			}
		}

		private static float TryPeak(AudioClip clip)
		{
			try
			{
				int num = clip.samples * clip.channels;
				if (num <= 0)
				{
					return 0f;
				}
				float[] array = new float[num];
				if (!clip.GetData(array, 0))
				{
					return 0f;
				}
				return Peak(array, array.Length);
			}
			catch (Exception ex)
			{
				ModLog.Debug("пик оригинала '" + ((Object)clip).name + "' недоступен: " + ex.Message);
				return 0f;
			}
		}

		private static float Peak(float[] data, int count)
		{
			float num = 0f;
			for (int i = 0; i < count; i++)
			{
				float num2 = ((data[i] < 0f) ? (0f - data[i]) : data[i]);
				if (num2 > num)
				{
					num = num2;
				}
			}
			return num;
		}
	}
	public sealed class HarmonyAudioBackend : ISoundBackend
	{
		internal static class SourcePlayPatch
		{
			public static bool Prefix(AudioSource __instance)
			{
				//IL_004f: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)__instance == (Object)null)
				{
					return true;
				}
				AudioClip clip = __instance.clip;
				ObserveClip(clip);
				SoundReplacementManager manager = Manager;
				if (manager == null)
				{
					return true;
				}
				if (!manager.TryGet(clip, out var replacement))
				{
					return true;
				}
				if ((Object)(object)replacement == (Object)null)
				{
					return false;
				}
				if (manager.ShouldSpatialize(((Object)clip).name))
				{
					TrackHost.Play(((Component)__instance).transform.position, replacement, manager.Config, ((Object)clip).name);
					return false;
				}
				AudioPatchHelpers.AssignClip(__instance, replacement);
				return true;
			}
		}

		internal static class ClipArgumentPatch
		{
			public static bool Prefix(AudioSource __instance, ref AudioClip clip)
			{
				//IL_005c: Unknown result type (might be due to invalid IL or missing references)
				//IL_004f: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)clip == (Object)null)
				{
					return true;
				}
				ObserveClip(clip);
				SoundReplacementManager manager = Manager;
				if (manager == null)
				{
					return true;
				}
				if (!manager.TryGet(clip, out var replacement))
				{
					return true;
				}
				if ((Object)(object)replacement == (Object)null)
				{
					return false;
				}
				if (manager.ShouldSpatialize(((Object)clip).name))
				{
					TrackHost.Play(((Object)(object)__instance != (Object)null) ? ((Component)__instance).transform.position : Vector3.zero, replacement, manager.Config, ((Object)clip).name);
					return false;
				}
				clip = replacement;
				return true;
			}
		}

		internal static class SetClipPatch
		{
			public static void Postfix(AudioSource __instance, AudioClip value)
			{
				if (!((Object)(object)__instance == (Object)null) && !((Object)(object)value == (Object)null))
				{
					SoundReplacementManager manager = Manager;
					if (manager != null && manager.PatchClipSetter && !manager.ShouldSpatialize(((Object)value).name) && manager.TryGetForAssignment(value, out var replacement) && !((Object)(object)replacement == (Object)null))
					{
						AudioPatchHelpers.AssignClip(__instance, replacement);
					}
				}
			}
		}

		private const string HarmonyId = "com.soundreplacer.repo.audio";

		private Harmony _harmony;

		private bool _installed;

		public string Name => "Harmony / AudioSource";

		private static SoundReplacementManager Manager
		{
			get
			{
				if (!((Object)(object)Plugin.Instance == (Object)null))
				{
					return Plugin.Instance.Replacements;
				}
				return null;
			}
		}

		public bool Install(SoundReplacementManager manager)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			if (_installed)
			{
				return true;
			}
			if (_harmony == null)
			{
				_harmony = new Harmony("com.soundreplacer.repo.audio");
			}
			int num = 0;
			int missing = 0;
			MethodInfo prefix = AccessTools.Method(typeof(SourcePlayPatch), "Prefix", (Type[])null, (Type[])null);
			MethodInfo prefix2 = AccessTools.Method(typeof(ClipArgumentPatch), "Prefix", (Type[])null, (Type[])null);
			MethodInfo postfix = AccessTools.Method(typeof(SetClipPatch), "Postfix", (Type[])null, (Type[])null);
			if (Patch(AccessTools.Method(typeof(AudioSource), "Play", new Type[0], (Type[])null), prefix, null, ref missing))
			{
				num++;
			}
			if (Patch(AccessTools.Method(typeof(AudioSource), "Play", new Type[1] { typeof(ulong) }, (Type[])null), prefix, null, ref missing))
			{
				num++;
			}
			if (Patch(AccessTools.Method(typeof(AudioSource), "PlayDelayed", new Type[1] { typeof(float) }, (Type[])null), prefix, null, ref missing))
			{
				num++;
			}
			if (Patch(AccessTools.Method(typeof(AudioSource), "PlayScheduled", new Type[1] { typeof(double) }, (Type[])null), prefix, null, ref missing))
			{
				num++;
			}
			if (Patch(AccessTools.Method(typeof(AudioSource), "PlayOneShot", new Type[1] { typeof(AudioClip) }, (Type[])null), prefix2, null, ref missing))
			{
				num++;
			}
			if (Patch(AccessTools.Method(typeof(AudioSource), "PlayOneShot", new Type[2]
			{
				typeof(AudioClip),
				typeof(float)
			}, (Type[])null), prefix2, null, ref missing))
			{
				num++;
			}
			if (Patch(AccessTools.Method(typeof(AudioSource), "PlayClipAtPoint", new Type[2]
			{
				typeof(Vector3),
				typeof(AudioClip)
			}, (Type[])null), prefix2, null, ref missing))
			{
				num++;
			}
			if (Patch(AccessTools.Method(typeof(AudioSource), "PlayClipAtPoint", new Type[3]
			{
				typeof(Vector3),
				typeof(AudioClip),
				typeof(float)
			}, (Type[])null), prefix2, null, ref missing))
			{
				num++;
			}
			if (Patch(AccessTools.Method(typeof(AudioSource), "set_clip", (Type[])null, (Type[])null), null, postfix, ref missing))
			{
				num++;
			}
			_installed = num > 0;
			ModLog.Info("Harmony backend: установлено патчей " + num + ", недоступно " + missing);
			if (num == 0)
			{
				ModLog.Error("Harmony backend не установил ни одного патча — замена звуков работать не будет");
			}
			return _installed;
		}

		public void Uninstall()
		{
			if (_harmony == null)
			{
				return;
			}
			try
			{
				_harmony.UnpatchSelf();
			}
			catch (Exception ex)
			{
				ModLog.Warn("не удалось снять Harmony-патчи: " + ex.Message);
			}
			finally
			{
				_installed = false;
			}
		}

		private bool Patch(MethodInfo target, MethodInfo prefix, MethodInfo postfix, ref int missing)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			string text = ((target == null) ? "?" : (target.DeclaringType.Name + "." + target.Name));
			if (target == null)
			{
				missing++;
				ModLog.Warn("метод для патча не найден: " + text);
				return false;
			}
			try
			{
				_harmony.Patch((MethodBase)target, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(prefix), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(postfix), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				ModLog.Debug("пропатчен " + text);
				return true;
			}
			catch (Exception ex)
			{
				ModLog.Warn("не удалось пропатчить " + text + ": " + ex.Message);
				return false;
			}
		}

		private static void ObserveClip(AudioClip clip)
		{
			if (!((Object)(object)clip == (Object)null) && !((Object)(object)Plugin.Instance == (Object)null))
			{
				Plugin.Instance.Catalog?.Observe(clip);
				SoundReplacementManager replacements = Plugin.Instance.Replacements;
				if (replacements != null && replacements.LogPlays)
				{
					ModLog.Info("играет звук: '" + ((Object)clip).name + "' (" + clip.length.ToString("0.00") + " c, " + clip.channels + " кан.)");
				}
			}
		}
	}
	internal static class AudioPatchHelpers
	{
		[ThreadStatic]
		private static bool _assigning;

		public static void AssignClip(AudioSource source, AudioClip clip)
		{
			if ((Object)(object)source == (Object)null || (Object)(object)clip == (Object)null || _assigning)
			{
				return;
			}
			_assigning = true;
			try
			{
				source.clip = clip;
			}
			catch (Exception ex)
			{
				ModLog.Warn("не удалось подменить клип у AudioSource: " + ex.Message);
			}
			finally
			{
				_assigning = false;
			}
		}
	}
	public interface ISoundBackend
	{
		string Name { get; }

		bool Install(SoundReplacementManager manager);

		void Uninstall();
	}
	public sealed class SoundReplacementManager
	{
		private readonly ReplacerConfig _config;

		private readonly string _pluginDirectory;

		private readonly Dictionary<string, AudioClip> _replacements = new Dictionary<string, AudioClip>(StringComparer.Ordinal);

		private readonly Dictionary<string, string> _sources = new Dictionary<string, string>(StringComparer.Ordinal);

		private readonly List<string> _failures = new List<string>();

		private readonly HashSet<string> _fitted = new HashSet<string>(StringComparer.Ordinal);

		private readonly List<AudioClip> _owned = new List<AudioClip>();

		private readonly Dictionary<string, float> _lastStartBySource = new Dictionary<string, float>(StringComparer.OrdinalIgnoreCase);

		private readonly object _sync = new object();

		public bool Enabled => _config.Enabled;

		public bool PatchClipSetter => _config.PatchClipSetter;

		public bool LogPlays => _config.LogPlays;

		public int Count
		{
			get
			{
				lock (_sync)
				{
					return _replacements.Count;
				}
			}
		}

		public IList<string> Failures
		{
			get
			{
				lock (_sync)
				{
					return _failures.ToArray();
				}
			}
		}

		public ReplacerConfig Config => _config;

		public SoundReplacementManager(ReplacerConfig config, string pluginDirectory)
		{
			_config = config ?? new ReplacerConfig();
			_pluginDirectory = pluginDirectory;
		}

		public bool IsNoTrim(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			foreach (string item in _config.NoTrim)
			{
				if (string.Equals(item, name, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		public bool TryGet(AudioClip original, out AudioClip replacement)
		{
			replacement = null;
			if ((Object)(object)original == (Object)null || !_config.Enabled)
			{
				return false;
			}
			string name = ((Object)original).name;
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			lock (_sync)
			{
				if (!_replacements.TryGetValue(name, out var value) || (Object)(object)value == (Object)null)
				{
					return false;
				}
				if (value == original)
				{
					return false;
				}
				if (!_fitted.Contains(name))
				{
					_fitted.Add(name);
					AudioClip val = ClipFitter.Fit(original, value, _config.MatchLength && !IsNoTrim(name), _config.MatchVolume, _config.MatchChannels, _config.FadeOutSeconds);
					if ((Object)(object)val != (Object)null)
					{
						_owned.Add(val);
						_replacements[name] = val;
						value = val;
					}
				}
				if (ShouldSuppress(name))
				{
					replacement = null;
					return true;
				}
				replacement = value;
				return true;
			}
		}

		private bool ShouldSuppress(string name)
		{
			if (!_config.OncePerFile)
			{
				return false;
			}
			if (!_sources.TryGetValue(name, out var value) || string.IsNullOrEmpty(value))
			{
				return false;
			}
			float unscaledTime = Time.unscaledTime;
			if (_lastStartBySource.TryGetValue(value, out var value2) && unscaledTime - value2 < (float)_config.OnceWindowSeconds)
			{
				ModLog.Info("повторный старт '" + name + "' заглушён (окно " + _config.OnceWindowSeconds.ToString("0.0") + " c)");
				return true;
			}
			_lastStartBySource[value] = unscaledTime;
			return false;
		}

		public bool TryGetByName(string originalName, out AudioClip replacement)
		{
			replacement = null;
			if (string.IsNullOrEmpty(originalName) || !_config.Enabled)
			{
				return false;
			}
			lock (_sync)
			{
				if (!_replacements.TryGetValue(originalName, out var value) || (Object)(object)value == (Object)null)
				{
					return false;
				}
				if (ShouldSuppress(originalName))
				{
					replacement = null;
					return true;
				}
				replacement = value;
				return true;
			}
		}

		public bool ShouldSpatialize(string name)
		{
			if (string.IsNullOrEmpty(name) || _config.Spatial.Count == 0)
			{
				return false;
			}
			for (int i = 0; i < _config.Spatial.Count; i++)
			{
				if (string.Equals(_config.Spatial[i], name, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		public bool TryGetForAssignment(AudioClip original, out AudioClip replacement)
		{
			replacement = null;
			if ((Object)(object)original == (Object)null || !_config.Enabled)
			{
				return false;
			}
			string name = ((Object)original).name;
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			lock (_sync)
			{
				if (!_replacements.TryGetValue(name, out var value) || (Object)(object)value == (Object)null)
				{
					return false;
				}
				if (value == original)
				{
					return false;
				}
				replacement = value;
				return true;
			}
		}

		public IEnumerator LoadAllCoroutine()
		{
			lock (_sync)
			{
				_replacements.Clear();
				_sources.Clear();
				_failures.Clear();
				_fitted.Clear();
				_owned.Clear();
				_lastStartBySource.Clear();
			}
			if (!_config.Enabled)
			{
				ModLog.Warn("замена звуков отключена в config.json (enabled = false)");
				yield break;
			}
			int total = _config.Sounds.Count;
			if (total == 0)
			{
				ModLog.Info("config.json не содержит маппингов — звуки остаются оригинальными");
				yield break;
			}
			ModLog.Info("загрузка пользовательских звуков, маппингов: " + total);
			int loaded = 0;
			foreach (KeyValuePair<string, string> sound in _config.Sounds)
			{
				string originalName = sound.Key;
				string configuredPath = sound.Value;
				if (!ReplacerConfig.TryResolvePath(_pluginDirectory, configuredPath, out var absolutePath, out var error))
				{
					Fail(originalName, configuredPath, error);
					yield return null;
					continue;
				}
				string text = Path.GetExtension(absolutePath).ToLowerInvariant();
				AudioClip val = null;
				string text2 = null;
				if (text == ".wav")
				{
					try
					{
						val = AudioFileLoader.LoadWav(absolutePath);
					}
					catch (Exception ex)
					{
						text2 = ex.Message;
					}
				}
				else if (text == ".ogg")
				{
					AudioClip decoded = null;
					string decodeError = null;
					yield return LoadOggCoroutine(absolutePath, delegate(AudioClip c)
					{
						decoded = c;
					}, delegate(string e)
					{
						decodeError = e;
					});
					val = decoded;
					text2 = decodeError;
				}
				else
				{
					text2 = "неподдерживаемое расширение '" + text + "' (нужен .ogg или .wav)";
				}
				if ((Object)(object)val == (Object)null)
				{
					Fail(originalName, configuredPath, text2 ?? "неизвестная ошибка декодирования");
					yield return null;
					continue;
				}
				try
				{
					((Object)val).name = "SR_" + SanitizeClipName(Path.GetFileNameWithoutExtension(absolutePath));
					Register(originalName, val, configuredPath);
					loaded++;
					ModLog.Info("заменён звук '" + originalName + "' -> " + configuredPath);
				}
				catch (Exception ex2)
				{
					Fail(originalName, configuredPath, ex2.Message);
				}
				yield return null;
				absolutePath = null;
			}
			ModLog.Info("итог загрузки: заменено " + loaded + " из " + total + " звук(ов)");
		}

		public void UnloadAll()
		{
			List<AudioClip> list;
			lock (_sync)
			{
				list = new List<AudioClip>(_owned);
				list.AddRange(_replacements.Values);
				_replacements.Clear();
				_sources.Clear();
				_owned.Clear();
				_fitted.Clear();
				_lastStartBySource.Clear();
			}
			foreach (AudioClip item in list)
			{
				if (!((Object)(object)item == (Object)null))
				{
					try
					{
						Object.Destroy((Object)(object)item);
					}
					catch (Exception ex)
					{
						ModLog.Warn("не удалось уничтожить AudioClip: " + ex.Message);
					}
				}
			}
		}

		private void Register(string originalName, AudioClip clip, string source)
		{
			lock (_sync)
			{
				if (_replacements.ContainsKey(originalName))
				{
					ModLog.Warn("дубликат маппинга для '" + originalName + "', используется последний файл");
				}
				_replacements[originalName] = clip;
				_sources[originalName] = source;
				_owned.Add(clip);
			}
		}

		private void Fail(string originalName, string path, string reason)
		{
			string text = "звук '" + originalName + "' не заменён: " + reason + " (файл: " + path + ") — играет оригинальный звук";
			ModLog.Warn(text);
			lock (_sync)
			{
				_failures.Add(text);
			}
		}

		private static IEnumerator LoadOggCoroutine(string path, Action<AudioClip> onSuccess, Action<string> onError)
		{
			string absoluteUri;
			try
			{
				absoluteUri = new Uri(path).AbsoluteUri;
			}
			catch (Exception ex)
			{
				onError("не удалось построить file:// URI: " + ex.Message);
				yield break;
			}
			UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(absoluteUri, (AudioType)14);
			try
			{
				yield return request.SendWebRequest();
				if ((int)request.result != 1)
				{
					onError("Unity не смог декодировать OGG: " + (request.error ?? "unknown") + " (HTTP " + request.responseCode + ")");
					yield break;
				}
				AudioClip content = DownloadHandlerAudioClip.GetContent(request);
				if ((Object)(object)content == (Object)null)
				{
					onError("декодер вернул null");
					yield break;
				}
				if (content.samples <= 0)
				{
					onError("в декодированном клипе 0 сэмплов");
					yield break;
				}
				onSuccess(content);
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		private static string SanitizeClipName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return "custom";
			}
			StringBuilder stringBuilder = new StringBuilder(name.Length);
			foreach (char c in name)
			{
				if (char.IsLetterOrDigit(c) || c == '_' || c == '-' || c == ' ')
				{
					stringBuilder.Append(c);
				}
				else
				{
					stringBuilder.Append('_');
				}
				if (stringBuilder.Length >= 80)
				{
					break;
				}
			}
			if (stringBuilder.Length != 0)
			{
				return stringBuilder.ToString();
			}
			return "custom";
		}
	}
	internal sealed class TrackHost : MonoBehaviour
	{
		internal static void Play(Vector3 position, AudioClip clip, ReplacerConfig config, string originalName)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)clip == (Object)null || config == null)
			{
				return;
			}
			try
			{
				GameObject val = new GameObject("SoundReplacerTrack");
				val.transform.position = position;
				AudioSource val2 = val.AddComponent<AudioSource>();
				val2.playOnAwake = false;
				val2.loop = false;
				val2.clip = clip;
				val2.volume = Mathf.Clamp01((float)config.TrackVolume);
				val2.spatialBlend = 1f;
				val2.rolloffMode = (AudioRolloffMode)0;
				val2.minDistance = Mathf.Max(0.1f, (float)config.MinDistance);
				val2.maxDistance = Mathf.Max(val2.minDistance + 0.1f, (float)config.MaxDistance);
				val2.dopplerLevel = 0f;
				val2.spread = 0f;
				val2.Play();
				float num = 0.5f;
				if (config.Echo)
				{
					AudioEchoFilter val3 = val.AddComponent<AudioEchoFilter>();
					val3.delay = Mathf.Clamp((float)config.EchoDelayMs, 10f, 5000f);
					val3.decayRatio = Mathf.Clamp((float)config.EchoDecayRatio, 0f, 0.95f);
					val3.wetMix = Mathf.Clamp01((float)config.EchoWetMix);
					val3.dryMix = Mathf.Clamp01((float)config.EchoDryMix);
					num += val3.delay * 0.001f * 6f;
				}
				Object.Destroy((Object)(object)val, clip.length + num);
				ModLog.Info(string.Format("трек для '{0}': 3D-источник в {1}, дистанции {2:0.0}-{3:0.0} м{4}", originalName, position, val2.minDistance, val2.maxDistance, config.Echo ? (", эхо " + config.EchoDelayMs.ToString("0") + " мс") : ", без эха"));
			}
			catch (Exception ex)
			{
				ModLog.Warn("не удалось запустить 3D-трек для '" + originalName + "': " + ex.Message);
			}
		}
	}
}