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 kossnikitaMatesMod v1.0.3
kossnikitaMatesMod.dll
Decompiled 8 months agousing System; using System.Collections; 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 BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Audio; [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("kossnikita")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.3.0")] [assembly: AssemblyInformationalVersion("1.0.3+2405c4dd8ccd4accb5843eda27113403ef74f8d2")] [assembly: AssemblyProduct("kossnikita mates mod")] [assembly: AssemblyTitle("kossnikitaMatesMod")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.3.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 kossnikitaMates { public class AudioSourceData { public AudioClip OriginalClip { get; set; } public AudioClip RealClip { get { using (new SpoofBypassContext()) { return Source.clip; } } set { using (new SpoofBypassContext()) { Source.clip = value; } } } public AudioSource Source { get; private set; } public float OriginalPitch { get; set; } public float? CustomPitch { get; set; } public bool ReplacementAttempted { get; set; } public AudioSourceData(AudioSource source) { Source = source; using (new SpoofBypassContext()) { OriginalPitch = source.pitch; } } public static AudioSourceData GetOrCreateData(AudioSource audioSource) { if (Plugin.audioSourceData.TryGetValue(audioSource, out var value)) { return value; } value = new AudioSourceData(audioSource); value.OriginalClip = value.RealClip; Plugin.audioSourceData.Add(audioSource, value); return value; } } public class SpoofBypassContext : IDisposable { public SpoofBypassContext() { AudioSourcePatch.bypassSpoofing = true; } public void Dispose() { AudioSourcePatch.bypassSpoofing = false; } } [HarmonyPatch(typeof(AudioSource))] public class AudioSourcePatch { public struct SpoofBypassContext : IDisposable { private bool previousState; public SpoofBypassContext() { previousState = bypassSpoofing; bypassSpoofing = true; } public void Dispose() { bypassSpoofing = previousState; } } private class AdditionalAudioBehaviour : MonoBehaviour { [CompilerGenerated] private sealed class <CleanupCoroutine>d__4 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float delay; public AdditionalAudioBehaviour <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <CleanupCoroutine>d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown int num = <>1__state; AdditionalAudioBehaviour additionalAudioBehaviour = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(delay); <>1__state = 1; return true; case 1: <>1__state = -1; if (additionalAudioBehaviour.unmuteOriginal && (Object)(object)additionalAudioBehaviour.original != (Object)null) { using (new SpoofBypassContext()) { try { additionalAudioBehaviour.original.mute = false; } catch { } } } Object.Destroy((Object)(object)((Component)additionalAudioBehaviour).gameObject); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private AudioSource original; private AudioSource replacement; private bool unmuteOriginal; public void Init(AudioSource original, AudioSource replacement, bool unmuteOriginal, float lifespan) { this.original = original; this.replacement = replacement; this.unmuteOriginal = unmuteOriginal; ((MonoBehaviour)this).StartCoroutine(CleanupCoroutine(lifespan)); } [IteratorStateMachine(typeof(<CleanupCoroutine>d__4))] private IEnumerator CleanupCoroutine(float delay) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <CleanupCoroutine>d__4(0) { <>4__this = this, delay = delay }; } } private class ReplacementDistanceBehaviour : MonoBehaviour { [CompilerGenerated] private sealed class <CleanupCoroutine>d__4 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float delay; public ReplacementDistanceBehaviour <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <CleanupCoroutine>d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown int num = <>1__state; ReplacementDistanceBehaviour replacementDistanceBehaviour = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(delay); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)replacementDistanceBehaviour.original != (Object)null) { using (new SpoofBypassContext()) { try { replacementDistanceBehaviour.original.maxDistance = replacementDistanceBehaviour.originalMaxDistance; } catch { } try { replacementDistanceBehaviour.original.volume = replacementDistanceBehaviour.originalVolume; } catch { } } } Object.Destroy((Object)(object)replacementDistanceBehaviour); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private AudioSource original; private float originalMaxDistance; private float originalVolume; public void Init(AudioSource original, float originalMaxDistance, float originalVolume, float lifespan) { this.original = original; this.originalMaxDistance = originalMaxDistance; this.originalVolume = originalVolume; ((MonoBehaviour)this).StartCoroutine(CleanupCoroutine(lifespan)); } [IteratorStateMachine(typeof(<CleanupCoroutine>d__4))] private IEnumerator CleanupCoroutine(float delay) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <CleanupCoroutine>d__4(0) { <>4__this = this, delay = delay }; } } [ThreadStatic] internal static bool bypassSpoofing; private static AudioMixerGroup[] cachedAudioMixerGroups; private static FieldInfo m_GroupsField; [HarmonyPrefix] [HarmonyPatch("Play", new Type[] { })] [HarmonyPatch("Play", new Type[] { typeof(ulong) })] [HarmonyPatch("PlayDelayed", new Type[] { typeof(float) })] [HarmonyPatch("PlayScheduled", new Type[] { typeof(double) })] private static bool PlayPrefix(AudioSource __instance) { if (bypassSpoofing) { return true; } if (!Plugin.ConfigSound.Value) { return true; } AudioSourceData orCreateData = AudioSourceData.GetOrCreateData(__instance); if (orCreateData.ReplacementAttempted) { return true; } try { string text = "<unknown>"; try { using (new SpoofBypassContext()) { AudioClip clip = __instance.clip; text = ((clip != null) ? ((Object)clip).name : null) ?? "<null>"; } } catch { } AudioClip originalClip = orCreateData.OriginalClip; string text2 = ((originalClip != null) ? ((Object)originalClip).name : null) ?? "<null>"; string text3 = null; try { AudioClip realClip = orCreateData.RealClip; text3 = ((realClip != null) ? ((Object)realClip).name : null) ?? "<null>"; } catch { text3 = "<error>"; } Plugin.DebugLogAudio($"PlayPrefix called: id={((Object)__instance).GetInstanceID()} go='{((Object)((Component)__instance).gameObject).name}' instClip='{text}' orig='{text2}' real='{text3}'"); } catch { } if (TryReplaceAudioWithType(__instance, orCreateData.OriginalClip, out var replacement, out var replacementType)) { try { AudioClip originalClip2 = orCreateData.OriginalClip; LogSpatialAudioParams(__instance, ((originalClip2 != null) ? ((Object)originalClip2).name : null) ?? "<unknown>", replacementType); } catch { } if ((Object)(object)replacement == (Object)null) { return false; } if (replacementType != null && replacementType.IsAdditional) { AdditionalSoundReplacement obj5 = replacementType as AdditionalSoundReplacement; string mixerGroupName = obj5?.MixerGroupName; float? replacementVolume = obj5?.Volume; float maxDistanceMultiplier = obj5?.MaxDistanceMultiplier ?? 1f; SpawnAdditionalAudioSource(__instance, replacement, replacementType, replacementType.ShouldMuteOriginal, replacementType.Is2D, mixerGroupName, replacementVolume, maxDistanceMultiplier); if (replacementType.ShouldMuteOriginal) { using (new SpoofBypassContext()) { __instance.volume = 0f; } } orCreateData.ReplacementAttempted = true; string[] obj6 = new string[6] { "Applied additional replacement '", ((Object)replacement).name, "' for original '", null, null, null }; AudioClip originalClip3 = orCreateData.OriginalClip; obj6[3] = ((originalClip3 != null) ? ((Object)originalClip3).name : null) ?? "<null>"; obj6[4] = "' on "; obj6[5] = ((Object)((Component)__instance).gameObject).name; Plugin.DebugLogAudio(string.Concat(obj6)); return true; } float? customPitch = replacementType?.Pitch; orCreateData.CustomPitch = customPitch; try { float num = 0f; try { using (new SpoofBypassContext()) { _ = __instance.isPlaying; num = __instance.time; } } catch { } orCreateData.RealClip = replacement; try { string text4 = "<null>"; try { using (new SpoofBypassContext()) { AudioClip clip2 = __instance.clip; text4 = ((clip2 != null) ? ((Object)clip2).name : null) ?? "<null>"; _ = __instance.isPlaying; } } catch { } AudioClip originalClip4 = orCreateData.OriginalClip; string text5 = ((originalClip4 != null) ? ((Object)originalClip4).name : null) ?? "<null>"; if (text4 == text5) { try { ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogWarning((object)("[PlayPrefix][Fallback] Underlying clip still '" + text4 + "', applying Stop/Play fallback to enforce replacement '" + ((Object)replacement).name + "'")); } using (new SpoofBypassContext()) { __instance.Stop(); __instance.clip = replacement; if (orCreateData.CustomPitch.HasValue) { __instance.pitch = orCreateData.CustomPitch.Value; } __instance.time = num; __instance.Play(); } } catch (Exception ex) { ManualLogSource logger2 = Plugin.Logger; if (logger2 != null) { logger2.LogWarning((object)("[PlayPrefix][Fallback] Failed Stop/Play fallback: " + ex.Message)); } } } } catch { } if (orCreateData.CustomPitch.HasValue) { using (new SpoofBypassContext()) { try { __instance.pitch = orCreateData.CustomPitch.Value; } catch { } } } try { using (new SpoofBypassContext()) { __instance.time = num; } } catch { } orCreateData.ReplacementAttempted = true; Plugin.Logger.LogInfo((object)$"Replaced audio clip with '{((Object)replacement).name}' (preserved time={num})"); object[] obj12 = new object[4] { ((Object)replacement).name, null, null, null }; AudioClip originalClip5 = orCreateData.OriginalClip; obj12[1] = ((originalClip5 != null) ? ((Object)originalClip5).name : null) ?? "<null>"; obj12[2] = ((Object)((Component)__instance).gameObject).name; obj12[3] = num; Plugin.DebugLogAudio(string.Format("Applied standard replacement '{0}' for original '{1}' on {2} (time preserved={3})", obj12)); } catch (Exception ex2) { Plugin.Logger.LogWarning((object)("Failed to apply replacement without Stop/Play: " + ex2.Message)); orCreateData.ReplacementAttempted = true; } return true; } return true; } [HarmonyPostfix] [HarmonyPatch("Play", new Type[] { })] [HarmonyPatch("Play", new Type[] { typeof(ulong) })] [HarmonyPatch("PlayDelayed", new Type[] { typeof(float) })] [HarmonyPatch("PlayScheduled", new Type[] { typeof(double) })] private static void PlayPostfix(AudioSource __instance) { try { string text = "<null>"; try { using (new SpoofBypassContext()) { AudioClip clip = __instance.clip; text = ((clip != null) ? ((Object)clip).name : null) ?? "<null>"; } } catch { } Plugin.DebugLogAudio($"PlayPostfix: AudioSource id={((Object)__instance).GetInstanceID()} go='{((Object)((Component)__instance).gameObject).name}' now has clip='{text}' isPlaying={__instance.isPlaying}"); } catch { } } [HarmonyPrefix] [HarmonyPatch("Stop", new Type[] { })] private static bool Stop(AudioSource __instance) { if (!Plugin.ConfigSound.Value) { return true; } AudioSourceData orCreateData = AudioSourceData.GetOrCreateData(__instance); try { if ((Object)(object)orCreateData.RealClip != (Object)null && (Object)(object)orCreateData.OriginalClip != (Object)null && (Object)(object)orCreateData.RealClip != (Object)(object)orCreateData.OriginalClip) { ISoundReplacement replacementType = Plugin.GetReplacementType(((Object)orCreateData.OriginalClip).name); if (replacementType != null && replacementType.PlayToEnd) { AudioClip realClip = orCreateData.RealClip; Plugin.DebugLogAudio("Stop intercepted: skipping Stop to allow replacement '" + (((realClip != null) ? ((Object)realClip).name : null) ?? "<null>") + "' to finish on " + ((Object)((Component)__instance).gameObject).name); return false; } } } catch { } orCreateData.ReplacementAttempted = false; try { orCreateData.RealClip = orCreateData.OriginalClip; } catch { } return true; } private static void SpawnAdditionalAudioSource(AudioSource original, AudioClip clip, ISoundReplacement replacementType, bool muteOriginal, bool is2D = false, string mixerGroupName = null, float? replacementVolume = null, float maxDistanceMultiplier = 1f) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Invalid comparison between Unknown and I4 GameObject val = new GameObject("AdditionalAudio_" + ((Object)clip).name); val.transform.position = ((Component)original).transform.position; AudioSource val2 = val.AddComponent<AudioSource>(); try { if (!string.IsNullOrEmpty(mixerGroupName)) { AudioMixerGroup outputAudioMixerGroup = original.outputAudioMixerGroup; AudioMixerGroup[] array = FindAudioMixerGroups((outputAudioMixerGroup != null) ? outputAudioMixerGroup.audioMixer : null); if (array != null) { AudioMixerGroup val3 = Array.Find(array, (AudioMixerGroup g) => ((Object)g).name == mixerGroupName); if ((Object)(object)val3 != (Object)null) { val2.outputAudioMixerGroup = val3; } else { Plugin.Logger.LogWarning((object)("Mixer group '" + mixerGroupName + "' not found, using original")); val2.outputAudioMixerGroup = original.outputAudioMixerGroup; } } } else { val2.outputAudioMixerGroup = original.outputAudioMixerGroup; } } catch { } float? num = replacementVolume; if (num.HasValue) { val2.volume = Math.Max(0f, Math.Min(1f, num.Value)); } else { try { using (new SpoofBypassContext()) { val2.volume = original.volume; } } catch { } } float? num2 = replacementType?.Pitch; if (num2.HasValue) { ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogDebug((object)$"[SpawnAdditionalAudioSource] Applying replacement pitch {num2.Value} to new source for clip '{((Object)clip).name}'"); } val2.pitch = num2.Value; } else { using (new SpoofBypassContext()) { float pitch = original.pitch; ManualLogSource logger2 = Plugin.Logger; if (logger2 != null) { logger2.LogDebug((object)$"[SpawnAdditionalAudioSource] Copying original pitch {pitch} to new source for clip '{((Object)clip).name}'"); } val2.pitch = pitch; } } if (is2D) { val2.spatialBlend = 0f; } else { val2.spatialBlend = original.spatialBlend; if (replacementType.MinDistance.HasValue) { val2.minDistance = replacementType.MinDistance.Value; } else { val2.minDistance = original.minDistance; } if (replacementType.MaxDistance.HasValue) { val2.maxDistance = replacementType.MaxDistance.Value; } else { val2.maxDistance = original.maxDistance * Math.Max(0.01f, maxDistanceMultiplier); } if (replacementType.RolloffMode.HasValue) { val2.rolloffMode = replacementType.RolloffMode.Value; } else { val2.rolloffMode = original.rolloffMode; } val2.dopplerLevel = original.dopplerLevel; val2.spread = original.spread; val2.spatialize = original.spatialize; val2.spatializePostEffects = original.spatializePostEffects; val2.reverbZoneMix = original.reverbZoneMix; if ((int)original.rolloffMode == 2) { try { AnimationCurve customCurve = original.GetCustomCurve((AudioSourceCurveType)0); if (customCurve != null) { val2.SetCustomCurve((AudioSourceCurveType)0, customCurve); } } catch { } } } val2.loop = false; val2.playOnAwake = false; val2.clip = clip; if (replacementType != null && replacementType.ResetTimeOnPlay) { val2.time = 0f; } val2.Play(); if (muteOriginal) { using (new SpoofBypassContext()) { try { original.mute = true; } catch { } } } val.AddComponent<AdditionalAudioBehaviour>().Init(original, val2, muteOriginal, clip.length + 0.5f); } public static AudioMixerGroup[] FindAudioMixerGroupsPublic(AudioMixer mixer = null) { return FindAudioMixerGroups(mixer); } private static AudioMixerGroup[] FindAudioMixerGroups(AudioMixer mixer = null) { try { if (cachedAudioMixerGroups != null && cachedAudioMixerGroups.Length != 0) { return cachedAudioMixerGroups; } AudioMixerGroup[] array = Resources.FindObjectsOfTypeAll<AudioMixerGroup>(); if (array != null && array.Length != 0) { cachedAudioMixerGroups = array; return array; } if ((Object)(object)mixer != (Object)null) { if (m_GroupsField == null) { m_GroupsField = AccessTools.Field(((object)mixer).GetType(), "m_Groups"); } if (m_GroupsField != null && m_GroupsField.GetValue(mixer) is AudioMixerGroup[] array2 && array2.Length != 0) { cachedAudioMixerGroups = array2; return array2; } } AudioMixer[] array3 = Resources.FindObjectsOfTypeAll<AudioMixer>(); if (array3 != null && array3.Length != 0) { AudioMixer[] array4 = array3; foreach (AudioMixer val in array4) { if (m_GroupsField == null) { m_GroupsField = AccessTools.Field(((object)val).GetType(), "m_Groups"); } if (m_GroupsField != null && m_GroupsField.GetValue(val) is AudioMixerGroup[] array5 && array5.Length != 0) { cachedAudioMixerGroups = array5; return array5; } } } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("Failed to get AudioMixer groups: " + ex.Message)); } return null; } [HarmonyPrefix] [HarmonyPatch("PlayOneShot", new Type[] { typeof(AudioClip), typeof(float) })] private static bool PlayOneShot(AudioSource __instance, ref AudioClip clip) { if (!Plugin.ConfigSound.Value) { return true; } try { ISoundReplacement soundReplacement = (((Object)(object)clip != (Object)null) ? Plugin.GetReplacementType(((Object)clip).name) : null); if (soundReplacement != null) { AudioClip obj = clip; LogSpatialAudioParams(__instance, ((obj != null) ? ((Object)obj).name : null) ?? "<unknown>", soundReplacement); } } catch { } if (TryReplaceAudio(__instance, clip, out var replacement)) { if ((Object)(object)replacement == (Object)null) { return false; } clip = replacement; } return true; } [HarmonyPrefix] [HarmonyPatch("PlayOneShot", new Type[] { typeof(AudioClip) })] private static bool PlayOneShotSingleParam(AudioSource __instance, ref AudioClip clip) { return PlayOneShot(__instance, ref clip); } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] private static bool ClipSetter(AudioSource __instance, AudioClip value) { if (bypassSpoofing) { return true; } AudioSourceData orCreateData = AudioSourceData.GetOrCreateData(__instance); if (orCreateData.ReplacementAttempted) { orCreateData.OriginalClip = value; Plugin.DebugLogAudio(string.Format("ClipSetter: stored OriginalClip while replacement active: '{0}' for AudioSource id={1} go='{2}'", ((value != null) ? ((Object)value).name : null) ?? "<null>", ((Object)__instance).GetInstanceID(), ((Object)((Component)__instance).gameObject).name)); return false; } if ((Object)(object)value != (Object)(object)orCreateData.OriginalClip) { orCreateData.OriginalClip = value; Plugin.DebugLogAudio(string.Format("ClipSetter: stored OriginalClip='{0}' for AudioSource id={1} go='{2}'", ((value != null) ? ((Object)value).name : null) ?? "<null>", ((Object)__instance).GetInstanceID(), ((Object)((Component)__instance).gameObject).name)); } return true; } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] private static void SpoofAudioSourceClip(AudioSource __instance, ref AudioClip __result) { if (bypassSpoofing || !Plugin.audioSourceData.TryGetValue(__instance, out var value) || (Object)(object)value.OriginalClip == (Object)null) { return; } try { if ((Object)(object)value.RealClip != (Object)(object)value.OriginalClip) { object[] obj = new object[4] { ((Object)__instance).GetInstanceID(), ((Object)((Component)__instance).gameObject).name, null, null }; AudioClip realClip = value.RealClip; obj[2] = ((realClip != null) ? ((Object)realClip).name : null) ?? "<null>"; AudioClip originalClip = value.OriginalClip; obj[3] = ((originalClip != null) ? ((Object)originalClip).name : null) ?? "<null>"; Plugin.DebugLogAudio(string.Format("SpoofAudioSourceClip: real != original, not spoofing for AudioSource id={0} go='{1}' real='{2}' orig='{3}'", obj)); return; } } catch { } object arg = ((Object)__instance).GetInstanceID(); string name = ((Object)((Component)__instance).gameObject).name; AudioClip originalClip2 = value.OriginalClip; Plugin.DebugLogAudio(string.Format("SpoofAudioSourceClip: spoofing clip for AudioSource id={0} go='{1}' to OriginalClip='{2}'", arg, name, ((originalClip2 != null) ? ((Object)originalClip2).name : null) ?? "<null>")); __result = value.OriginalClip; } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] private static void SpoofIsPlaying(AudioSource __instance, ref bool __result) { if (bypassSpoofing || !Plugin.audioSourceData.TryGetValue(__instance, out var _)) { return; } using (new SpoofBypassContext()) { __result = __instance.isPlaying; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] private static bool PitchSetter(AudioSource __instance, ref float value) { if (bypassSpoofing) { return true; } if ((Object)(object)__instance == (Object)null) { return true; } if (Plugin.audioSourceData.TryGetValue(__instance, out var value2) && (Object)(object)value2.RealClip != (Object)null && value2.CustomPitch.HasValue) { ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogDebug((object)$"[PitchSetter] Overriding pitch on '{((Object)((Component)__instance).gameObject).name}' to {value2.CustomPitch.Value}"); } value = value2.CustomPitch.Value; } return true; } private static bool TryReplaceAudio(AudioSource audioSource, AudioClip originalClip, out AudioClip replacement) { replacement = null; if ((Object)(object)originalClip == (Object)null) { return false; } ISoundReplacement replacementType = Plugin.GetReplacementType(((Object)originalClip).name); if (replacementType == null) { ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogInfo((object)$"[TryReplaceAudio] No replacement type for '{((Object)originalClip).name}' on AudioSource id={((Object)audioSource).GetInstanceID()} name='{((Object)((Component)audioSource).gameObject).name}'"); } return false; } ManualLogSource logger2 = Plugin.Logger; if (logger2 != null) { logger2.LogInfo((object)("[TryReplaceAudio] Replacement type '" + replacementType.GetType().Name + "' found for '" + ((Object)originalClip).name + "'")); } string replacementForAudioSource = replacementType.GetReplacementForAudioSource(((Object)originalClip).name, audioSource); if (replacementForAudioSource == null) { ManualLogSource logger3 = Plugin.Logger; if (logger3 != null) { logger3.LogInfo((object)$"[TryReplaceAudio] Replacement type returned null for '{((Object)originalClip).name}' on AudioSource id={((Object)audioSource).GetInstanceID()}"); } return false; } replacement = Plugin.GetAudioClip(replacementForAudioSource); if ((Object)(object)replacement == (Object)null) { ManualLogSource logger4 = Plugin.Logger; if (logger4 != null) { logger4.LogWarning((object)("[TryReplaceAudio] Audio clip '" + replacementForAudioSource + "' not found in cache for original '" + ((Object)originalClip).name + "'")); } return false; } ManualLogSource logger5 = Plugin.Logger; if (logger5 != null) { logger5.LogInfo((object)$"[TryReplaceAudio] Will replace '{((Object)originalClip).name}' with '{replacementForAudioSource}' on AudioSource id={((Object)audioSource).GetInstanceID()} name='{((Object)((Component)audioSource).gameObject).name}'"); } return true; } private static bool TryReplaceAudioWithType(AudioSource audioSource, AudioClip originalClip, out AudioClip replacement, out ISoundReplacement replacementType) { replacement = null; replacementType = null; try { ManualLogSource logger = Plugin.Logger; if (logger != null) { string arg = ((originalClip != null) ? ((Object)originalClip).name : null) ?? "<null>"; object arg2 = ((audioSource != null) ? new int?(((Object)audioSource).GetInstanceID()) : null); object obj; if (audioSource == null) { obj = null; } else { GameObject gameObject = ((Component)audioSource).gameObject; obj = ((gameObject != null) ? ((Object)gameObject).name : null); } if (obj == null) { obj = "<null>"; } logger.LogWarning((object)$"[TryReplaceAudioWithType] Called for clip='{arg}' on AudioSource id={arg2} name='{obj}'"); } } catch { } if ((Object)(object)originalClip == (Object)null) { return false; } replacementType = Plugin.GetReplacementType(((Object)originalClip).name); if (replacementType == null) { ManualLogSource logger2 = Plugin.Logger; if (logger2 != null) { logger2.LogInfo((object)("[TryReplaceAudioWithType] No replacement type for '" + ((Object)originalClip).name + "'")); } return false; } string replacementForAudioSource = replacementType.GetReplacementForAudioSource(((Object)originalClip).name, audioSource); if (replacementForAudioSource == null) { ManualLogSource logger3 = Plugin.Logger; if (logger3 != null) { logger3.LogInfo((object)("[TryReplaceAudioWithType] Replacement type returned null for '" + ((Object)originalClip).name + "'")); } return false; } replacement = Plugin.GetAudioClip(replacementForAudioSource); if ((Object)(object)replacement == (Object)null) { ManualLogSource logger4 = Plugin.Logger; if (logger4 != null) { logger4.LogWarning((object)("[TryReplaceAudioWithType] Audio clip '" + replacementForAudioSource + "' not found in cache for original '" + ((Object)originalClip).name + "'")); } return false; } ManualLogSource logger5 = Plugin.Logger; if (logger5 != null) { logger5.LogInfo((object)("[TryReplaceAudioWithType] Replacing '" + ((Object)originalClip).name + "' with '" + replacementForAudioSource + "'")); } return true; } private static void LogSpatialAudioParams(AudioSource src, string originalClipName, ISoundReplacement replacementType) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)src == (Object)null) { return; } try { using (new SpoofBypassContext()) { Vector3 val = (((Object)(object)((Component)src).transform != (Object)null) ? ((Component)src).transform.position : Vector3.zero); float pitch = src.pitch; float minDistance = src.minDistance; float maxDistance = src.maxDistance; float spatialBlend = src.spatialBlend; AudioRolloffMode rolloffMode = src.rolloffMode; float dopplerLevel = src.dopplerLevel; float spread = src.spread; float reverbZoneMix = src.reverbZoneMix; bool spatialize = src.spatialize; bool spatializePostEffects = src.spatializePostEffects; string text = null; try { AudioMixerGroup outputAudioMixerGroup = src.outputAudioMixerGroup; text = ((outputAudioMixerGroup != null) ? ((Object)outputAudioMixerGroup).name : null); } catch { text = "<unknown>"; } ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogInfo((object)string.Format("[AudioSpatial] Clip='{0}' ReplacementType='{1}' Pos={2} Pitch={3} MinDist={4} MaxDist={5} SpatialBlend={6} Rolloff={7} Doppler={8} Spread={9} Reverb={10} Spatialize={11} SpatializePost={12} MixerGroup={13}", originalClipName, replacementType?.GetType().Name ?? "<null>", val, pitch, minDistance, maxDistance, spatialBlend, rolloffMode, dopplerLevel, spread, reverbZoneMix, spatialize, spatializePostEffects, text)); } } } catch (Exception ex) { ManualLogSource logger2 = Plugin.Logger; if (logger2 != null) { logger2.LogWarning((object)("[AudioSpatial] Failed to log spatial params for '" + originalClipName + "': " + ex.Message)); } } } } [HarmonyPatch(typeof(EnemySpinny), "GetAndSyncRandomRotationRPC")] public class PatchRouletteTargetAngle { private static void Prefix(EnemySpinny __instance, float _targetAngleDegrees) { Plugin.rouletteAngles.GetOrCreateValue(__instance).TargetAngle = _targetAngleDegrees; Plugin.LastRouletteTargetAngle = _targetAngleDegrees; ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogDebug((object)$"Target angle for {((Object)((Component)__instance).gameObject).name}: {_targetAngleDegrees}°"); } } } public interface ISoundReplacement { bool IsAdditional => false; bool ShouldMuteOriginal => true; bool Is2D => false; bool ResetTimeOnPlay => false; bool PlayToEnd => false; float? Pitch => null; float? Volume => null; float? MinDistance => null; float? MaxDistance => null; AudioRolloffMode? RolloffMode => null; string GetReplacement(string originalName); string GetReplacementForAudioSource(string originalName, AudioSource audioSource) { return GetReplacement(originalName); } } public static class PlayerDamageResponder { [HarmonyPatch(typeof(HurtCollider), "PlayerHurt")] private static class HurtColliderPlayerHurtPatch { [HarmonyPostfix] private static void Postfix(HurtCollider __instance) { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)__instance == (Object)null) { return; } if (Time.time - lastDiagnosticLogTime > 10f) { lastDiagnosticLogTime = Time.time; Plugin.Logger.LogDebug((object)"PlayerDamageResponder: HurtCollider.PlayerHurt hook is firing (diagnostic ping)."); } SemiLaser componentInParent = ((Component)__instance).GetComponentInParent<SemiLaser>(); if ((Object)(object)componentInParent == (Object)null) { return; } AudioSource audioSource = componentInParent.audioSource; if (!TryResolveBeamerForSemiLaser(componentInParent, out var beamer)) { Plugin.Logger.LogDebug((object)"PlayerDamageResponder: Laser hurt detected but owning Beamer not resolved yet; skipping sound."); return; } Vector3 val; try { val = (((Object)(object)beamer.enemy != (Object)null && (Object)(object)beamer.enemy.CenterTransform != (Object)null) ? beamer.enemy.CenterTransform.position : ((Component)beamer).transform.position); } catch (Exception) { val = ((Component)beamer).transform.position; } int instanceID; try { instanceID = ((Object)componentInParent).GetInstanceID(); } catch (Exception) { instanceID = ((Object)__instance).GetInstanceID(); } if (!ShouldThrottle(instanceID)) { Vector3 val2 = val; string text; try { text = ((Object)((Component)componentInParent).gameObject).name; } catch (Exception) { text = "<unknown>"; } object obj; if (!((Object)(object)audioSource != (Object)null)) { obj = "<no template>"; } else { Vector3 position = ((Component)audioSource).transform.position; obj = ((object)(Vector3)(ref position)).ToString(); } string text2 = (string)obj; Plugin.Logger.LogInfo((object)$"PlayerDamageResponder: Laser player hurt detected (src={text}). beamerPos={val}, templatePos={text2}, playingAt={val2}"); Plugin.PlaySoundAtPosition("system/naiden", val2, 1f, null, audioSource, 2f); } } catch (Exception ex4) { Plugin.Logger.LogError((object)("PlayerDamageResponder.HurtColliderPlayerHurtPatch error: " + ex4.Message)); } } } [HarmonyPatch(typeof(PlayerHealth), "Hurt")] private static class PlayerHealthHurtPatch { [HarmonyPostfix] private static void Postfix(object[] __args) { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) try { if (__args == null) { return; } SemiLaser val = null; foreach (object obj in __args) { if (obj == null) { continue; } GameObject val2 = (GameObject)((obj is GameObject) ? obj : null); if (val2 != null) { val = val2.GetComponent<SemiLaser>(); } else { SemiLaser val3 = (SemiLaser)((obj is SemiLaser) ? obj : null); if (val3 != null) { val = val3; } } if ((Object)(object)val != (Object)null) { break; } } if ((Object)(object)val == (Object)null) { return; } AudioSource audioSource = val.audioSource; if (!TryResolveBeamerForSemiLaser(val, out var beamer)) { Plugin.Logger.LogDebug((object)"PlayerDamageResponder.PlayerHealthHurtPatch: Laser detected but owning Beamer not resolved; skipping sound."); return; } Vector3 position; try { position = (((Object)(object)beamer.enemy != (Object)null && (Object)(object)beamer.enemy.CenterTransform != (Object)null) ? beamer.enemy.CenterTransform.position : ((Component)beamer).transform.position); } catch (Exception) { position = ((Component)beamer).transform.position; } int instanceID; try { instanceID = ((Object)val).GetInstanceID(); } catch (Exception) { instanceID = ((Object)beamer).GetInstanceID(); } if (!ShouldThrottle(instanceID)) { Plugin.PlaySoundAtPosition("system/naiden", position, 1f, null, audioSource, 2f); } } catch (Exception ex3) { Plugin.Logger.LogError((object)("PlayerDamageResponder.PlayerHealthHurtPatch error: " + ex3.Message)); } } } private const float LaserHitSoundCooldownSeconds = 1f; private const float DiagnosticLogIntervalSeconds = 10f; private static float lastDiagnosticLogTime; private static readonly Dictionary<int, float> lastPlayedTimeBySourceId = new Dictionary<int, float>(); private static readonly Dictionary<int, EnemyBeamer> beamerBySemiLaserId = new Dictionary<int, EnemyBeamer>(); private static bool TryResolveBeamerForSemiLaser(SemiLaser semiLaser, out EnemyBeamer beamer) { beamer = null; if ((Object)(object)semiLaser == (Object)null) { return false; } int instanceID; try { instanceID = ((Object)semiLaser).GetInstanceID(); } catch (Exception) { return false; } if (beamerBySemiLaserId.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null) { beamer = value; return true; } try { EnemyBeamer[] array = Object.FindObjectsOfType<EnemyBeamer>(); foreach (EnemyBeamer val in array) { if ((Object)(object)val == (Object)null) { continue; } try { if ((Object)(object)val.laser != (Object)null && val.laser == semiLaser) { beamerBySemiLaserId[instanceID] = val; beamer = val; return true; } } catch (Exception ex2) { Plugin.Logger.LogDebug((object)("Error checking beamer laser: " + ex2.Message)); } } } catch (Exception ex3) { Plugin.Logger.LogDebug((object)("Error resolving beamer for SemiLaser: " + ex3.Message)); } return false; } private static bool ShouldThrottle(int sourceId) { float now = Time.time; if (lastPlayedTimeBySourceId.TryGetValue(sourceId, out var value) && now - value < 1f) { return true; } lastPlayedTimeBySourceId[sourceId] = now; if (lastPlayedTimeBySourceId.Count > 512) { int[] array = (from kvp in lastPlayedTimeBySourceId where now - kvp.Value > 60f select kvp.Key).ToArray(); foreach (int key in array) { lastPlayedTimeBySourceId.Remove(key); } } return false; } public static void PatchPlayerDamage() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) try { new Harmony("kossnikitaMatesMod.playerdamage").PatchAll(typeof(PlayerDamageResponder)); Plugin.Logger.LogInfo((object)"PlayerDamageResponder: Patches applied via attributes."); } catch (Exception ex) { Plugin.Logger.LogError((object)("PlayerDamageResponder patching failed: " + ex.Message)); } } } [BepInPlugin("kossnikitaMatesMod", "kossnikita mates mod", "1.0.3")] public class Plugin : BaseUnityPlugin { public class RouletteAngleData { public float TargetAngle { get; set; } } internal static ManualLogSource Logger; public static ConfigEntry<bool> ConfigSound = null; public static ConfigEntry<bool> DebugAudio = null; public static ConfigEntry<bool> ConfigEnableTextureReplacement = null; public static ConfigEntry<bool> ConfigEnableLoadingUIRevUp = null; public static ConfigEntry<bool> ConfigEnableSmeshariki = null; public static ConfigEntry<bool> ConfigEnableTrapMusicboxMusic = null; public static ConfigEntry<bool> ConfigEnableCrazyFrog = null; public static ConfigEntry<bool> ConfigEnableSystemPoiska = null; public static ConfigEntry<bool> ConfigEnableKazik = null; public static ConfigEntry<bool> ConfigEnableNapas = null; public static ConfigEntry<bool> ConfigEnableUpgrade = null; public static ConfigEntry<string> HunterShotType = null; private static Dictionary<string, Texture2D> textureCache = new Dictionary<string, Texture2D>(); private AssetBundle assetBundle; private static Dictionary<string, AudioClip> audioClips = new Dictionary<string, AudioClip>(); public static ConditionalWeakTable<AudioSource, AudioSourceData> audioSourceData = new ConditionalWeakTable<AudioSource, AudioSourceData>(); private static Dictionary<string, ISoundReplacement> soundReplacements = new Dictionary<string, ISoundReplacement> { ["loading ui rev up"] = new ConditionalSoundReplacement("misc/vstroiku", (int count) => count % 2 == 1, () => ConfigEnableLoadingUIRevUp, 1f), ["headman chase begin"] = new SimpleSoundReplacement("smesharikibegin", () => ConfigEnableSmeshariki), ["headman chase loop"] = new SequentialSoundReplacement(new string[2] { "smesharikiloop01", "smesharikiloop02" }, () => ConfigEnableSmeshariki), ["trap musicbox music"] = new SequentialSoundReplacement(new string[9] { "musicbox/9mm", "musicbox/autist", "musicbox/daleko", "musicbox/daymne", "musicbox/podomnoy", "musicbox/sinyak", "musicbox/vodila", "musicbox/yapotyanu", "musicbox/zerkalo" }, () => ConfigEnableTrapMusicboxMusic), ["enemy tumbler tumble loop global"] = new SimpleSoundReplacement("crazy-frog", () => ConfigEnableCrazyFrog), ["enemy tumbler hurt01"] = new SimpleSoundReplacement("bing", () => ConfigEnableCrazyFrog), ["enemy tumbler hurt02"] = new SimpleSoundReplacement("bing", () => ConfigEnableCrazyFrog), ["enemy tumbler hurt03"] = new SimpleSoundReplacement("bing", () => ConfigEnableCrazyFrog), ["enemy slow walker death"] = new AdditionalSoundReplacement("timenyaposlal"), ["enemy beamer attack intro"] = new AdditionalSoundReplacement("system/sistemapoiska", () => ConfigEnableSystemPoiska, muteOriginal: false, is2D: false, null, 0.4f, 0.5f), ["enemy beamer attack intro global"] = new AdditionalSoundReplacement("system/sistemapoiska1global", () => ConfigEnableSystemPoiska, muteOriginal: false), ["enemy ceiling eye staring start"] = new AdditionalSoundReplacement("tvoya_popochka_vozbuzdaet", null, muteOriginal: false, is2D: false, "Sound Effects", 0.4f, 0.01f), ["enemy spinny music"] = new RouletteColorSoundReplacement("kazino/jackpot", "kazino/esli_hochesh", () => ConfigEnableKazik), ["enemy spinny music leaving"] = new RouletteColorSoundReplacement("kazino/jackpot_leaving", "kazino/esli_hochesh_leaving", () => ConfigEnableKazik), ["enemy spinny music muffled"] = new SimpleSoundReplacement("kazino/esli_hochesh_muffled", () => ConfigEnableKazik), ["gramophone music"] = new SimpleSoundReplacement("gramophone_napas", () => ConfigEnableNapas), ["player upgrade"] = new SimpleSoundReplacement("amamamminecraft", () => ConfigEnableUpgrade), ["enemy bowtie scream loop"] = new SimpleSoundReplacement("cheboksary"), ["enemy bowtie scream loop global"] = new SimpleSoundReplacement("cheboksaryglobal"), ["extraction point surplus spawn"] = new AdditionalSequentialSoundReplacement(new string[6] { "extraction/herecomes", "extraction/platina01", "extraction/abba", "extraction/platina02", "extraction/gimmetheloot", "extraction/instasamka" }), ["enemy hidden man player pickup"] = new SimpleSoundReplacement("dejavu/dejavu", null, 1f), ["enemy hidden man player release"] = new AdditionalSoundReplacement("dejavu/dejavuijustbeenthisplacebefore", null, muteOriginal: true, is2D: false, null, pitch: 1f, volume: 0.2f), ["enemy hidden man footstep sprint01"] = new SimpleSoundReplacement("dejavu/dejavu1", null, 1f), ["enemy hidden man footstep sprint02"] = new SimpleSoundReplacement("dejavu/dejavu2", null, 1f), ["enemy hidden man footstep sprint03"] = new SimpleSoundReplacement("dejavu/dejavuaha", null, 1f), ["enemy hidden man player move loop"] = new SimpleSoundReplacement("dejavu/dejavusection", null, 1f), ["enemy upscream scream01"] = new SimpleSoundReplacement("goida/scream01"), ["enemy upscream scream02"] = new SimpleSoundReplacement("goida/scream02"), ["enemy upscream scream03"] = new SimpleSoundReplacement("goida/scream03"), ["enemy upscream scream global01"] = new SimpleSoundReplacement("goida/global01"), ["enemy upscream scream global02"] = new SimpleSoundReplacement("goida/global02"), ["enemy upscream scream global03"] = new SimpleSoundReplacement("goida/global03"), ["enemy hunter shoot"] = new DynamicSoundReplacement(delegate { string text2 = HunterShotType.Value.ToLowerInvariant(); if (text2 == "kraber") { return "apex/kraber shot"; } return (text2 == "sentinel") ? "apex/sentinel shot" : null; }, () => !string.Equals(HunterShotType.Value, "standard", StringComparison.OrdinalIgnoreCase)), ["enemy hunter shoot global"] = new DynamicSoundReplacement(delegate { string text = HunterShotType.Value.ToLowerInvariant(); if (text == "kraber") { return "apex/kraber shot global"; } return (text == "sentinel") ? "apex/sentinel shot global" : null; }, () => !string.Equals(HunterShotType.Value, "standard", StringComparison.OrdinalIgnoreCase)), ["ambience breaker global14"] = new SimpleSoundReplacement("arat"), ["ambience breaker arctic01"] = new SimpleSoundReplacement("arat"), ["ambience breaker wizard06"] = new SimpleSoundReplacement("arat"), ["ambience breaker manor08"] = new SimpleSoundReplacement("binauralknocking"), ["sledgehammer hit01"] = new WeightedRandomSoundReplacement(new WeightedSound[3] { new WeightedSound("armatura1", 40), new WeightedSound("armatura2", 30), new WeightedSound("armatura3", 30) }), ["sledgehammer hit02"] = new WeightedRandomSoundReplacement(new WeightedSound[3] { new WeightedSound("armatura1", 40), new WeightedSound("armatura2", 30), new WeightedSound("armatura3", 30) }), ["sledgehammer hit03"] = new WeightedRandomSoundReplacement(new WeightedSound[3] { new WeightedSound("armatura1", 40), new WeightedSound("armatura2", 30), new WeightedSound("armatura3", 30) }), ["trap doll loop"] = new SimpleSoundReplacement("raspilimenya"), ["player revive"] = new SimpleSoundReplacement("yarodilsa") }; public static ConcurrentDictionary<string, int> playbackCounters = new ConcurrentDictionary<string, int>(); public static float LastRouletteTargetAngle = 0f; public static ConditionalWeakTable<EnemySpinny, RouletteAngleData> rouletteAngles = new ConditionalWeakTable<EnemySpinny, RouletteAngleData>(); private const string modResourcePath = "assets/mods/mates/"; private void Awake() { //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Expected O, but got Unknown //IL_021b: Unknown result type (might be due to invalid IL or missing references) Logger = ((BaseUnityPlugin)this).Logger; ConfigSound = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableSound", true, "Enable or disable sound effects in the plugin."); ConfigEnableLoadingUIRevUp = ((BaseUnityPlugin)this).Config.Bind<bool>("Sound Replacements", "LoadingTruck", true, "Enable replacement for 'loading ui rev up'"); ConfigEnableSmeshariki = ((BaseUnityPlugin)this).Config.Bind<bool>("Sound Replacements", "Smeshariki", true, "Enable replacement for 'smeshariki'"); ConfigEnableTrapMusicboxMusic = ((BaseUnityPlugin)this).Config.Bind<bool>("Sound Replacements", "TrapMusicboxMusic", true, "Enable replacement for 'trap musicbox music'"); ConfigEnableCrazyFrog = ((BaseUnityPlugin)this).Config.Bind<bool>("Sound Replacements", "CrazyFrog", true, "Enable replacement for Crazy Frog"); ConfigEnableSystemPoiska = ((BaseUnityPlugin)this).Config.Bind<bool>("Sound Replacements", "SystemPoiska", true, "Enable replacement for 'system poiska'"); ConfigEnableKazik = ((BaseUnityPlugin)this).Config.Bind<bool>("Sound Replacements", "Kazik", true, "Enable replacement for 'kazik' sound"); ConfigEnableNapas = ((BaseUnityPlugin)this).Config.Bind<bool>("Sound Replacements", "Napas", true, "Enable replacement for 'napas' sound"); ConfigEnableUpgrade = ((BaseUnityPlugin)this).Config.Bind<bool>("Sound Replacements", "Upgrade", true, "Enable replacement for 'upgrade' sound"); HunterShotType = ((BaseUnityPlugin)this).Config.Bind<string>("Sound Replacements", "HunterShotType", "standard", new ConfigDescription("Type of sound to play for Hunter shots (standard, kraber, sentinel)", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "standard", "kraber", "sentinel" }), Array.Empty<object>())); DebugAudio = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugAudio", false, "Enable verbose audio debug logs (very chatty)"); assetBundle = LoadAssetBundleFromFile("mates"); if ((Object)(object)assetBundle == (Object)null) { Logger.LogError((object)"Failed to load asset bundle."); return; } LoadAllAudioClips(); if (DebugAudio != null && DebugAudio.Value) { try { DumpAudioClipKeys(200); } catch { } } Logger.LogInfo((object)$"Loaded {soundReplacements.Count} sound replacements"); new Harmony("kossnikitaMatesMod").PatchAll(); Logger.LogInfo((object)"Harmony patches applied successfully!"); try { PlayerDamageResponder.PatchPlayerDamage(); } catch (Exception ex) { Logger.LogError((object)("Failed to install PlayerDamageResponder: " + ex.Message)); } Logger.LogInfo((object)"Plugin kossnikitaMatesMod is loaded!"); } private AssetBundle LoadAssetBundleFromFile(string fileName) { try { string text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? AppDomain.CurrentDomain.BaseDirectory, fileName); if (!File.Exists(text)) { Logger.LogError((object)("AssetBundle file not found at '" + text + "'. Expected it next to the plugin DLL.")); return null; } AssetBundle val = AssetBundle.LoadFromFile(text); if ((Object)(object)val != (Object)null) { Logger.LogDebug((object)("Successfully loaded AssetBundle from file: " + text)); return val; } Logger.LogError((object)("Failed to load AssetBundle from file: " + text)); return null; } catch (Exception ex) { Logger.LogError((object)("Failed to load AssetBundle from file: " + ex.Message)); return null; } } private void LoadAllAudioClips() { string[] allAssetNames = assetBundle.GetAllAssetNames(); foreach (string text in allAssetNames) { if (text.EndsWith(".mp3") || text.EndsWith(".wav") || text.EndsWith(".ogg")) { AudioClip val = assetBundle.LoadAsset<AudioClip>(text); if ((Object)(object)val != (Object)null) { string key = text.Replace(Path.GetExtension(text), "").Substring("assets/mods/mates/".Length).ToLowerInvariant(); audioClips[key] = val; } else { Logger.LogWarning((object)("Failed to load audio clip: " + text)); } } } Logger.LogDebug((object)$"Loaded {audioClips.Count} audio clips and {textureCache.Count} textures into cache"); if (textureCache.Count > 0) { Logger.LogDebug((object)$"Loaded {textureCache.Count} textures into cache"); } } public static AudioClip GetAudioClip(string clipName) { if (string.IsNullOrEmpty(clipName)) { return null; } if (audioClips.TryGetValue(clipName, out var value)) { return value; } Logger.LogError((object)("Audio clip not found in cache: " + clipName)); return null; } private static string NormalizeLookupKey(string key) { if (string.IsNullOrEmpty(key)) { return key; } string text = key.ToLowerInvariant().Trim(); text = text.Replace('\\', '/'); if (text.EndsWith(".mp3") || text.EndsWith(".wav") || text.EndsWith(".ogg")) { text = text.Substring(0, text.LastIndexOf('.')); } if (text.StartsWith("assets/mods/mates/")) { text = text.Substring("assets/mods/mates/".Length); } text = text.Replace('_', ' ').Replace('-', ' '); while (text.Contains(" ")) { text = text.Replace(" ", " "); } return text; } public static Texture2D GetReplacedTexture(string textureName) { if (!ConfigEnableTextureReplacement.Value) { return null; } if (string.IsNullOrEmpty(textureName)) { return null; } if (textureCache.TryGetValue(textureName, out var value)) { Logger.LogDebug((object)("Found replacement texture for '" + textureName + "'")); return value; } return null; } public static AudioClip GetReplacedSound(string originalName) { if (soundReplacements.TryGetValue(originalName, out var value)) { string replacement = value.GetReplacement(originalName); if (replacement != null) { AudioClip audioClip = GetAudioClip(replacement); if ((Object)(object)audioClip != (Object)null) { return audioClip; } Logger.LogWarning((object)("Replacement sound '" + replacement + "' not found for '" + originalName + "'")); } } return null; } public static void DebugLogAudio(string message) { try { if (DebugAudio != null && DebugAudio.Value) { Logger.LogDebug((object)("[DebugAudio] " + message)); } } catch { } } public static void DumpAudioClipKeys(int max = 50) { List<string> list = new List<string>(audioClips.Keys); Logger.LogDebug((object)$"[DebugAudio] audioClips count={list.Count}"); for (int i = 0; i < Math.Min(max, list.Count); i++) { Logger.LogDebug((object)$"[DebugAudio] clip[{i}] = {list[i]}"); } } public static bool HasReplacedSound(string originalName) { return soundReplacements.ContainsKey(originalName); } public static ISoundReplacement GetReplacementType(string originalName) { if (string.IsNullOrEmpty(originalName)) { return null; } if (soundReplacements.TryGetValue(originalName, out var value)) { return value; } ManualLogSource logger = Logger; if (logger != null) { logger.LogDebug((object)("[GetReplacementType] No replacement found for '" + originalName + "'")); } return null; } public static void PlaySound(string clipName, float volume = 1f) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown if (ConfigSound.Value) { AudioClip audioClip = GetAudioClip(clipName); if ((Object)(object)audioClip != (Object)null) { GameObject val = new GameObject("Audio_" + clipName); AudioSource obj = val.AddComponent<AudioSource>(); obj.clip = audioClip; obj.volume = volume; obj.Play(); Object.Destroy((Object)val, audioClip.length); } } } public static void PlaySoundAtPosition(string clipName, Vector3 position, float volume = 1f, string mixerGroupName = null, AudioSource templateSource = null, float maxDistanceMultiplier = 1f) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Invalid comparison between Unknown and I4 if (!ConfigSound.Value) { return; } AudioClip audioClip = GetAudioClip(clipName); if ((Object)(object)audioClip == (Object)null) { Logger.LogError((object)("PlaySoundAtPosition: Audio clip '" + clipName + "' not found in cache!")); return; } GameObject val = new GameObject("Audio_" + clipName); val.transform.position = position; AudioSource val2 = val.AddComponent<AudioSource>(); val2.volume = Math.Max(0f, volume); val2.pitch = 1f; val2.spatialBlend = 1f; val2.minDistance = 1f; val2.maxDistance = 500f * Math.Max(0.01f, maxDistanceMultiplier); val2.rolloffMode = (AudioRolloffMode)0; val2.loop = false; val2.playOnAwake = false; val2.clip = audioClip; if ((Object)(object)templateSource != (Object)null) { try { val2.outputAudioMixerGroup = templateSource.outputAudioMixerGroup; val2.spatialBlend = templateSource.spatialBlend; val2.minDistance = templateSource.minDistance; val2.maxDistance = templateSource.maxDistance * Math.Max(0.01f, maxDistanceMultiplier); val2.rolloffMode = templateSource.rolloffMode; val2.dopplerLevel = templateSource.dopplerLevel; val2.spread = templateSource.spread; if ((int)templateSource.rolloffMode == 2) { AnimationCurve customCurve = templateSource.GetCustomCurve((AudioSourceCurveType)0); if (customCurve != null) { val2.SetCustomCurve((AudioSourceCurveType)0, customCurve); } } val2.spatialize = templateSource.spatialize; val2.spatializePostEffects = templateSource.spatializePostEffects; val2.reverbZoneMix = templateSource.reverbZoneMix; } catch (Exception ex) { Logger.LogWarning((object)("PlaySoundAtPosition: Failed to copy template AudioSource settings: " + ex.Message)); } } val2.Play(); Object.Destroy((Object)(object)val, audioClip.length + 0.5f); } private static void LogAvailableAudioMixers() { } } public abstract class CounterBasedSoundReplacement : ISoundReplacement { protected readonly Func<ConfigEntry<bool>> configGetter; protected CounterBasedSoundReplacement(Func<ConfigEntry<bool>> configGetter) { this.configGetter = configGetter; } protected int GetAndIncrementCounter(string originalName) { return Plugin.playbackCounters.AddOrUpdate(originalName, 1, (string key, int oldValue) => oldValue + 1); } public abstract string GetReplacement(string originalName); } public class SimpleSoundReplacement : ISoundReplacement { private readonly string replacementName; private readonly Func<ConfigEntry<bool>> configGetter; private readonly float? pitch; private readonly float? volume; private readonly float? minDistance; private readonly float? maxDistance; private readonly AudioRolloffMode? rolloffMode; public float? Pitch => pitch; public float? Volume => volume; public float? MinDistance => minDistance; public float? MaxDistance => maxDistance; public AudioRolloffMode? RolloffMode => rolloffMode; public SimpleSoundReplacement(string replacementName, Func<ConfigEntry<bool>> configGetter = null, float? pitch = null, float? volume = null, float? minDistance = null, float? maxDistance = null, AudioRolloffMode? rolloffMode = null) { this.replacementName = replacementName; this.configGetter = configGetter; this.pitch = pitch; this.volume = volume; this.minDistance = minDistance; this.maxDistance = maxDistance; this.rolloffMode = rolloffMode; } public string GetReplacement(string originalName) { Func<ConfigEntry<bool>> func = configGetter; if (func != null && func()?.Value == false) { return null; } return replacementName; } } public class WeightedRandomSoundReplacement : ISoundReplacement { private readonly WeightedSound[] sounds; private readonly int totalWeight; private readonly Func<ConfigEntry<bool>> configGetter; private readonly float? volume; private readonly float? minDistance; private readonly float? maxDistance; private readonly AudioRolloffMode? rolloffMode; public float? Volume => volume; public float? MinDistance => minDistance; public float? MaxDistance => maxDistance; public AudioRolloffMode? RolloffMode => rolloffMode; public WeightedRandomSoundReplacement(WeightedSound[] sounds, Func<ConfigEntry<bool>> configGetter = null, float? volume = null, float? minDistance = null, float? maxDistance = null, AudioRolloffMode? rolloffMode = null) { this.sounds = sounds; totalWeight = sounds.Sum((WeightedSound s) => s.Weight); this.configGetter = configGetter; this.volume = volume; this.minDistance = minDistance; this.maxDistance = maxDistance; this.rolloffMode = rolloffMode; } public string GetReplacement(string originalName) { Func<ConfigEntry<bool>> func = configGetter; if (func != null && func()?.Value == false) { return null; } int num = Random.Range(0, totalWeight); int num2 = 0; WeightedSound[] array = sounds; for (int i = 0; i < array.Length; i++) { WeightedSound weightedSound = array[i]; num2 += weightedSound.Weight; if (num < num2) { return weightedSound.Name; } } return sounds[0].Name; } } public struct WeightedSound { public string Name; public int Weight; public WeightedSound(string name, int weight) { Name = name; Weight = weight; } } public class ConditionalSoundReplacement : CounterBasedSoundReplacement { private readonly string replacementName; private readonly Func<int, bool> condition; private readonly float? pitch; private readonly float? volume; private readonly float? minDistance; private readonly float? maxDistance; private readonly AudioRolloffMode? rolloffMode; public float? Pitch => pitch; public float? Volume => volume; public float? MinDistance => minDistance; public float? MaxDistance => maxDistance; public AudioRolloffMode? RolloffMode => rolloffMode; public ConditionalSoundReplacement(string replacementName, Func<int, bool> condition, Func<ConfigEntry<bool>> configGetter = null, float? pitch = null, float? volume = null, float? minDistance = null, float? maxDistance = null, AudioRolloffMode? rolloffMode = null) : base(configGetter) { this.replacementName = replacementName; this.condition = condition; this.pitch = pitch; this.volume = volume; this.minDistance = minDistance; this.maxDistance = maxDistance; this.rolloffMode = rolloffMode; } public override string GetReplacement(string originalName) { Func<ConfigEntry<bool>> func = configGetter; if (func != null && func()?.Value == false) { return null; } int andIncrementCounter = GetAndIncrementCounter(originalName); if (condition(andIncrementCounter)) { return replacementName; } return null; } } public class SequentialSoundReplacement : CounterBasedSoundReplacement { private readonly string[] sounds; private readonly float maxDistanceMultiplier; private readonly float? volume; private readonly float? minDistance; private readonly float? maxDistance; private readonly AudioRolloffMode? rolloffMode; private readonly bool playtoend; private readonly float? pitch; public bool PlayToEnd => playtoend; public float? Pitch => pitch; public float MaxDistanceMultiplier => maxDistanceMultiplier; public float? Volume => volume; public float? MinDistance => minDistance; public float? MaxDistance => maxDistance; public AudioRolloffMode? RolloffMode => rolloffMode; public SequentialSoundReplacement(string[] sounds, Func<ConfigEntry<bool>> configGetter = null, float maxDistanceMultiplier = 1f, float? volume = null, bool playtoend = false, float? pitch = null, float? minDistance = null, float? maxDistance = null, AudioRolloffMode? rolloffMode = null) : base(configGetter) { this.sounds = sounds; this.maxDistanceMultiplier = Math.Max(0.01f, maxDistanceMultiplier); this.volume = (volume.HasValue ? new float?(Math.Max(0f, volume.Value)) : null); this.playtoend = playtoend; this.pitch = pitch; this.minDistance = minDistance; this.maxDistance = maxDistance; this.rolloffMode = rolloffMode; } public override string GetReplacement(string originalName) { Func<ConfigEntry<bool>> func = configGetter; if (func != null && func()?.Value == false) { return null; } int num = (GetAndIncrementCounter(originalName) - 1) % sounds.Length; return sounds[num]; } public string GetReplacementForAudioSource(string originalName, AudioSource audioSource) { return GetReplacement(originalName); } } public class AdditionalSoundReplacement : ISoundReplacement { private readonly string replacementName; private readonly Func<ConfigEntry<bool>> configGetter; private readonly bool muteOriginal; private readonly bool is2D; private readonly string mixerGroupName; private readonly float? volume; private readonly float maxDistanceMultiplier; private readonly float? minDistance; private readonly float? maxDistance; private readonly AudioRolloffMode? rolloffMode; private readonly float? pitch; public bool ShouldMuteOriginal => muteOriginal; public bool IsAdditional => true; public bool Is2D => is2D; public string MixerGroupName => mixerGroupName; public float? Volume => volume; public float MaxDistanceMultiplier => maxDistanceMultiplier; public float? MinDistance => minDistance; public float? MaxDistance => maxDistance; public AudioRolloffMode? RolloffMode => rolloffMode; public float? Pitch => pitch; public AdditionalSoundReplacement(string replacementName, Func<ConfigEntry<bool>> configGetter = null, bool muteOriginal = true, bool is2D = false, string mixerGroupName = null, float? volume = null, float maxDistanceMultiplier = 1f, float? pitch = null, float? minDistance = null, float? maxDistance = null, AudioRolloffMode? rolloffMode = null) { this.replacementName = replacementName; this.configGetter = configGetter; this.muteOriginal = muteOriginal; this.is2D = is2D; this.mixerGroupName = mixerGroupName; this.volume = (volume.HasValue ? new float?(Math.Max(0f, Math.Min(1f, volume.Value))) : null); this.maxDistanceMultiplier = Math.Max(0.01f, maxDistanceMultiplier); this.minDistance = minDistance; this.maxDistance = maxDistance; this.rolloffMode = rolloffMode; this.pitch = pitch; } public string GetReplacement(string originalName) { Func<ConfigEntry<bool>> func = configGetter; if (func != null && func()?.Value == false) { return null; } return replacementName; } } public class RouletteColorSoundReplacement : ISoundReplacement { private readonly string blackPrizeSound; private readonly string otherPrizeSound; private readonly Func<ConfigEntry<bool>> configGetter; private readonly bool resetTimeOnPlay; public bool ResetTimeOnPlay => resetTimeOnPlay; public RouletteColorSoundReplacement(string blackPrizeSound, string otherPrizeSound, Func<ConfigEntry<bool>> configGetter = null, bool resetTimeOnPlay = true) { this.blackPrizeSound = blackPrizeSound; this.otherPrizeSound = otherPrizeSound; this.configGetter = configGetter; this.resetTimeOnPlay = resetTimeOnPlay; } public string GetReplacement(string originalName) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Invalid comparison between Unknown and I4 //IL_010b: Unknown result type (might be due to invalid IL or missing references) Func<ConfigEntry<bool>> func = configGetter; if (func != null && func()?.Value == false) { return otherPrizeSound; } try { EnemySpinny val = Object.FindObjectOfType<EnemySpinny>(); if ((Object)(object)val == (Object)null) { ManualLogSource logger = Plugin.Logger; if (logger != null) { logger.LogWarning((object)"[Roulette] EnemySpinny not found, using default sound"); } return otherPrizeSound; } float num = Plugin.LastRouletteTargetAngle; if (Plugin.rouletteAngles.TryGetValue(val, out var value)) { num = value.TargetAngle; } Colors currentColorbyAngle = val.GetCurrentColorbyAngle(num); ManualLogSource logger2 = Plugin.Logger; if (logger2 != null) { logger2.LogDebug((object)$"[Roulette] Angle: {num}°, Prize: {currentColorbyAngle}"); } if ((int)currentColorbyAngle == 4) { ManualLogSource logger3 = Plugin.Logger; if (logger3 != null) { logger3.LogDebug((object)("[Roulette] BLACK PRIZE! Playing: " + blackPrizeSound)); } return blackPrizeSound; } ManualLogSource logger4 = Plugin.Logger; if (logger4 != null) { logger4.LogDebug((object)$"[Roulette] Other prize: {currentColorbyAngle}. Playing: {otherPrizeSound}"); } return otherPrizeSound; } catch (Exception ex) { ManualLogSource logger5 = Plugin.Logger; if (logger5 != null) { logger5.LogError((object)("[Roulette] Error determining prize: " + ex.Message)); } return otherPrizeSound; } } } public class DynamicSoundReplacement : ISoundReplacement { private readonly Func<string> replacementProvider; private readonly Func<bool> enabledProvider; private readonly float? pitch; private readonly float? volume; private readonly float? minDistance; private readonly float? maxDistance; private readonly AudioRolloffMode? rolloffMode; public float? Pitch => pitch; public float? Volume => volume; public float? MinDistance => minDistance; public float? MaxDistance => maxDistance; public AudioRolloffMode? RolloffMode => rolloffMode; public DynamicSoundReplacement(Func<string> replacementProvider, Func<bool> enabledProvider = null, float? pitch = null, float? volume = null, float? minDistance = null, float? maxDistance = null, AudioRolloffMode? rolloffMode = null) { this.replacementProvider = replacementProvider ?? throw new ArgumentNullException("replacementProvider"); this.enabledProvider = enabledProvider; this.pitch = pitch; this.volume = volume; this.minDistance = minDistance; this.maxDistance = maxDistance; this.rolloffMode = rolloffMode; } public string GetReplacement(string originalName) { if (enabledProvider != null && !enabledProvider()) { return null; } try { string text = replacementProvider(); if (string.IsNullOrEmpty(text)) { return null; } return text; } catch { return null; } } } public class AdditionalSequentialSoundReplacement : CounterBasedSoundReplacement, ISoundReplacement { private readonly string[] sounds; private readonly bool muteOriginal; private readonly bool is2D; private readonly string mixerGroupName; private readonly float? volume; private readonly float maxDistanceMultiplier; private readonly float? pitch; private readonly float? minDistance; private readonly float? maxDistance; private readonly AudioRolloffMode? rolloffMode; private readonly bool playtoend; public bool ShouldMuteOriginal => muteOriginal; public bool IsAdditional => true; public bool Is2D => is2D; public string MixerGroupName => mixerGroupName; public float? Volume => volume; public float MaxDistanceMultiplier => maxDistanceMultiplier; public float? MinDistance => minDistance; public float? MaxDistance => maxDistance; public AudioRolloffMode? RolloffMode => rolloffMode; public float? Pitch => pitch; public bool PlayToEnd => playtoend; public AdditionalSequentialSoundReplacement(string[] sounds, Func<ConfigEntry<bool>> configGetter = null, bool muteOriginal = true, bool is2D = false, string mixerGroupName = null, float? volume = null, float maxDistanceMultiplier = 1f, float? pitch = null, float? minDistance = null, float? maxDistance = null, AudioRolloffMode? rolloffMode = null, bool playtoend = false) : base(configGetter) { this.sounds = sounds ?? throw new ArgumentNullException("sounds"); this.muteOriginal = muteOriginal; this.is2D = is2D; this.mixerGroupName = mixerGroupName; this.volume = (volume.HasValue ? new float?(Math.Max(0f, Math.Min(1f, volume.Value))) : null); this.maxDistanceMultiplier = Math.Max(0.01f, maxDistanceMultiplier); this.pitch = pitch; this.minDistance = minDistance; this.maxDistance = maxDistance; this.rolloffMode = rolloffMode; this.playtoend = playtoend; } public override string GetReplacement(string originalName) { Func<ConfigEntry<bool>> func = configGetter; if (func != null && func()?.Value == false) { return null; } int num = (GetAndIncrementCounter(originalName) - 1) % sounds.Length; return sounds[num]; } public string GetReplacementForAudioSource(string originalName, AudioSource audioSource) { return GetReplacement(originalName); } } public class TextureReplacer : MonoBehaviour { [CompilerGenerated] private sealed class <InitialScan>d__5 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public TextureReplacer <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <InitialScan>d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown int num = <>1__state; TextureReplacer textureReplacer = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; case 1: <>1__state = -1; textureReplacer.ScanAndReplaceTextures(); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static readonly HashSet<string> textureNamesToReplace = new HashSet<string>(); private static readonly HashSet<int> processedMaterials = new HashSet<int>(); private float scanInterval = 2f; private float lastScanTime; private void Start() { ((MonoBehaviour)this).StartCoroutine(InitialScan()); } [IteratorStateMachine(typeof(<InitialScan>d__5))] private IEnumerator InitialScan() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <InitialScan>d__5(0) { <>4__this = this }; } private void Update() { if (Plugin.ConfigEnableTextureReplacement.Value && Time.time - lastScanTime >= scanInterval) { lastScanTime = Time.time; ScanAndReplaceTextures(); } } private void ScanAndReplaceTextures() { if (!Plugin.ConfigEnableTextureReplacement.Value) { return; } Renderer[] array = Object.FindObjectsOfType<Renderer>(); foreach (Renderer val in array) { if ((Object)(object)val == (Object)null) { continue; } Material[] sharedMaterials = val.sharedMaterials; if (sharedMaterials == null) { continue; } Material[] array2 = sharedMaterials; foreach (Material val2 in array2) { if (!((Object)(object)val2 == (Object)null)) { int instanceID = ((Object)val2).GetInstanceID(); if (!processedMaterials.Contains(instanceID)) { TryReplaceMaterialTextures(val2, ((Object)((Component)val).gameObject).name); } } } } } private void TryReplaceMaterialTextures(Material material, string objectName) { if ((Object)(object)material == (Object)null) { return; } int instanceID = ((Object)material).GetInstanceID(); string[] obj = new string[6] { "_MainTex", "_EmissionMap", "_Emissive", "_BaseMap", "_BaseColorMap", "_AlbedoMap" }; bool flag = false; string[] array = obj; foreach (string text in array) { if (!material.HasProperty(text)) { continue; } Texture texture = material.GetTexture(text); if ((Object)(object)texture == (Object)null) { continue; } string text2 = ((Object)texture).name?.ToLowerInvariant() ?? ""; if (textureNamesToReplace.Contains(text2)) { Texture2D replacedTexture = Plugin.GetReplacedTexture(text2); if ((Object)(object)replacedTexture != (Object)null) { material.SetTexture(text, (Texture)(object)replacedTexture); flag = true; } } } if (flag || !HasRelevantTextures(material)) { processedMaterials.Add(instanceID); } } private bool HasRelevantTextures(Material material) { return (((Object)material).name?.ToLowerInvariant() ?? "").Contains("valuable"); } public static void ClearCache() { processedMaterials.Clear(); } private void OnDestroy() { processedMaterials.Clear(); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "kossnikitaMatesMod"; public const string PLUGIN_NAME = "kossnikita mates mod"; public const string PLUGIN_VERSION = "1.0.3"; } }