Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of GambitMainMusicReplacer v0.3.0
BepInEx\plugins\GambitMainMusicReplacer\GambitRouletteMusic.dll
Decompiled 14 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("GambitRouletteMusic")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("0.3.0.0")] [assembly: AssemblyInformationalVersion("0.3.0")] [assembly: AssemblyProduct("GambitRouletteMusic")] [assembly: AssemblyTitle("GambitRouletteMusic")] [assembly: AssemblyVersion("0.3.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 GambitRouletteMusic { internal static class AudioFileLoader { internal static IEnumerator LoadFromPluginFolder(string fileName, Action<AudioClip?> onLoaded) { string pluginDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (string.IsNullOrWhiteSpace(pluginDirectory)) { Plugin.Log.LogError((object)"Could not determine the plugin directory."); onLoaded(null); yield break; } string fullPath = Path.Combine(pluginDirectory, fileName); if (!File.Exists(fullPath)) { Plugin.Log.LogWarning((object)("Replacement music was not found: " + fullPath)); Plugin.Log.LogWarning((object)"Place the configured OGG, WAV or MP3 file next to the DLL."); onLoaded(null); yield break; } AudioType audioType = GetAudioType(Path.GetExtension(fullPath)); if ((int)audioType == 0) { Plugin.Log.LogError((object)("Unsupported audio extension: " + Path.GetExtension(fullPath))); onLoaded(null); yield break; } string fileUri = new Uri(fullPath).AbsoluteUri; UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(fileUri, audioType); try { yield return request.SendWebRequest(); if ((int)request.result != 1) { Plugin.Log.LogError((object)("Could not load replacement music: " + request.error)); onLoaded(null); yield break; } AudioClip clip = DownloadHandlerAudioClip.GetContent(request); if ((Object)(object)clip == (Object)null) { Plugin.Log.LogError((object)"Unity returned an empty AudioClip."); onLoaded(null); } else { ((Object)clip).name = "GambitMainMusic_" + Path.GetFileNameWithoutExtension(fullPath); Plugin.Log.LogInfo((object)$"Replacement music loaded: {Path.GetFileName(fullPath)} ({clip.length:0.00}s)."); onLoaded(clip); } } finally { ((IDisposable)request)?.Dispose(); } } private static AudioType GetAudioType(string extension) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) string text = extension.ToLowerInvariant(); if (1 == 0) { } AudioType result = (AudioType)(text switch { ".ogg" => 14, ".wav" => 20, ".mp3" => 13, _ => 0, }); if (1 == 0) { } return result; } } [BepInPlugin("com.artem.gambitroulettemusic", "Gambit Main Music Replacer", "0.3.0")] [BepInProcess("REPO.exe")] public sealed class Plugin : BaseUnityPlugin { private sealed class GambitMusicSession { private readonly Dictionary<int, TrackedSource> _sources = new Dictionary<int, TrackedSource>(); private readonly Transform _root; private bool _active; private double _startDspTime; private double _noPlayingSince = -1.0; internal IEnumerable<int> SourceIds => _sources.Keys; internal bool ShouldRemove => (Object)(object)_root == (Object)null || _sources.Count == 0; internal GambitMusicSession(Transform root) { _root = root; } internal void AddSource(AudioSource source, AudioClip originalClip, AudioClip replacement, bool forceLoop) { int instanceID = ((Object)source).GetInstanceID(); if (!_sources.ContainsKey(instanceID)) { bool isPlaying = source.isPlaying; bool loop = source.loop; if (isPlaying) { source.Stop(); } source.clip = replacement; if (forceLoop) { source.loop = true; } if (isPlaying) { TrySetTime(source, 0f); source.Play(); } _sources.Add(instanceID, new TrackedSource(source, originalClip, loop, isPlaying)); } } internal void Update(AudioClip replacement, bool forceLoop, float startVolumeThreshold, float endGraceSeconds, float syncToleranceSeconds) { RemoveDestroyedOrRepurposedSources(replacement); if ((Object)(object)_root == (Object)null || _sources.Count == 0) { return; } if (!((Component)_root).gameObject.activeInHierarchy) { StopSession(resetToStart: true, stopPlaying: true); return; } bool flag = false; bool flag2 = false; foreach (TrackedSource value in _sources.Values) { AudioSource source = value.Source; if ((Object)(object)source == (Object)null) { continue; } bool flag3 = EnsureReplacementClip(value, replacement, forceLoop, _active ? GetExpectedTime(replacement) : 0f); bool isPlaying = source.isPlaying; flag = flag || isPlaying; flag2 |= isPlaying && source.volume > startVolumeThreshold && !source.mute; if (_active && isPlaying) { float expectedTime = GetExpectedTime(replacement); bool flag4 = !value.WasPlaying; if (flag4 || flag3) { SeekPlayingSource(source, expectedTime); } else if (IsOutOfSync(source, expectedTime, replacement.length, syncToleranceSeconds)) { SeekPlayingSource(source, expectedTime); } } value.WasPlaying = source.isPlaying; } if (!_active) { if (flag2) { StartSession(replacement, forceLoop); } return; } if (flag) { _noPlayingSince = -1.0; return; } double dspTime = AudioSettings.dspTime; if (_noPlayingSince < 0.0) { _noPlayingSince = dspTime; } else if (dspTime - _noPlayingSince >= (double)Math.Max(0f, endGraceSeconds)) { StopSession(resetToStart: true, stopPlaying: false); } } private void StartSession(AudioClip replacement, bool forceLoop) { _active = true; _startDspTime = AudioSettings.dspTime; _noPlayingSince = -1.0; foreach (TrackedSource value in _sources.Values) { AudioSource source = value.Source; if (!((Object)(object)source == (Object)null)) { bool isPlaying = source.isPlaying; if (isPlaying) { source.Stop(); } source.clip = replacement; if (forceLoop) { source.loop = true; } if (isPlaying) { TrySetTime(source, 0f); source.Play(); } value.WasPlaying = source.isPlaying; } } Log.LogInfo((object)("[MUSIC START] Gambit main music started from 0:00 at '" + GetTransformPath(_root) + "'.")); } private void StopSession(bool resetToStart, bool stopPlaying) { if (!_active) { return; } _active = false; _noPlayingSince = -1.0; foreach (TrackedSource value in _sources.Values) { AudioSource source = value.Source; if (!((Object)(object)source == (Object)null)) { if (stopPlaying && source.isPlaying) { source.Stop(); } if (resetToStart && !source.isPlaying) { TrySetTime(source, 0f); } value.WasPlaying = source.isPlaying; } } Log.LogInfo((object)("[MUSIC STOP] Gambit main music session ended at '" + GetTransformPath(_root) + "'.")); } private float GetExpectedTime(AudioClip replacement) { if (!_active || replacement.length <= 0.001f) { return 0f; } double num = Math.Max(0.0, AudioSettings.dspTime - _startDspTime); return (float)(num % (double)replacement.length); } private static bool EnsureReplacementClip(TrackedSource tracked, AudioClip replacement, bool forceLoop, float expectedTime) { AudioSource source = tracked.Source; if ((Object)(object)source == (Object)null) { return false; } if (forceLoop) { source.loop = true; } if ((Object)(object)source.clip == (Object)(object)replacement) { return false; } AudioClip clip = source.clip; if ((Object)(object)clip == (Object)null || !TargetClipNames.Contains(((Object)clip).name)) { return false; } bool isPlaying = source.isPlaying; if (isPlaying) { source.Stop(); } source.clip = replacement; TrySetTime(source, expectedTime); if (isPlaying) { source.Play(); } return true; } private static bool IsOutOfSync(AudioSource source, float expectedTime, float clipLength, float tolerance) { if (clipLength <= 0.001f) { return false; } float time; try { time = source.time; } catch { return false; } float num = Mathf.Abs(time - expectedTime); float num2 = clipLength - num; float num3 = Mathf.Min(num, num2); return num3 > Mathf.Max(0.05f, tolerance); } private static void SeekPlayingSource(AudioSource source, float expectedTime) { bool isPlaying = source.isPlaying; if (isPlaying) { source.Stop(); } TrySetTime(source, expectedTime); if (isPlaying) { source.Play(); } } private static void TrySetTime(AudioSource source, float time) { try { source.time = Mathf.Max(0f, time); } catch { try { source.timeSamples = 0; } catch { } } } private void RemoveDestroyedOrRepurposedSources(AudioClip replacement) { List<int> list = null; foreach (KeyValuePair<int, TrackedSource> source2 in _sources) { AudioSource source = source2.Value.Source; if ((Object)(object)source == (Object)null) { if (list == null) { list = new List<int>(); } list.Add(source2.Key); continue; } AudioClip clip = source.clip; if (!((Object)(object)clip == (Object)(object)replacement) && (!((Object)(object)clip != (Object)null) || !TargetClipNames.Contains(((Object)clip).name))) { if (list == null) { list = new List<int>(); } list.Add(source2.Key); } } if (list == null) { return; } foreach (int item in list) { _sources.Remove(item); } } internal void Restore(AudioClip? replacementClip) { foreach (TrackedSource value in _sources.Values) { AudioSource source = value.Source; if (!((Object)(object)source == (Object)null) && !((Object)(object)source.clip != (Object)(object)replacementClip)) { bool isPlaying = source.isPlaying; if (isPlaying) { source.Stop(); } source.clip = value.OriginalClip; source.loop = value.OriginalLoop; TrySetTime(source, 0f); if (isPlaying) { source.Play(); } } } } } private sealed class TrackedSource { internal AudioSource Source { get; } internal AudioClip OriginalClip { get; } internal bool OriginalLoop { get; } internal bool WasPlaying { get; set; } internal TrackedSource(AudioSource source, AudioClip originalClip, bool originalLoop, bool wasPlaying) { Source = source; OriginalClip = originalClip; OriginalLoop = originalLoop; WasPlaying = wasPlaying; } } public const string PluginGuid = "com.artem.gambitroulettemusic"; public const string PluginName = "Gambit Main Music Replacer"; public const string PluginVersion = "0.3.0"; private static readonly HashSet<string> TargetClipNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "enemy spinny music", "enemy spinny music muffled", "enemy spinny music leaving" }; private ConfigEntry<bool> _modEnabled = null; private ConfigEntry<string> _audioFileName = null; private ConfigEntry<float> _scanInterval = null; private ConfigEntry<float> _startVolumeThreshold = null; private ConfigEntry<float> _endGraceSeconds = null; private ConfigEntry<float> _syncToleranceSeconds = null; private ConfigEntry<bool> _forceLoop = null; private AudioClip? _replacementClip; private Coroutine? _monitorCoroutine; private bool _sourcesRestoredForDisabledState; private readonly Dictionary<int, GambitMusicSession> _sessions = new Dictionary<int, GambitMusicSession>(); private readonly Dictionary<int, int> _sourceToSession = new Dictionary<int, int>(); internal static ManualLogSource Log { get; private set; } = null; private void Awake() { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Expected O, but got Unknown //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; _modEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enables replacement of Gambit's main music."); _audioFileName = ((BaseUnityPlugin)this).Config.Bind<string>("Audio", "AudioFileName", "roulette.ogg", "Replacement track placed next to the DLL. Supported: .ogg, .wav and .mp3."); _forceLoop = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "ForceLoop", true, "Loops the replacement track until Gambit's music sources stop or the enemy leaves."); _startVolumeThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("Playback", "StartVolumeThreshold", 0.005f, new ConfigDescription("The track restarts from 0:00 when Gambit's music first becomes audible.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.25f), Array.Empty<object>())); _endGraceSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Playback", "EndGraceSeconds", 0.6f, new ConfigDescription("Prevents a brief game-side source switch from ending the current music session.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); _syncToleranceSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Playback", "SyncToleranceSeconds", 0.35f, new ConfigDescription("Maximum allowed drift between the normal, muffled and leaving music sources.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 2f), Array.Empty<object>())); _scanInterval = ((BaseUnityPlugin)this).Config.Bind<float>("Advanced", "ScanIntervalSeconds", 0.05f, new ConfigDescription("How often the mod checks Gambit's music AudioSources.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.02f, 1f), Array.Empty<object>())); ((MonoBehaviour)this).StartCoroutine(AudioFileLoader.LoadFromPluginFolder(_audioFileName.Value, OnReplacementLoaded)); Log.LogInfo((object)"Gambit Main Music Replacer v0.3.0 loaded."); Log.LogInfo((object)"The replacement starts at 0:00, stays synchronized and stops with Gambit."); } private void OnReplacementLoaded(AudioClip? clip) { _replacementClip = clip; if (!((Object)(object)clip == (Object)null)) { if (_monitorCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_monitorCoroutine); } _monitorCoroutine = ((MonoBehaviour)this).StartCoroutine(MonitorGambitMusic()); } } private IEnumerator MonitorGambitMusic() { while (true) { if (!_modEnabled.Value) { if (!_sourcesRestoredForDisabledState) { RestoreAllSources(); _sourcesRestoredForDisabledState = true; } } else { _sourcesRestoredForDisabledState = false; UpdateGambitMusicSessions(); } yield return (object)new WaitForSecondsRealtime(Mathf.Max(0.02f, _scanInterval.Value)); } } private void UpdateGambitMusicSessions() { AudioClip replacementClip = _replacementClip; if ((Object)(object)replacementClip == (Object)null) { return; } DiscoverTargetSources(replacementClip); List<int> list = null; foreach (KeyValuePair<int, GambitMusicSession> session in _sessions) { GambitMusicSession value = session.Value; value.Update(replacementClip, _forceLoop.Value, _startVolumeThreshold.Value, _endGraceSeconds.Value, _syncToleranceSeconds.Value); if (value.ShouldRemove) { if (list == null) { list = new List<int>(); } list.Add(session.Key); } } if (list == null) { return; } foreach (int item in list) { RemoveSession(item, restoreSources: false); } } private void DiscoverTargetSources(AudioClip replacement) { AudioSource[] array = Resources.FindObjectsOfTypeAll<AudioSource>(); AudioSource[] array2 = array; foreach (AudioSource val in array2) { if ((Object)(object)val == (Object)null) { continue; } int instanceID = ((Object)val).GetInstanceID(); bool flag = _sourceToSession.ContainsKey(instanceID); AudioClip clip = val.clip; bool flag2 = (Object)(object)clip != (Object)null && TargetClipNames.Contains(((Object)clip).name); if ((!flag && !flag2) || flag) { continue; } Transform val2 = FindGambitRoot(((Component)val).transform); if (!((Object)(object)val2 == (Object)null) && !((Object)(object)clip == (Object)null)) { int instanceID2 = ((Object)val2).GetInstanceID(); if (!_sessions.TryGetValue(instanceID2, out GambitMusicSession value)) { value = new GambitMusicSession(val2); _sessions.Add(instanceID2, value); } value.AddSource(val, clip, replacement, _forceLoop.Value); _sourceToSession[instanceID] = instanceID2; Log.LogInfo((object)("[TRACKED] '" + ((Object)clip).name + "' at '" + GetTransformPath(((Component)val).transform) + "'.")); } } } private static Transform? FindGambitRoot(Transform? transform) { Transform val = transform; while ((Object)(object)val != (Object)null) { if (((Object)val).name.IndexOf("Enemy - Spinny", StringComparison.OrdinalIgnoreCase) >= 0) { return val; } val = val.parent; } return null; } private void RemoveSession(int sessionId, bool restoreSources) { if (!_sessions.TryGetValue(sessionId, out GambitMusicSession value)) { return; } List<int> list = null; foreach (KeyValuePair<int, int> item in _sourceToSession) { if (item.Value == sessionId) { if (list == null) { list = new List<int>(); } list.Add(item.Key); } } if (list != null) { foreach (int item2 in list) { _sourceToSession.Remove(item2); } } if (restoreSources) { value.Restore(_replacementClip); } _sessions.Remove(sessionId); } private void RestoreAllSources() { foreach (GambitMusicSession value in _sessions.Values) { value.Restore(_replacementClip); } _sessions.Clear(); _sourceToSession.Clear(); } private static string GetTransformPath(Transform? transform) { if ((Object)(object)transform == (Object)null) { return "<null>"; } string text = ((Object)transform).name; Transform parent = transform.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } private void OnDestroy() { if (_monitorCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_monitorCoroutine); _monitorCoroutine = null; } RestoreAllSources(); } } }