The BepInEx console will not appear when launching like it does for other games on Thunderstore (you can turn it back on in your BepInEx.cfg file). If your PEAK crashes on startup, add -dx12 to your launch parameters.
Decompiled source of loaforcsSoundAPI v2.0.8
BepInEx/plugins/loaforcsSoundAPI/me.loaforc.soundapi.dll
Decompiled 2 months agousing System; using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using UnityEngine; using UnityEngine.Networking; using UnityEngine.SceneManagement; using loaforcsSoundAPI.Core; using loaforcsSoundAPI.Core.Data; using loaforcsSoundAPI.Core.JSON; using loaforcsSoundAPI.Core.Networking; using loaforcsSoundAPI.Core.Patches; using loaforcsSoundAPI.Core.Util; using loaforcsSoundAPI.Core.Util.Extensions; using loaforcsSoundAPI.Reporting; using loaforcsSoundAPI.Reporting.Data; using loaforcsSoundAPI.SoundPacks; using loaforcsSoundAPI.SoundPacks.Data; using loaforcsSoundAPI.SoundPacks.Data.Conditions; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("me.loaforc.soundapi")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.0.8.0")] [assembly: AssemblyInformationalVersion("2.0.8+02f5b90764ee9441879eaa0c04ef15b7674b7d42")] [assembly: AssemblyProduct("loaforcsSoundAPI")] [assembly: AssemblyTitle("me.loaforc.soundapi")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.0.8.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 loaforcsSoundAPI { [BepInPlugin("me.loaforc.soundapi", "loaforcsSoundAPI", "2.0.8")] internal class loaforcsSoundAPI : BaseUnityPlugin { private static loaforcsSoundAPI _instance; internal static ManualLogSource Logger { get; private set; } private void Awake() { _instance = this; Logger = Logger.CreateLogSource("me.loaforc.soundapi"); ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; Logger.LogInfo((object)"Setting up config"); Debuggers.Bind(((BaseUnityPlugin)this).Config); SoundReportHandler.Bind(((BaseUnityPlugin)this).Config); PatchConfig.Bind(((BaseUnityPlugin)this).Config); PackLoadingConfig.Bind(((BaseUnityPlugin)this).Config); Logger.LogInfo((object)"Running patches"); Harmony harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "me.loaforc.soundapi"); UnityObjectPatch.Init(harmony); Logger.LogInfo((object)"Registering data"); SoundAPI.RegisterAll(Assembly.GetExecutingAssembly()); SoundAPIAudioManager.SpawnManager(); SoundReplacementHandler.Register(); ((BaseUnityPlugin)this).Config.Save(); Logger.LogInfo((object)"me.loaforc.soundapi by loaforc has loaded :3"); } internal static ConfigFile GenerateConfigFile(string name, BepInPlugin metadata) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown return new ConfigFile(Utility.CombinePaths(new string[2] { Paths.ConfigPath, name + ".cfg" }), false, metadata); } } public static class SoundAPI { public const string PLUGIN_GUID = "me.loaforc.soundapi"; internal static NetworkAdapter CurrentNetworkAdapter { get; private set; } public static async Task<AudioClip> LoadAudioFileAsync(string fullPath) { if (!File.Exists(fullPath)) { throw new FileNotFoundException("'" + fullPath + "' not found."); } if (!SoundPackLoadPipeline.audioExtensions.ContainsKey(Path.GetExtension(fullPath))) { throw new NotImplementedException("Audio file extension: '" + Path.GetExtension(fullPath) + "' is not implemented."); } UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(fullPath, SoundPackLoadPipeline.audioExtensions[Path.GetExtension(fullPath)]); await (AsyncOperation)(object)request.SendWebRequest(); AudioClip content = DownloadHandlerAudioClip.GetContent(request); request.Dispose(); return content; } public static void RegisterAll(Assembly assembly) { foreach (Type loadableType in assembly.GetLoadableTypes()) { if (loadableType.IsNested) { continue; } foreach (SoundAPIConditionAttribute conditionAttribute in loadableType.GetCustomAttributes<SoundAPIConditionAttribute>()) { if (!typeof(Condition).IsAssignableFrom(loadableType)) { loaforcsSoundAPI.Logger.LogError((object)("Condition: '" + loadableType.FullName + "' has been marked with [SoundAPICondition] but does not extend Condition!")); continue; } ConstructorInfo info = loadableType.GetConstructor(Array.Empty<Type>()); if (info == null) { loaforcsSoundAPI.Logger.LogError((object)("Condition: '" + loadableType.FullName + "' has no valid constructor! It must have a constructor with no parameters! If you need extra parameters do not mark it with [SoundAPICondition] and register it manually.")); continue; } RegisterCondition(conditionAttribute.ID, delegate { if (conditionAttribute.IsDeprecated) { if (conditionAttribute.DeprecationReason == null) { loaforcsSoundAPI.Logger.LogWarning((object)("Condition: '" + conditionAttribute.ID + "' is deprecated and may be removed in future.")); } else { loaforcsSoundAPI.Logger.LogWarning((object)("Condition: '" + conditionAttribute.ID + "' is deprecated. " + conditionAttribute.DeprecationReason)); } } return (Condition)info.Invoke(Array.Empty<object>()); }); } } } public static void RegisterCondition(string id, Func<Condition> factory) { SoundPackDataHandler.Register(id, factory); } public static void RegisterNetworkAdapter(NetworkAdapter adapter) { CurrentNetworkAdapter = adapter; loaforcsSoundAPI.Logger.LogInfo((object)("Registered network adapter: '" + CurrentNetworkAdapter.Name + "'")); CurrentNetworkAdapter.OnRegister(); } public static void RegisterSoundPack(SoundPack pack) { if (SoundPackDataHandler.LoadedPacks.Contains(pack)) { throw new InvalidOperationException("Already registered sound-pack: '" + pack.Name + "'!"); } SoundPackDataHandler.AddLoadedPack(pack); foreach (SoundReplacementCollection replacementCollection in pack.ReplacementCollections) { foreach (SoundReplacementGroup replacement in replacementCollection.Replacements) { SoundPackDataHandler.AddReplacement(replacement); } } } } internal static class MyPluginInfo { public const string PLUGIN_GUID = "me.loaforc.soundapi"; public const string PLUGIN_NAME = "loaforcsSoundAPI"; public const string PLUGIN_VERSION = "2.0.8"; } } namespace loaforcsSoundAPI.SoundPacks { internal class LoadSoundOperation { public readonly UnityWebRequest WebRequest = webRequest.webRequest; public readonly SoundInstance Sound; public bool IsReady => WebRequest.isDone; public bool IsDone { get; set; } public LoadSoundOperation(SoundInstance soundInstance, UnityWebRequestAsyncOperation webRequest) { Sound = soundInstance; base..ctor(); } } internal static class PackLoadingConfig { internal static bool MetadataSpoofing { get; private set; } internal static void Bind(ConfigFile file) { MetadataSpoofing = file.Bind<bool>("PackLoading", "MetadataSpoofing", true, "Should SoundAPI use a fake BepInPlugin attribute when generating configs? This can fix some issues with mod managers, notably with Gale displaying the config file name, instead of the sound-pack name.").Value; } } internal static class SoundPackDataHandler { private static List<SoundPack> _loadedPacks = new List<SoundPack>(); internal static Dictionary<string, List<SoundReplacementGroup>> SoundReplacements = new Dictionary<string, List<SoundReplacementGroup>>(); internal static Dictionary<string, Func<Condition>> conditionFactories = new Dictionary<string, Func<Condition>>(); internal static List<AudioClip> allLoadedClips = new List<AudioClip>(); internal static IReadOnlyList<SoundPack> LoadedPacks => _loadedPacks.AsReadOnly(); internal static void Register(string id, Func<Condition> factory) { conditionFactories[id] = factory; } public static Condition CreateCondition(string id) { if (conditionFactories.TryGetValue(id, out var value)) { return value(); } return new InvalidCondition(id); } internal static void AddLoadedPack(SoundPack pack) { _loadedPacks.Add(pack); } internal static void AddReplacement(SoundReplacementGroup group) { foreach (string match in group.Matches) { string key = match.Split(":").Last(); if (!SoundReplacements.TryGetValue(key, out var value)) { value = new List<SoundReplacementGroup>(); } if (!value.Contains(group)) { value.Add(group); SoundReplacements[key] = value; } } } } internal static class SoundPackLoadPipeline { private class SkippedResults { public int Collections; public int Groups; public int Sounds; } private static volatile int _activeThreads; private static Dictionary<string, List<string>> mappings; internal static Dictionary<string, AudioType> audioExtensions; internal static event Action OnFinishedPipeline; internal static async void StartPipeline() { Stopwatch completeLoadingTimer = Stopwatch.StartNew(); Stopwatch timer = Stopwatch.StartNew(); List<SoundPack> list = FindAndLoadPacks(); loaforcsSoundAPI.Logger.LogInfo((object)$"(Step 1) Loading Sound-pack definitions took {timer.ElapsedMilliseconds}ms"); if (list.Count == 0) { loaforcsSoundAPI.Logger.LogWarning((object)"No sound-packs were found to load! This can be ignorable if you're doing testing or using SoundAPI for another purpose, but if you expected sound-packs to load you may have set it up incorrectly."); } timer.Restart(); List<LoadSoundOperation> webRequestOperations = new List<LoadSoundOperation>(); foreach (SoundPack item2 in list) { string path = Path.Combine(item2.PackFolder, "soundapi_mappings.json"); if (!File.Exists(path)) { continue; } Dictionary<string, List<string>> dictionary = JSONDataLoader.LoadFromFile<Dictionary<string, List<string>>>(path); foreach (KeyValuePair<string, List<string>> item3 in dictionary) { if (mappings.ContainsKey(item3.Key)) { mappings[item3.Key].AddRange(item3.Value); } else { mappings[item3.Key] = item3.Value; } } } loaforcsSoundAPI.Logger.LogInfo((object)$"(Step 2) Loading Sound-pack mappings ('{mappings.Count}') took {timer.ElapsedMilliseconds}ms"); timer.Restart(); SkippedResults skippedStats = new SkippedResults(); foreach (SoundPack item4 in list) { foreach (SoundReplacementCollection item5 in LoadSoundReplacementCollections(item4, ref skippedStats)) { foreach (SoundReplacementGroup replacement in item5.Replacements) { SoundPackDataHandler.AddReplacement(replacement); foreach (SoundInstance sound in replacement.Sounds) { if (sound.Condition is ConstantCondition constantCondition && !constantCondition.Value) { Debuggers.SoundReplacementLoader?.Log("skipping a sound in '" + LogFormats.FormatFilePath(item5.FilePath) + "' because sound is marked as constant and has a value of false."); skippedStats.Sounds++; } else { webRequestOperations.Add(StartWebRequestOperation(item4, sound, audioExtensions[Path.GetExtension(sound.Sound)])); } } } } } int amountOfOperations = webRequestOperations.Count; loaforcsSoundAPI.Logger.LogInfo((object)$"(Step 3) Skipped {skippedStats.Collections} collection(s), {skippedStats.Groups} replacement(s), {skippedStats.Sounds} sound(s)"); loaforcsSoundAPI.Logger.LogInfo((object)$"(Step 3) Loading sound replacement collections took {timer.ElapsedMilliseconds}ms"); if (SoundReportHandler.CurrentReport != null) { SoundReportHandler.CurrentReport.AudioClipsLoaded = amountOfOperations; } loaforcsSoundAPI.Logger.LogInfo((object)$"(Step 4) Started loading {amountOfOperations} audio file(s)"); loaforcsSoundAPI.Logger.LogInfo((object)"Waiting for splash screens to complete to continue..."); completeLoadingTimer.Stop(); await Task.Delay(1); loaforcsSoundAPI.Logger.LogInfo((object)"Splash screens done! Continuing pipeline"); loaforcsSoundAPI.Logger.LogWarning((object)"The game will freeze for a moment!"); timer.Restart(); completeLoadingTimer.Start(); bool flag = false; bool threadsShouldExit = false; ConcurrentQueue<LoadSoundOperation> queuedOperations = new ConcurrentQueue<LoadSoundOperation>(); ConcurrentBag<Exception> threadPoolExceptions = new ConcurrentBag<Exception>(); for (int i = 0; i < 16; i++) { new Thread((ThreadStart)delegate { while (queuedOperations.Count == 0 && !threadsShouldExit) { Thread.Yield(); } Interlocked.Increment(ref _activeThreads); Debuggers.SoundReplacementLoader?.Log($"active threads at {_activeThreads}"); LoadSoundOperation result; while (queuedOperations.TryDequeue(out result)) { try { AudioClip content = DownloadHandlerAudioClip.GetContent(result.WebRequest); result.Sound.Clip = content; result.WebRequest.Dispose(); Debuggers.SoundReplacementLoader?.Log("clip generated"); result.IsDone = true; } catch (Exception item) { threadPoolExceptions.Add(item); } } Interlocked.Decrement(ref _activeThreads); }).Start(); } while (webRequestOperations.Count > 0) { foreach (LoadSoundOperation item6 in from operation in webRequestOperations.ToList() where operation.IsReady select operation) { queuedOperations.Enqueue(item6); webRequestOperations.Remove(item6); } if (!flag && webRequestOperations.Count < amountOfOperations / 2) { flag = true; loaforcsSoundAPI.Logger.LogInfo((object)"(Step 5) Queued half of the needed operations!"); } Thread.Yield(); } loaforcsSoundAPI.Logger.LogInfo((object)"(Step 5) All file reads are done, waiting for the audio clips conversions."); threadsShouldExit = true; while (_activeThreads > 0 || webRequestOperations.Any((LoadSoundOperation operation) => !operation.IsDone)) { Thread.Yield(); } loaforcsSoundAPI.Logger.LogInfo((object)$"(Step 6) Took {timer.ElapsedMilliseconds}ms to finish loading audio clips from files"); if (threadPoolExceptions.Count != 0) { loaforcsSoundAPI.Logger.LogError((object)$"(Step 6) {threadPoolExceptions.Count} internal error(s) happened while loading:"); foreach (Exception item7 in threadPoolExceptions) { loaforcsSoundAPI.Logger.LogError((object)item7.ToString()); } } SoundPackLoadPipeline.OnFinishedPipeline(); mappings = null; loaforcsSoundAPI.Logger.LogDebug((object)$"Active Threads that are left over: {_activeThreads}"); loaforcsSoundAPI.Logger.LogInfo((object)$"Entire load process took an effective {completeLoadingTimer.ElapsedMilliseconds}ms"); } private static List<SoundPack> FindAndLoadPacks(string entryPoint = "sound_pack.json") { //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Expected O, but got Unknown Dictionary<string, SoundPack> dictionary = new Dictionary<string, SoundPack>(); string[] files = Directory.GetFiles(Paths.PluginPath, entryPoint, SearchOption.AllDirectories); foreach (string text in files) { Debuggers.SoundReplacementLoader?.Log("found entry point: '" + text + "'!"); SoundPack soundPack = JSONDataLoader.LoadFromFile<SoundPack>(text); if (soundPack == null) { continue; } soundPack.PackFolder = Path.GetDirectoryName(text); if (dictionary.TryGetValue(soundPack.Name, out var value)) { IValidatable.LogAndCheckValidationResult("loading '" + text + "'", new List<IValidatable.ValidationResult>(1) { new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "A sound-pack with name '" + soundPack.Name + "' was already loaded from '" + LogFormats.FormatFilePath(Path.Combine(value.PackFolder, "sound_pack.json")) + "'. Skipping loading the duplicate!!") }, soundPack.Logger); continue; } Debuggers.SoundReplacementLoader?.Log("json loaded, validating"); List<IValidatable.ValidationResult> results = soundPack.Validate(); if (IValidatable.LogAndCheckValidationResult("loading '" + text + "'", results, soundPack.Logger)) { ConfigFile val = loaforcsSoundAPI.GenerateConfigFile(metadata: (BepInPlugin)((!PackLoadingConfig.MetadataSpoofing) ? ((object)MetadataHelper.GetMetadata(typeof(loaforcsSoundAPI))) : ((object)new BepInPlugin(soundPack.GUID, soundPack.Name, soundPack.Version ?? "1.0.0"))), name: soundPack.GUID); val.SaveOnConfigSet = false; soundPack.Bind(val); if (val.Count > 0) { val.Save(); } dictionary[soundPack.Name] = soundPack; SoundPackDataHandler.AddLoadedPack(soundPack); Debuggers.SoundReplacementLoader?.Log("pack folder: " + soundPack.PackFolder); } } Debuggers.SoundReplacementLoader?.Log($"loaded '{dictionary.Count}' packs."); return dictionary.Values.ToList(); } private static List<SoundReplacementCollection> LoadSoundReplacementCollections(SoundPack pack, ref SkippedResults skippedStats) { List<SoundReplacementCollection> list = new List<SoundReplacementCollection>(); if (!Directory.Exists(Path.Combine(pack.PackFolder, "replacers"))) { return list; } Debuggers.SoundReplacementLoader?.Log("start loading '" + pack.Name + "'!"); string[] files = Directory.GetFiles(Path.Combine(pack.PackFolder, "replacers"), "*.json", SearchOption.AllDirectories); foreach (string text in files) { Debuggers.SoundReplacementLoader?.Log("found replacer: '" + text + "'!"); SoundReplacementCollection soundReplacementCollection = JSONDataLoader.LoadFromFile<SoundReplacementCollection>(text); if (soundReplacementCollection == null) { continue; } soundReplacementCollection.Pack = pack; if (soundReplacementCollection.Condition is ConstantCondition constantCondition && !constantCondition.Value) { Debuggers.SoundReplacementLoader?.Log("skipping '" + LogFormats.FormatFilePath(soundReplacementCollection.FilePath) + "' because collection is marked as constant and has a value of false."); skippedStats.Collections++; } else { if (!IValidatable.LogAndCheckValidationResult("loading '" + LogFormats.FormatFilePath(text) + "'", soundReplacementCollection.Validate(), pack.Logger)) { continue; } List<IValidatable.ValidationResult> list2 = new List<IValidatable.ValidationResult>(); foreach (SoundReplacementGroup replacement in soundReplacementCollection.Replacements) { replacement.Parent = soundReplacementCollection; if (replacement.Condition is ConstantCondition constantCondition2 && !constantCondition2.Value) { Debuggers.SoundReplacementLoader?.Log("skipping a replacement in '" + LogFormats.FormatFilePath(soundReplacementCollection.FilePath) + "' because group is marked as constant and has a value of false."); skippedStats.Groups++; continue; } List<IValidatable.ValidationResult> list3 = replacement.Validate(); foreach (string item in replacement.Matches.ToList()) { if (item.StartsWith("#")) { replacement.Matches.Remove(item); Dictionary<string, List<string>> dictionary = mappings; string text2 = item; if (dictionary.TryGetValue(text2.Substring(1, text2.Length - 1), out var value)) { replacement.Matches.AddRange(value); } else { list3.Add(new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Mapping: '" + item + "' has not been found. If it's part of a soft dependency, make sure to use a 'mod_installed' condition with 'constant' enabled.")); } } } if (list3.Count != 0) { list2.AddRange(list3); continue; } foreach (SoundInstance sound in replacement.Sounds) { sound.Parent = replacement; list3.AddRange(sound.Validate()); } if (list3.Count != 0) { list2.AddRange(list3); continue; } List<string> collection = replacement.Matches.Select((string match) => (match.Split(":").Length != 2) ? match : ("*:" + match)).ToList(); replacement.Matches.Clear(); replacement.Matches.AddRange(collection); } if (IValidatable.LogAndCheckValidationResult("loading '" + LogFormats.FormatFilePath(text) + "'", list2, pack.Logger)) { list.Add(soundReplacementCollection); } } } return list; } private static LoadSoundOperation StartWebRequestOperation(SoundPack pack, SoundInstance sound, AudioType type) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) string text = Path.Combine(pack.PackFolder, "sounds", sound.Sound); UnityWebRequest audioClip = UnityWebRequestMultimedia.GetAudioClip(text, type); return new LoadSoundOperation(sound, audioClip.SendWebRequest()); } static SoundPackLoadPipeline() { SoundPackLoadPipeline.OnFinishedPipeline = delegate { }; mappings = new Dictionary<string, List<string>>(); audioExtensions = new Dictionary<string, AudioType> { { ".ogg", (AudioType)14 }, { ".wav", (AudioType)20 }, { ".mp3", (AudioType)13 } }; } } internal static class SoundReplacementHandler { private const int TOKEN_PARENT_NAME = 0; private const int TOKEN_OBJECT_NAME = 1; private const int TOKEN_CLIP_NAME = 2; private static readonly string[] _suffixesToRemove = new string[1] { "(Clone)" }; private static readonly Dictionary<int, string> _cachedObjectNames = new Dictionary<int, string>(); private static readonly StringBuilder _builder = new StringBuilder(); internal static void Register() { SceneManager.sceneLoaded += delegate(Scene scene, LoadSceneMode _) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) _cachedObjectNames.Clear(); AudioSource[] array = Object.FindObjectsOfType<AudioSource>(true); foreach (AudioSource val in array) { if (!(((Component)val).gameObject.scene != scene)) { CheckAudioSource(val); } } }; } internal static void CheckAudioSource(AudioSource source) { if (!source.playOnAwake || !((Behaviour)source).enabled) { return; } AudioSourceAdditionalData orCreate = AudioSourceAdditionalData.GetOrCreate(source); if (TryReplaceAudio(source, orCreate.OriginalClip, out var replacement)) { source.Stop(); if (!((Object)(object)replacement == (Object)null)) { orCreate.RealClip = replacement; source.Play(); } } } internal static bool TryReplaceAudio(AudioSource source, AudioClip clip, out AudioClip replacement) { replacement = null; if ((Object)(object)((Component)source).gameObject == (Object)null) { return false; } AudioSourceAdditionalData orCreate = AudioSourceAdditionalData.GetOrCreate(source); if (orCreate.ReplacedWith != null && orCreate.ReplacedWith.Parent.UpdateEveryFrame) { return false; } if (orCreate.DisableReplacing) { return false; } string[] name = ArrayPool<string>.Shared.Rent(3); if (!TryProcessName(ref name, source, clip) || !TryGetReplacementClip(name, out var group, out var clip2, orCreate.CurrentContext ?? DefaultConditionContext.DEFAULT)) { ArrayPool<string>.Shared.Return(name); return false; } ArrayPool<string>.Shared.Return(name); ((Object)clip2).name = ((Object)clip).name; replacement = clip2; orCreate.ReplacedWith = group; if (group.Parent.UpdateEveryFrame) { Debuggers.UpdateEveryFrame?.Log("swapped to a clip that uses update_every_frame !!!"); } return true; } private static string TrimObjectName(GameObject gameObject) { if (_cachedObjectNames.ContainsKey(((object)gameObject).GetHashCode())) { return _cachedObjectNames[((object)gameObject).GetHashCode()]; } _builder.Clear(); _builder.Append(((Object)gameObject).name); string[] suffixesToRemove = _suffixesToRemove; foreach (string oldValue in suffixesToRemove) { _builder.Replace(oldValue, string.Empty); } for (int j = 0; j < _builder.Length; j++) { if (_builder[j] == '(') { int num = j; for (j++; j < _builder.Length && char.IsDigit(_builder[j]); j++) { } if (j < _builder.Length && _builder[j] == ')') { _builder.Remove(num, j - num + 1); j = num - 1; } } } int num2 = _builder.Length; while (num2 > 0 && _builder[num2 - 1] == ' ') { num2--; } _builder.Remove(num2, _builder.Length - num2); string text = _builder.ToString(); _cachedObjectNames[((object)gameObject).GetHashCode()] = text; return text; } private static bool TryProcessName(ref string[] name, AudioSource source, AudioClip clip) { if ((Object)(object)clip == (Object)null) { return false; } if ((Object)(object)((Component)source).transform.parent == (Object)null) { name[0] = "*"; } else { name[0] = TrimObjectName(((Component)((Component)source).transform.parent).gameObject); } name[1] = TrimObjectName(((Component)source).gameObject); name[2] = ((Object)clip).name; if (SoundReportHandler.CurrentReport != null) { string caller; try { caller = new StackTrace(fNeedFileInfo: true).GetFrame(5).GetMethod().DeclaringType.Name; } catch { caller = "unknown caller"; } SoundReport.PlayedSound playedSound = new SoundReport.PlayedSound(name[0] + ":" + name[1] + ":" + name[2], caller, source.playOnAwake); if (!SoundReportHandler.CurrentReport.PlayedSounds.Any(playedSound.Equals)) { SoundReportHandler.CurrentReport.PlayedSounds.Add(playedSound); } } Debuggers.MatchStrings?.Log(name[0] + ":" + name[1] + ":" + name[2]); return true; } private static bool TryGetReplacementClip(string[] name, out SoundReplacementGroup group, out AudioClip clip, IContext context) { group = null; clip = null; if (name == null) { return false; } Debuggers.SoundReplacementHandler?.Log("beginning replacement attempt for " + name[2]); if (!SoundPackDataHandler.SoundReplacements.TryGetValue(name[2], out var value)) { return false; } Debuggers.SoundReplacementHandler?.Log("sound dictionary hit"); value = value.Where((SoundReplacementGroup it) => it.Parent.Evaluate(context) && it.Evaluate(context) && CheckGroupMatches(it, name)).ToList(); if (value.Count == 0) { return false; } Debuggers.SoundReplacementHandler?.Log("sound group that matches"); group = value[Random.Range(0, value.Count)]; List<SoundInstance> list = group.Sounds.Where((SoundInstance it) => it.Evaluate(context)).ToList(); if (list.Count == 0) { return false; } Debuggers.SoundReplacementHandler?.Log("has valid sounds"); int totalWeight = 0; list.ForEach(delegate(SoundInstance replacement) { totalWeight += replacement.Weight; }); int num = Random.Range(0, totalWeight + 1); SoundInstance soundInstance = null; foreach (SoundInstance item in list) { soundInstance = item; num -= soundInstance.Weight; if (num <= 0) { break; } } clip = soundInstance.Clip; Debuggers.SoundReplacementHandler?.Log("done, dumping stack trace!"); Debuggers.SoundReplacementHandler?.Log(string.Join(", ", group.Matches)); Debuggers.SoundReplacementHandler?.Log(((Object)clip).name); Debuggers.SoundReplacementHandler?.Log(new StackTrace(fNeedFileInfo: true).ToString().Trim()); return true; } private static bool CheckGroupMatches(SoundReplacementGroup group, string[] a) { foreach (string match in group.Matches) { if (MatchStrings(a, match)) { return true; } } return false; } private static bool MatchStrings(string[] a, string b) { string[] array = b.Split(":"); if (array[0] != "*" && array[0] != a[0]) { return false; } if (array[1] != "*" && array[1] != a[1]) { return false; } return a[2] == array[2]; } } } namespace loaforcsSoundAPI.SoundPacks.Data { public interface IPackData { SoundPack Pack { get; internal set; } } public class SoundInstance : Conditional { [field: NonSerialized] public SoundReplacementGroup Parent { get; internal set; } public string Sound { get; private set; } public int Weight { get; private set; } [field: NonSerialized] public AudioClip Clip { get; internal set; } public override SoundPack Pack { get { return Parent.Pack; } set { if (Parent.Pack != null) { throw new InvalidOperationException("Pack has already been set."); } Parent.Pack = value; } } [JsonConstructor] internal SoundInstance() { } public SoundInstance(SoundReplacementGroup parent, int weight, AudioClip clip) { Parent = parent; Weight = weight; Clip = clip; parent.AddSoundReplacement(this); } public override List<IValidatable.ValidationResult> Validate() { List<IValidatable.ValidationResult> list = base.Validate(); if (!File.Exists(Path.Combine(Pack.PackFolder, "sounds", Sound))) { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Sound '" + Sound + "' couldn't be found or doesn't exist!")); } else if (!SoundPackLoadPipeline.audioExtensions.ContainsKey(Path.GetExtension(Sound))) { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Audio type: '" + Path.GetExtension(Sound) + "' is not supported!")); } return list; } } public class SoundPack : IValidatable { [NonSerialized] private readonly Dictionary<string, object> _configData = new Dictionary<string, object>(); private ManualLogSource _logger; [JsonProperty] private Dictionary<string, JObject> config { get; set; } public string Name { get; private set; } public string GUID => "soundpack." + Name; [CanBeNull] public string Version { get; private set; } public string PackFolder { get; internal set; } [field: NonSerialized] public List<SoundReplacementCollection> ReplacementCollections { get; private set; } = new List<SoundReplacementCollection>(); public ManualLogSource Logger { get { if (_logger == null) { _logger = Logger.CreateLogSource(GUID); } return _logger; } } [JsonConstructor] internal SoundPack() { } internal void Bind(ConfigFile file) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Invalid comparison between Unknown and I4 //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Invalid comparison between Unknown and I4 if (config == null || config.Count == 0) { return; } loaforcsSoundAPI.Logger.LogDebug((object)"handling config"); JToken val2 = default(JToken); foreach (KeyValuePair<string, JObject> item in config) { string[] array = item.Key.Split(":"); string text = array[0]; string text2 = array[1]; JToken val = item.Value["default"]; string text3 = (item.Value.TryGetValue("description", ref val2) ? ((object)val2).ToString() : "no description defined!"); JTokenType type = val.Type; if ((int)type != 8) { if ((int)type != 9) { throw new NotImplementedException("WHAT"); } _configData[item.Key] = file.Bind<bool>(text, text2, (bool)val, text3).Value; } else { _configData[item.Key] = file.Bind<string>(text, text2, (string)val, text3).Value; } } } public SoundPack(string name, string packFolder) { Name = name; PackFolder = packFolder; } internal bool TryGetConfigValue(string id, out object returnValue) { returnValue = null; if (!_configData.TryGetValue(id, out var value)) { return false; } returnValue = value; return true; } public List<IValidatable.ValidationResult> Validate() { //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Invalid comparison between Unknown and I4 //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Invalid comparison between Unknown and I4 //IL_0154: Unknown result type (might be due to invalid IL or missing references) List<IValidatable.ValidationResult> list = new List<IValidatable.ValidationResult>(); if (string.IsNullOrEmpty(Name)) { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "'name' can not be missing or empty!")); return list; } string name = Name; foreach (char c in name) { if (!char.IsLetter(c) && c != '_') { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, $"'name' can not contain special character '{c}'!")); } } if (string.IsNullOrEmpty(Version) && PackLoadingConfig.MetadataSpoofing) { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.WARN, "'version' should not be empty, defaulting to '1.0.0' (needed by MetadataSpoofing config)")); } if (config == null) { return list; } JToken val = default(JToken); foreach (KeyValuePair<string, JObject> item in config) { string[] array = item.Key.Split(":"); if (array.Length != 2) { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "'" + item.Key + "' is not a valid key for config! It must be 'section:name' with exactly one colon!")); } if (!item.Value.TryGetValue("default", ref val)) { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "'" + item.Key + "' does not have a 'default' value! This is needed to get what type the config is!")); } else if ((int)val.Type != 9 && (int)val.Type != 8) { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, $"'{item.Key}' is of unsupported type: '{val.Type}'! Only supported types are strings/text or booleans!")); } if (!item.Value.ContainsKey("description")) { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.WARN, "'" + item.Key + "' does not have a description.")); } } return list; } } public class SoundReplacementCollection : Conditional, IFilePathAware, IPackData { [field: NonSerialized] public override SoundPack Pack { get; set; } public bool UpdateEveryFrame { get; private set; } public bool Synced { get; private set; } public List<SoundReplacementGroup> Replacements { get; private set; } = new List<SoundReplacementGroup>(); public string FilePath { get; set; } [JsonConstructor] internal SoundReplacementCollection() { } public SoundReplacementCollection(SoundPack pack) { Pack = pack; pack.ReplacementCollections.Add(this); } internal void AddSoundReplacementGroup(SoundReplacementGroup group) { Replacements.Add(group); } } public class SoundReplacementGroup : Conditional { [field: NonSerialized] public SoundReplacementCollection Parent { get; internal set; } public List<string> Matches { get; private set; } public List<SoundInstance> Sounds { get; private set; } = new List<SoundInstance>(); public override SoundPack Pack { get { return Parent.Pack; } set { if (Parent.Pack != null) { throw new InvalidOperationException("Pack has already been set."); } Parent.Pack = value; } } [JsonConstructor] internal SoundReplacementGroup() { } public SoundReplacementGroup(SoundReplacementCollection parent, List<string> matches) { Parent = parent; Matches = matches; if (SoundPackDataHandler.LoadedPacks.Contains(parent.Pack)) { throw new InvalidOperationException("SoundPack has already been registered, trying to add a new SoundReplacementGroup does not work!"); } parent.AddSoundReplacementGroup(this); } internal void AddSoundReplacement(SoundInstance sound) { Sounds.Add(sound); } public override List<IValidatable.ValidationResult> Validate() { List<IValidatable.ValidationResult> list = base.Validate(); foreach (string match in Matches) { if (string.IsNullOrEmpty(match)) { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Match string can not be empty!")); continue; } string[] array = match.Split(":"); if (array.Length == 1) { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "'" + match + "' is not valid! If you mean to match to all Audio clips with this name you must explicitly do '*:" + match + "'.")); } if (array.Length > 3) { list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.WARN, $"'{match}' has more than 3 parts! SoundAPI will handle this as '{match[0]}:{match[1]}:{match[2]}', discarding the rest!")); } } return list; } } } namespace loaforcsSoundAPI.SoundPacks.Data.Conditions { public abstract class Condition : IValidatable { [field: NonSerialized] public Conditional Parent { get; internal set; } protected SoundPack Pack => Parent.Pack; public bool? Constant { get; private set; } protected internal virtual void OnRegistered() { } public abstract bool Evaluate(IContext context); public virtual List<IValidatable.ValidationResult> Validate() { return new List<IValidatable.ValidationResult>(); } protected bool EvaluateRangeOperator(int number, string condition) { return EvaluateRangeOperator((double)number, condition); } protected bool EvaluateRangeOperator(float number, string condition) { return EvaluateRangeOperator((double)number, condition); } protected bool EvaluateRangeOperator(double value, string condition) { string[] array = condition.Split(".."); if (array.Length == 1) { if (double.TryParse(array[0], out var result)) { return value == result; } return false; } if (array.Length == 2) { double result2; if (array[0] == "") { result2 = double.MinValue; } else if (!double.TryParse(array[0], out result2)) { return false; } double result3; if (array[1] == "") { result3 = double.MaxValue; } else if (!double.TryParse(array[1], out result3)) { return false; } if (value >= result2) { return value <= result3; } return false; } return false; } protected bool ValidateRangeOperator(string condition, out IValidatable.ValidationResult result) { result = null; if (string.IsNullOrEmpty(condition)) { result = new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Range operator can not be missing or empty!"); return false; } string[] array = condition.Split(".."); int num = array.Length; if (num <= 2) { switch (num) { case 1: { if (!double.TryParse(array[0], out var _)) { result = new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Failed to parse: '" + array[0] + "' as a number!"); } break; } case 2: { double num2; if (array[0] == "") { num2 = double.MinValue; } else if (!double.TryParse(array[0], out num2)) { result = new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Failed to parse: '" + array[0] + "' as a number!"); } double num3; if (array[1] == "") { num3 = double.MaxValue; } else if (!double.TryParse(array[1], out num3)) { result = new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Failed to parse: '" + array[1] + "' as a number!"); } break; } } } else { result = new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Range operator: '" + condition + "' uses .. more than once!"); } return result == null; } protected static void LogDebug(string name, object message) { Debuggers.ConditionsInfo?.Log($"({name}) {message}"); } } internal sealed class InvalidCondition : Condition { [CompilerGenerated] private string <type>P; public InvalidCondition(string type) { <type>P = type; base..ctor(); } public override bool Evaluate(IContext context) { return false; } public override List<IValidatable.ValidationResult> Validate() { if (string.IsNullOrEmpty(<type>P)) { return new List<IValidatable.ValidationResult>(1) { new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Condition must have a type!") }; } return new List<IValidatable.ValidationResult>(1) { new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "'" + <type>P + "' is not a valid condition type!") }; } } internal sealed class ConstantCondition : Condition { public static ConstantCondition TRUE = new ConstantCondition(constant: true); public static ConstantCondition FALSE = new ConstantCondition(constant: false); public bool Value { get; private set; } private ConstantCondition(bool constant) { Value = constant; } public override bool Evaluate(IContext context) { return Value; } } public abstract class Condition<TContext> : Condition where TContext : IContext { public override bool Evaluate(IContext context) { if (!(context is TContext context2)) { return EvaluateFallback(context); } return EvaluateWithContext(context2); } protected abstract bool EvaluateWithContext(TContext context); protected virtual bool EvaluateFallback(IContext context) { return false; } } public abstract class Conditional : IValidatable, IPackData { public Condition Condition { get; set; } public abstract SoundPack Pack { get; set; } public bool Evaluate(IContext context) { if (Condition == null) { return true; } return Condition.Evaluate(context); } public virtual List<IValidatable.ValidationResult> Validate() { if (Condition == null) { return new List<IValidatable.ValidationResult>(); } Condition.Parent = this; Condition.OnRegistered(); return Condition.Validate(); } } public interface IContext { } internal class DefaultConditionContext : IContext { internal static readonly DefaultConditionContext DEFAULT = new DefaultConditionContext(); private DefaultConditionContext() { } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] [MeansImplicitUse] public class SoundAPIConditionAttribute : Attribute { public string ID { get; private set; } public bool IsDeprecated { get; private set; } public string DeprecationReason { get; private set; } public SoundAPIConditionAttribute(string id, bool deprecated = false, string deprecationReason = null) { ID = id; IsDeprecated = deprecated; DeprecationReason = deprecationReason; base..ctor(); } } } namespace loaforcsSoundAPI.SoundPacks.Conditions { public abstract class LogicGateCondition : Condition { public abstract Condition[] Conditions { get; protected set; } protected abstract string ValidateWarnMessage { get; } protected internal override void OnRegistered() { Condition[] conditions = Conditions; foreach (Condition condition in conditions) { condition.Parent = base.Parent; condition.OnRegistered(); } } public override List<IValidatable.ValidationResult> Validate() { if (Conditions.Length == 0) { return new List<IValidatable.ValidationResult>(1) { new IValidatable.ValidationResult(IValidatable.ResultType.WARN, ValidateWarnMessage) }; } return new List<IValidatable.ValidationResult>(); } protected static bool And(Condition[] conditions, IContext context) { foreach (Condition condition in conditions) { if (condition is InvalidCondition) { return false; } if (!condition.Evaluate(context)) { return false; } } return true; } protected static bool Or(Condition[] conditions, IContext context) { foreach (Condition condition in conditions) { if (condition is InvalidCondition) { return false; } if (condition.Evaluate(context)) { return true; } } return false; } } [SoundAPICondition("and", false, null)] internal class AndCondition : LogicGateCondition { public override Condition[] Conditions { get; protected set; } protected override string ValidateWarnMessage => "'and' condition has no conditions and will always return true!"; public override bool Evaluate(IContext context) { return LogicGateCondition.And(Conditions, context); } } [SoundAPICondition("nand", false, null)] internal class NandCondition : LogicGateCondition { public override Condition[] Conditions { get; protected set; } protected override string ValidateWarnMessage => "'nand' condition has no conditions and will always return false!"; public override bool Evaluate(IContext context) { return !LogicGateCondition.And(Conditions, context); } } [SoundAPICondition("config", false, null)] internal class ConfigCondition : Condition { public string Config { get; private set; } public object Value { get; private set; } public override bool Evaluate(IContext context) { if (!base.Pack.TryGetConfigValue(Config, out var returnValue)) { return false; } if (Value == null) { if (returnValue is bool) { return (bool)returnValue; } if (returnValue is string value) { return string.IsNullOrEmpty(value); } return false; } if (returnValue is bool flag) { return flag == (bool)Value; } if (returnValue is string text) { return text == (string)Value; } return false; } public override List<IValidatable.ValidationResult> Validate() { if (!base.Pack.TryGetConfigValue(Config, out var returnValue)) { List<IValidatable.ValidationResult> list = new List<IValidatable.ValidationResult>(1); list.Add(new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Config '" + Config + "' does not exist on SoundPack '" + base.Pack.Name + "'")); return list; } if (Value != null && returnValue.GetType() != Value.GetType()) { return new List<IValidatable.ValidationResult>(1) { new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, $"Config '{Config}' has a type of: '{returnValue.GetType()}' but the Value type is '{Value.GetType()}'!") }; } return new List<IValidatable.ValidationResult>(); } } [SoundAPICondition("counter", false, null)] public class CounterCondition : Condition { private int _count; public string Value { get; private set; } public int? ResetsAt { get; private set; } public override bool Evaluate(IContext context) { Condition.LogDebug("counter", $"counting: {_count} -> {_count + 1}"); _count++; bool flag = EvaluateRangeOperator(_count, Value); Condition.LogDebug("counter", $"is {_count} in range ({Value})? {flag}"); if (_count >= ResetsAt) { _count = 0; Condition.LogDebug("counter", "reset count to 0."); } return flag; } public override List<IValidatable.ValidationResult> Validate() { if (!ValidateRangeOperator(Value, out var result)) { return new List<IValidatable.ValidationResult>(1) { result }; } return new List<IValidatable.ValidationResult>(); } } [SoundAPICondition("mod_installed", false, null)] internal class ModInstalledCondition : Condition { public string Value { get; private set; } public override bool Evaluate(IContext context) { return Chainloader.PluginInfos.ContainsKey(Value); } public override List<IValidatable.ValidationResult> Validate() { if (string.IsNullOrEmpty(Value)) { return new List<IValidatable.ValidationResult>(1) { new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "Value on 'mod_installed' must be there and must not be empty.") }; } return new List<IValidatable.ValidationResult>(); } } [SoundAPICondition("not", false, null)] internal class NotCondition : Condition { public Condition Condition { get; private set; } protected internal override void OnRegistered() { if (Condition != null) { Condition.Parent = base.Parent; Condition.OnRegistered(); } } public override bool Evaluate(IContext context) { if (Condition is InvalidCondition) { return false; } return !Condition.Evaluate(context); } public override List<IValidatable.ValidationResult> Validate() { if (Condition == null) { return new List<IValidatable.ValidationResult>(1) { new IValidatable.ValidationResult(IValidatable.ResultType.FAIL, "'not' condition has no valid condition to invert!") }; } return new List<IValidatable.ValidationResult>(); } } [SoundAPICondition("or", false, null)] internal class OrCondition : LogicGateCondition { public override Condition[] Conditions { get; protected set; } protected override string ValidateWarnMessage => "'or' condition has no conditions and will always return false!"; public override bool Evaluate(IContext context) { return LogicGateCondition.Or(Conditions, context); } } [SoundAPICondition("nor", false, null)] internal class NorCondition : LogicGateCondition { public override Condition[] Conditions { get; protected set; } protected override string ValidateWarnMessage => "'nor' condition has no conditions and will always return true!"; public override bool Evaluate(IContext context) { return !LogicGateCondition.Or(Conditions, context); } } } namespace loaforcsSoundAPI.Reporting { public static class SoundReportHandler { private const string _datetimeFormat = "dd_MM_yyyy-HH_mm"; private static Action<StreamWriter, SoundReport> _reportSections = delegate { }; public static SoundReport CurrentReport { get; private set; } public static void AddReportSection(string header, Action<StreamWriter, SoundReport> callback) { _reportSections = (Action<StreamWriter, SoundReport>)Delegate.Combine(_reportSections, (Action<StreamWriter, SoundReport>)delegate(StreamWriter stream, SoundReport report) { stream.WriteLine("## " + header); callback(stream, report); stream.WriteLine(""); stream.WriteLine(""); }); } internal static void Register() { Directory.CreateDirectory(GetFolder()); CurrentReport = new SoundReport(); loaforcsSoundAPI.Logger.LogWarning((object)"SoundAPI is generating a report!"); loaforcsSoundAPI.Logger.LogInfo((object)("The report will be located at '" + LogFormats.FormatFilePath(Path.Combine(GetFolder(), GetFileName(CurrentReport, ".md"))))); Application.quitting += delegate { WriteReportToFile(CurrentReport); }; SoundPackLoadPipeline.OnFinishedPipeline += delegate { foreach (SoundPack loadedPack in SoundPackDataHandler.LoadedPacks) { CurrentReport.SoundPackNames.Add(loadedPack.Name); } }; AddReportSection("General Information", delegate(StreamWriter stream, SoundReport report) { stream.WriteLine("SoundAPI version: `2.0.8` <br/><br/>"); stream.WriteLine($"Audio-clips loaded: `{report.AudioClipsLoaded}` <br/>"); stream.WriteLine($"Match strings registered: `{SoundPackDataHandler.SoundReplacements.Values.Sum((List<SoundReplacementGroup> it) => it.Count)}` <br/>"); WriteList("Loaded sound-packs", stream, report.SoundPackNames); }); AddReportSection("Dynamic Data", delegate(StreamWriter stream, SoundReport _) { if (SoundAPI.CurrentNetworkAdapter != null) { stream.WriteLine("Network Adapter: `" + SoundAPI.CurrentNetworkAdapter.Name + "` <br/><br/>"); } WriteList("Registered Conditions", stream, SoundPackDataHandler.conditionFactories.Keys.ToList()); }); AddReportSection("All Played Sounds", delegate(StreamWriter stream, SoundReport report) { WriteList(null, stream, report.PlayedSounds.Select((SoundReport.PlayedSound it) => it.FormatForReport()).ToList()); }); } internal static void Bind(ConfigFile file) { if (file.Bind<bool>("Developer", "GenerateReports", false, "While true SoundAPI will generate a json and markdown file per session that records information SoundAPI and related mods find.").Value) { Register(); } } private static string GetFileName(SoundReport report, string extension) { return "generated_report-" + report.StartedAt.ToString("dd_MM_yyyy-HH_mm") + extension; } private static string GetFolder() { return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "reports"); } private static void WriteReportToFile(SoundReport report) { using StreamWriter streamWriter = new StreamWriter(Path.Combine(GetFolder(), GetFileName(report, ".md"))); streamWriter.WriteLine("# Generated Report"); streamWriter.WriteLine($"At {report.StartedAt} :3"); streamWriter.WriteLine(""); _reportSections(streamWriter, report); streamWriter.Flush(); streamWriter.Close(); using StreamWriter streamWriter2 = new StreamWriter(Path.Combine(GetFolder(), GetFileName(report, ".json"))); streamWriter2.WriteLine(JsonConvert.SerializeObject((object)report, (Formatting)1)); } public static void WriteList(string header, StreamWriter stream, ICollection<string> list) { if (!string.IsNullOrEmpty(header)) { stream.WriteLine($"### {header} (`{list.Count}`)"); } stream.WriteLine(string.Join("<br/>\n", list.Select((string it) => "- " + it))); } public static void WriteEnum<T>(string header, StreamWriter stream) where T : Enum { WriteList(header, stream, (from it in Enum.GetValues(typeof(T)).OfType<T>() select it.ToString().ToLowerInvariant()).ToList()); } } } namespace loaforcsSoundAPI.Reporting.Data { public class SoundReport { public class PlayedSound { public string MatchString { get; private set; } public string Caller { get; private set; } public bool IsPlayOnAwake { get; private set; } public PlayedSound(string matchString, string caller, bool isPlayOnAwake) { MatchString = matchString; Caller = caller; IsPlayOnAwake = isPlayOnAwake; base..ctor(); } public override bool Equals(object obj) { if (!(obj is PlayedSound other)) { return false; } return Equals(other); } protected bool Equals(PlayedSound other) { if (MatchString == other.MatchString && Caller == other.Caller) { return IsPlayOnAwake == other.IsPlayOnAwake; } return false; } public override int GetHashCode() { return HashCode.Combine(MatchString, Caller, IsPlayOnAwake); } public string FormatForReport() { return $"Match String: {MatchString}, Caller: {Caller}, IsPlayOnAwake: {IsPlayOnAwake}"; } } public DateTime StartedAt { get; private set; } = DateTime.Now; public List<PlayedSound> PlayedSounds { get; private set; } = new List<PlayedSound>(); public List<string> SoundPackNames { get; private set; } = new List<string>(); public int AudioClipsLoaded { get; set; } } } namespace loaforcsSoundAPI.Core { public class AudioSourceAdditionalData { private SoundReplacementGroup _replacedWith; public AudioSource Source { get; private set; } public AudioClip OriginalClip { get; internal set; } public AudioClip RealClip { get { using (new SpoofBypassContext()) { if (Debuggers.AudioSourceAdditionalData != null) { string text = "null"; if (Object.op_Implicit((Object)(object)Source.clip)) { text = ((Object)Source.clip).name; } string text2 = "null"; if (Object.op_Implicit((Object)(object)OriginalClip)) { text2 = ((Object)OriginalClip).name; } Debuggers.AudioSourceAdditionalData.Log("(" + ((Object)Source).name + ") Getting real clip: " + text + " (original clip: " + text2 + ")"); } return Source.clip; } } set { using (new SpoofBypassContext()) { if (Debuggers.AudioSourceAdditionalData != null) { string text = "null"; if (Object.op_Implicit((Object)(object)OriginalClip)) { text = ((Object)OriginalClip).name; } Debuggers.AudioSourceAdditionalData?.Log("(" + ((Object)Source).name + ") Setting real clip: " + ((Object)value).name + " (original clip: " + text + ")"); } Source.clip = value; } } } internal SoundReplacementGroup ReplacedWith { get { return _replacedWith; } set { _replacedWith = value; if (RequiresUpdateFunction()) { if (!SoundAPIAudioManager.liveAudioSourceData.Contains(this)) { SoundAPIAudioManager.liveAudioSourceData.Add(this); } } else if (SoundAPIAudioManager.liveAudioSourceData.Contains(this)) { SoundAPIAudioManager.liveAudioSourceData.Remove(this); } } } public bool DisableReplacing { get; private set; } public IContext CurrentContext { get; set; } internal AudioSourceAdditionalData(AudioSource source) { Source = source; } internal void Update() { if (RequiresUpdateFunction() && AudioSourceIsPlaying()) { Debuggers.UpdateEveryFrame?.Log("success: updating every frame for " + ((Object)Source).name); IContext context = CurrentContext ?? DefaultConditionContext.DEFAULT; SoundInstance soundInstance = ReplacedWith.Sounds.FirstOrDefault((SoundInstance x) => x.Evaluate(context)); if (soundInstance != null && !((Object)(object)soundInstance.Clip == (Object)(object)Source.clip)) { Debuggers.UpdateEveryFrame?.Log("new clip found, swapping!!"); float time = Source.time; Source.clip = soundInstance.Clip; Source.Play(); Source.time = time; Debuggers.UpdateEveryFrame?.Log("new clip found, swapped"); } } } private bool RequiresUpdateFunction() { if (ReplacedWith != null) { return ReplacedWith.Parent.UpdateEveryFrame; } return false; } private bool AudioSourceIsPlaying() { if (Object.op_Implicit((Object)(object)Source) && ((Behaviour)Source).enabled) { return Source.isPlaying; } return false; } public static AudioSourceAdditionalData GetOrCreate(AudioSource source) { if (SoundAPIAudioManager.audioSourceData.TryGetValue(source, out var value)) { return value; } value = new AudioSourceAdditionalData(source); value.OriginalClip = value.RealClip; SoundAPIAudioManager.audioSourceData[source] = value; return value; } } internal static class Debuggers { internal static DebugLogSource AudioSourceAdditionalData; internal static DebugLogSource SoundReplacementLoader; internal static DebugLogSource SoundReplacementHandler; internal static DebugLogSource MatchStrings; internal static DebugLogSource ConditionsInfo; internal static DebugLogSource UpdateEveryFrame; internal static DebugLogSource AudioClipSpoofing; internal static void Bind(ConfigFile file) { FieldInfo[] fields = typeof(Debuggers).GetFields(BindingFlags.Static | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (file.Bind<bool>("InternalDebugging", fieldInfo.Name, false, "Enable/Disable this DebugLogSource. Should only be true if you know what you are doing or have been asked to.").Value) { fieldInfo.SetValue(null, new DebugLogSource(fieldInfo.Name)); loaforcsSoundAPI.Logger.LogDebug((object)("created a DebugLogSource for " + fieldInfo.Name + "!")); } else { fieldInfo.SetValue(null, null); loaforcsSoundAPI.Logger.LogDebug((object)("no DebugLogSource for " + fieldInfo.Name + ".")); } } } } internal class DebugLogSource { [CompilerGenerated] private string <title>P; public DebugLogSource(string title) { <title>P = title; base..ctor(); } internal void Log(object message) { loaforcsSoundAPI.Logger.LogDebug((object)$"[Debug-{<title>P}] {message}"); } } internal class SoundAPIAudioManager : MonoBehaviour { internal static readonly Dictionary<AudioSource, AudioSourceAdditionalData> audioSourceData = new Dictionary<AudioSource, AudioSourceAdditionalData>(); internal static readonly List<AudioSourceAdditionalData> liveAudioSourceData = new List<AudioSourceAdditionalData>(); private static SoundAPIAudioManager Instance; private void Awake() { SceneManager.sceneLoaded += delegate { if (!Object.op_Implicit((Object)(object)Instance)) { SpawnManager(); } RunCleanup(); }; } internal static void SpawnManager() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown loaforcsSoundAPI.Logger.LogInfo((object)"Starting AudioManager."); GameObject val = new GameObject("SoundAPI_AudioManager"); Object.DontDestroyOnLoad((Object)(object)val); Instance = val.AddComponent<SoundAPIAudioManager>(); } private void Update() { Debuggers.UpdateEveryFrame?.Log("sanity check: soundapi audio manager is running!"); foreach (AudioSourceAdditionalData liveAudioSourceDatum in liveAudioSourceData) { liveAudioSourceDatum.Update(); } } private void OnDisable() { loaforcsSoundAPI.Logger.LogDebug((object)"manager disabled"); } private void OnDestroy() { loaforcsSoundAPI.Logger.LogDebug((object)"manager destroyed"); } private static void RunCleanup() { loaforcsSoundAPI.Logger.LogDebug((object)"cleaning up old audio source entries"); AudioSource[] array = audioSourceData.Keys.ToArray(); foreach (AudioSource val in array) { if (!Object.op_Implicit((Object)(object)val)) { if (liveAudioSourceData.Contains(audioSourceData[val])) { liveAudioSourceData.Remove(audioSourceData[val]); } audioSourceData.Remove(val); } } } } internal class SpoofBypassContext : IDisposable { public SpoofBypassContext() { AudioSourcePatch.bypassSpoofing = true; } public void Dispose() { AudioSourcePatch.bypassSpoofing = false; } } } namespace loaforcsSoundAPI.Core.Util { public class AdaptiveConfigEntry { public AdaptiveBool State { get; private set; } public bool DefaultValue { get; private set; } public bool? OverrideValue { get; set; } public bool Value => State switch { AdaptiveBool.Enabled => true, AdaptiveBool.Disabled => false, _ => OverrideValue ?? DefaultValue, }; public AdaptiveConfigEntry(AdaptiveBool state, bool defaultValue) { State = state; DefaultValue = defaultValue; } } public enum AdaptiveBool { Automatic, Enabled, Disabled } internal static class LogFormats { internal static string FormatFilePath(string path) { return $"plugins{Path.DirectorySeparatorChar}{Path.Combine(Path.GetRelativePath(Paths.PluginPath, path))}"; } } } namespace loaforcsSoundAPI.Core.Util.Extensions { internal static class AssemblyExtensions { internal static IEnumerable<Type> GetLoadableTypes(this Assembly assembly) { if (assembly == null) { throw new ArgumentNullException("assembly"); } try { return assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { return ex.Types.Where((Type t) => t != null); } } } public static class AsyncOperationExtensions { public static TaskAwaiter GetAwaiter(this AsyncOperation asyncOp) { TaskCompletionSource<AsyncOperation> tcs = new TaskCompletionSource<AsyncOperation>(); asyncOp.completed += delegate(AsyncOperation operation) { tcs.SetResult(operation); }; return ((Task)tcs.Task).GetAwaiter(); } } public static class ConfigFileExtensions { public static AdaptiveConfigEntry BindAdaptive(this ConfigFile file, string section, string key, bool defaultValue, string description) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown AdaptiveBool value = file.Bind<AdaptiveBool>(section, key, AdaptiveBool.Automatic, new ConfigDescription($"{description}\nAutomatic default: {defaultValue}", (AcceptableValueBase)null, Array.Empty<object>())).Value; return new AdaptiveConfigEntry(value, defaultValue); } } public static class ListExtensions { public static void AddUnique<T>(this List<T> list, T item) { if (!list.Contains(item)) { list.Add(item); } } } } namespace loaforcsSoundAPI.Core.Patches { [HarmonyPatch(typeof(AudioSource))] internal static class AudioSourcePatch { internal static bool bypassSpoofing; [HarmonyPrefix] [HarmonyPatch("Play", new Type[] { })] [HarmonyPatch("Play", new Type[] { typeof(ulong) })] [HarmonyPatch("Play", new Type[] { typeof(double) })] private static bool Play(AudioSource __instance) { AudioSourceAdditionalData orCreate = AudioSourceAdditionalData.GetOrCreate(__instance); if (SoundReplacementHandler.TryReplaceAudio(__instance, orCreate.OriginalClip, out var replacement)) { if ((Object)(object)replacement == (Object)null) { return false; } orCreate.RealClip = replacement; } return true; } [HarmonyPrefix] [HarmonyPatch("PlayOneShot", new Type[] { typeof(AudioClip), typeof(float) })] private static bool PlayOneShot(AudioSource __instance, ref AudioClip clip) { if (SoundReplacementHandler.TryReplaceAudio(__instance, clip, out var replacement)) { if ((Object)(object)replacement == (Object)null) { return false; } clip = replacement; } return true; } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPriority(0)] [HarmonyPrefix] private static void UpdateOriginalClip(AudioSource __instance, AudioClip value, bool __runOriginal) { if (__runOriginal) { AudioSourceAdditionalData orCreate = AudioSourceAdditionalData.GetOrCreate(__instance); orCreate.OriginalClip = value; Debuggers.AudioClipSpoofing?.Log("(" + ((Object)((Component)__instance).gameObject).name + ") updating original clip to: " + ((Object)value).name); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] private static bool PreventClipRestartingWithSpoofed(AudioSource __instance, AudioClip value) { if (!PatchConfig.AudioClipSpoofing || bypassSpoofing) { return true; } AudioSourceAdditionalData orCreate = AudioSourceAdditionalData.GetOrCreate(__instance); if ((Object)(object)orCreate.OriginalClip == (Object)(object)value) { Debuggers.AudioClipSpoofing?.Log("prevented clip from restarting"); } return (Object)(object)orCreate.OriginalClip != (Object)(object)value; } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] private static void SpoofAudioSourceClip(AudioSource __instance, ref AudioClip __result) { if (PatchConfig.AudioClipSpoofing && !bypassSpoofing) { AudioSourceAdditionalData orCreate = AudioSourceAdditionalData.GetOrCreate(__instance); __result = orCreate.OriginalClip; Debuggers.AudioClipSpoofing?.Log("(" + ((Object)((Component)__instance).gameObject).name + ") spoofing result to " + ((Object)orCreate.OriginalClip).name); } } } [HarmonyPatch(typeof(GameObject))] internal static class GameObjectPatch { [HarmonyPostfix] [HarmonyPatch("AddComponent", new Type[] { typeof(Type) })] internal static void NewAudioSource(GameObject __instance, ref Component __result) { Component obj = __result; AudioSource val = (AudioSource)(object)((obj is AudioSource) ? obj : null); if (val != null) { AudioSourceAdditionalData.GetOrCreate(val); } } } [HarmonyPatch(typeof(Logger))] internal static class LoggerPatch { [HarmonyPrefix] [HarmonyPatch("LogMessage")] private static void ReenableAndSaveConfigs(object data) { if (data is string text && text == "Chainloader startup complete") { loaforcsSoundAPI.Logger.LogInfo((object)"Starting Sound-pack loading pipeline"); SoundPackLoadPipeline.StartPipeline(); } } } internal static class PatchConfig { internal static bool AudioClipSpoofing { get; private set; } internal static void Bind(ConfigFile file) { AudioClipSpoofing = file.Bind<bool>("Experiments", "AudioClipSpoofing", false, "Should SoundAPI spoof the return value of AudioSource.clip? This can improve compatibility but is also fairly invasive.").Value; } } internal static class UnityObjectPatch { private static void InstantiatePatch(Object __result) { Debuggers.AudioSourceAdditionalData?.Log("aghuobr: " + __result.name); GameObject val = (GameObject)(object)((__result is GameObject) ? __result : null); if (val != null) { CheckInstantiationRecursively(val); } } internal static void Init(Harmony harmony) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown HarmonyMethod val = new HarmonyMethod(typeof(UnityObjectPatch).GetMethod("InstantiatePatch", BindingFlags.Static | BindingFlags.NonPublic)); MethodInfo[] methods = typeof(Object).GetMethods(); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "Instantiate")) { Debuggers.AudioSourceAdditionalData?.Log($"patching {methodInfo}"); if (methodInfo.IsGenericMethod) { harmony.Patch((MethodBase)methodInfo.MakeGenericMethod(typeof(Object)), (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } } private static void CheckInstantiationRecursively(GameObject gameObject) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown AudioSourceAdditionalData audioSourceAdditionalData = default(AudioSourceAdditionalData); if (gameObject.TryGetComponent<AudioSourceAdditionalData>(ref audioSourceAdditionalData)) { return; } AudioSource[] components = gameObject.GetComponents<AudioSource>(); foreach (AudioSource source in components) { SoundReplacementHandler.CheckAudioSource(source); } foreach (Transform item in gameObject.transform) { Transform val = item; CheckInstantiationRecursively(((Component)val).gameObject); } } } } namespace loaforcsSoundAPI.Core.Networking { public abstract class NetworkAdapter { public abstract string Name { get; } public abstract void OnRegister(); } } namespace loaforcsSoundAPI.Core.JSON { public static class JSONDataLoader { private class MatchesJSONConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(List<string>); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Invalid comparison between Unknown and I4 JToken val = JToken.Load(reader); if ((int)val.Type == 2) { return val.ToObject<List<string>>(); } return new List<string> { ((object)val).ToString() }; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } } private class IncludePrivatePropertiesContractResolver : DefaultContractResolver { internal IncludePrivatePropertiesContractResolver() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown ((DefaultContractResolver)this).NamingStrategy = (NamingStrategy)new SnakeCaseNamingStrategy(); } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) JsonProperty val = ((DefaultContractResolver)this).CreateProperty(member, memberSerialization); if (!val.Writable && member is PropertyInfo propertyInfo) { val.Writable = propertyInfo.GetSetMethod(nonPublic: true) != null; } return val; } } private class ConditionConverter : JsonConverter<Condition> { public override Condition ReadJson(JsonReader reader, Type objectType, Condition existingValue, bool hasExistingValue, JsonSerializer serializer) { JObject val = JObject.Load(reader); string text = ((object)val["type"])?.ToString(); if (string.IsNullOrEmpty(text)) { return new InvalidCondition(null); } Condition condition = SoundPackDataHandler.CreateCondition(text); if (condition == null) { return null; } serializer.Populate(((JToken)val).CreateReader(), (object)condition); if (condition.Constant == true) { if (!condition.Evaluate(DefaultConditionContext.DEFAULT)) { return ConstantCondition.FALSE; } return ConstantCondition.TRUE; } return condition; } public override void WriteJson(JsonWriter writer, Condition value, JsonSerializer serializer) { throw new NotImplementedException("no."); } } private static readonly JsonSerializerSettings _settings = new JsonSerializerSettings { ContractResolver = (IContractResolver)(object)new IncludePrivatePropertiesContractResolver(), Converters = new List<JsonConverter>(2) { (JsonConverter)(object)new MatchesJSONConverter(), (JsonConverter)(object)new ConditionConverter() } }; public static T LoadFromFile<T>(string path) { //IL_0061: Expected O, but got Unknown string text = File.ReadAllText(path); try { T val = JsonConvert.DeserializeObject<T>(text, _settings); if ((object)val is IFilePathAware filePathAware) { filePathAware.FilePath = path; } if ((object)val is Conditional conditional && conditional.Condition != null) { conditional.Condition.Parent = conditional; conditional.Condition.OnRegistered(); } return val; } catch (JsonReaderException val2) { JsonReaderException val3 = val2; loaforcsSoundAPI.Logger.LogError((object)$"Failed to read json file: 'plugins{Path.DirectorySeparatorChar}{Path.GetRelativePath(Paths.PluginPath, path)}'"); loaforcsSoundAPI.Logger.LogError((object)((Exception)(object)val3).Message); string[] array = text.Split("\n"); int num = int.MaxValue; for (int i = Mathf.Max(0, val3.LineNumber - 3); i < Mathf.Min(array.Length, val3.LineNumber + 3); i++) { int num2 = array[i].TakeWhile(char.IsWhiteSpace).Count(); num = Mathf.Min(num, num2); } for (int j = Mathf.Max(0, val3.LineNumber - 3); j < Mathf.Min(array.Length, val3.LineNumber + 3); j++) { string text2 = $"{(j + 1).ToString(),-5}| "; string text3 = array[j]; int num3 = Mathf.Min(array[j].Length, num); string text4 = text2 + text3.Substring(num3, text3.Length - num3).TrimEnd(); if (j + 1 == val3.LineNumber) { text4 += " // <- HERE"; } loaforcsSoundAPI.Logger.LogError((object)text4); } } return default(T); } } } namespace loaforcsSoundAPI.Core.Data { public interface IFilePathAware { string FilePath { get; internal set; } } public interface IValidatable { public enum ResultType { WARN, FAIL } public class ValidationResult { public ResultType Status { get; private set; } public string Reason { get; private set; } public ValidationResult(ResultType resultType, string reason = null) { Status = resultType; Reason = reason ?? string.Empty; base..ctor(); } } private static readonly StringBuilder _stringBuilder = new StringBuilder(); List<ValidationResult> Validate(); internal static bool LogAndCheckValidationResult(string context, List<ValidationResult> results, ManualLogSource logger) { if (results.Count == 0) { return true; } int num = 0; int num2 = 0; foreach (ValidationResult result in results) { switch (result.Status) { case ResultType.WARN: num++; break; case ResultType.FAIL: num2++; break; default: throw new ArgumentOutOfRangeException(); } } _stringBuilder.Clear(); if (num2 != 0) { _stringBuilder.Append(num2); _stringBuilder.Append(" fail(s)"); } if (num != 0) { if (num2 != 0) { _stringBuilder.Append(" and "); } _stringBuilder.Append(num); _stringBuilder.Append(" warning(s)"); } _stringBuilder.Append(" while "); _stringBuilder.Append(context); _stringBuilder.Append(": "); if (num2 != 0) { logger.LogError((object)_stringBuilder); } else { logger.LogWarning((object)_stringBuilder); } foreach (ValidationResult result2 in results) { switch (result2.Status) { case ResultType.WARN: logger.LogWarning((object)("WARN: " + result2.Reason)); break; case ResultType.FAIL: logger.LogError((object)("FAIL: " + result2.Reason)); break; default: throw new ArgumentOutOfRangeException(); } } return num2 == 0; } } }