Decompiled source of Audio Guard v1.0.0

Mods/AudioGuard.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppScheduleOne.Audio;
using Il2CppSystem.Collections.Generic;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using ScheduleIAudioGuard;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(AudioGuard), "Schedule I Audio Guard", "1.0.0", "RebornReaper", null)]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: MelonPlatformDomain(/*Could not decode attribute arguments.*/)]
[assembly: HarmonyDontPatchAll]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("RebornReaper")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Standalone audio capacity utility for Schedule I.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Audio Guard for Schedule I")]
[assembly: AssemblyTitle("AudioGuard")]
[assembly: AssemblyVersion("1.0.0.0")]
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;
		}
	}
}
namespace ScheduleIAudioGuard
{
	public sealed class AudioGuard : MelonMod
	{
		private struct AudioConfig
		{
			public int SpeakerMode;

			public int DspBufferSize;

			public int SampleRate;

			public int RealVoices;

			public int VirtualVoices;
		}

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void GetConfiguration(out AudioConfig config);

		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		[return: MarshalAs(UnmanagedType.I1)]
		private delegate bool SetConfiguration(ref AudioConfig config);

		private static AudioGuard self = null;

		private static MelonPreferences_Entry<int> poolSize = null;

		private static MelonPreferences_Entry<int> realVoices = null;

		private static MelonPreferences_Entry<int> virtualVoices = null;

		private static MelonPreferences_Entry<bool> enabled = null;

		private static MelonPreferences_Entry<bool> diagnostics = null;

		private static long requests;

		private static long misses;

		private static int poolHighWater;

		private static float nextReport;

		private static bool voicesAttempted;

		private static bool reportFailed;

		private static SFXManager? manager;

		private static string voiceStatus = "not checked";

		public override void OnInitializeMelon()
		{
			self = this;
			MelonPreferences_Category obj = MelonPreferences.CreateCategory("AudioGuard");
			enabled = obj.CreateEntry<bool>("Enabled", true, (string)null, "Enable changes. Restart after editing any setting.", false, false, (ValueValidator)null, (string)null);
			poolSize = obj.CreateEntry<int>("SoundPoolSize", 64, (string)null, "Minimum footstep/impact pool, clamped to 20-256. Does not shrink a larger existing pool.", false, false, (ValueValidator)null, (string)null);
			realVoices = obj.CreateEntry<int>("RealVoices", 128, (string)null, "Minimum simultaneous audible sounds, clamped to 32-256.", false, false, (ValueValidator)null, (string)null);
			virtualVoices = obj.CreateEntry<int>("VirtualVoices", 1024, (string)null, "Minimum tracked sounds, clamped to 128-4096.", false, false, (ValueValidator)null, (string)null);
			diagnostics = obj.CreateEntry<bool>("Diagnostics", true, (string)null, "Audio summary every 60 seconds in MelonLoader Latest.log.", false, false, (ValueValidator)null, (string)null);
			if (!enabled.Value)
			{
				((MelonBase)this).LoggerInstance.Msg("Disabled by configuration.");
				return;
			}
			Patch("SetupSoundPool", "ExpandPool");
			Patch("TryPullAudioSource", "RecordPoolRequest");
			((MelonBase)this).LoggerInstance.Msg("Loaded. Pool and voice capacity changes apply this session only. Save files are not edited.");
		}

		private void Patch(string method, string postfix)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			try
			{
				((MelonBase)this).HarmonyInstance.Patch((MethodBase)AccessTools.Method(typeof(SFXManager), method, (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(AudioGuard), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Error("Could not patch " + method + ": " + ex.Message);
			}
		}

		private static void ExpandPool(SFXManager __instance)
		{
			try
			{
				manager = __instance;
				List<AudioSourceController> soundPool = __instance._soundPool;
				List<AudioSourceController> soundsInUse = __instance._soundsInUse;
				SFXConfiguration configuration = __instance._configuration;
				AudioSourceController val = ((configuration != null) ? configuration.ImpactSoundPrefab : null);
				if (soundPool == null || soundsInUse == null || (Object)(object)val == (Object)null)
				{
					((MelonBase)self).LoggerInstance.Warning("Pool configuration unavailable; leaving original pool intact.");
					return;
				}
				int num = soundPool.Count + soundsInUse.Count;
				int num2 = Math.Max(num, Math.Clamp(poolSize.Value, 20, 256));
				while (soundPool.Count + soundsInUse.Count < num2)
				{
					AudioSourceController val2 = Object.Instantiate<AudioSourceController>(val, ((Component)__instance).transform);
					val2.Stop();
					soundPool.Add(val2);
				}
				((MelonBase)self).LoggerInstance.Msg($"Footstep/impact pool: {num} -> {soundPool.Count + soundsInUse.Count} sources.");
			}
			catch (Exception ex)
			{
				((MelonBase)self).LoggerInstance.Error("Pool expansion failed: " + ex.Message);
			}
		}

		private static void RecordPoolRequest(SFXManager __instance, bool __result)
		{
			if (!diagnostics.Value)
			{
				return;
			}
			requests++;
			if (!__result)
			{
				misses++;
			}
			try
			{
				manager = __instance;
				poolHighWater = Math.Max(poolHighWater, __instance._soundsInUse?.Count ?? 0);
			}
			catch
			{
			}
		}

		public override void OnSceneWasInitialized(int buildIndex, string sceneName)
		{
			if (enabled.Value)
			{
				if (!voicesAttempted && (sceneName.Equals("Menu", StringComparison.OrdinalIgnoreCase) || sceneName.Contains("MainMenu", StringComparison.OrdinalIgnoreCase)))
				{
					voicesAttempted = true;
					ConfigureVoices();
				}
				requests = (misses = 0L);
				poolHighWater = 0;
				nextReport = Time.realtimeSinceStartup + 15f;
				if (diagnostics.Value)
				{
					((MelonBase)this).LoggerInstance.Msg($"Scene initialized: {sceneName}; voices: {voiceStatus}.");
				}
			}
		}

		private void ConfigureVoices()
		{
			try
			{
				IntPtr intPtr = IL2CPP.il2cpp_resolve_icall("UnityEngine.AudioSettings::GetConfiguration_Injected");
				IntPtr intPtr2 = IL2CPP.il2cpp_resolve_icall("UnityEngine.AudioSettings::SetConfiguration_Injected");
				if (intPtr == IntPtr.Zero || intPtr2 == IntPtr.Zero)
				{
					throw new NotSupportedException("Unity audio configuration calls unavailable");
				}
				GetConfiguration delegateForFunctionPointer = Marshal.GetDelegateForFunctionPointer<GetConfiguration>(intPtr);
				SetConfiguration delegateForFunctionPointer2 = Marshal.GetDelegateForFunctionPointer<SetConfiguration>(intPtr2);
				delegateForFunctionPointer(out var config);
				if (config.SpeakerMode < 1 || config.SpeakerMode > 7 || config.SampleRate < 8000 || config.SampleRate > 384000 || config.DspBufferSize <= 0 || config.RealVoices <= 0 || config.VirtualVoices < config.RealVoices)
				{
					throw new InvalidOperationException("Unexpected Unity audio configuration; refusing to change it");
				}
				AudioConfig config2 = config;
				config2.RealVoices = Math.Max(config.RealVoices, Math.Clamp(realVoices.Value, 32, 256));
				config2.VirtualVoices = Math.Max(config.VirtualVoices, Math.Max(config2.RealVoices, Math.Clamp(virtualVoices.Value, 128, 4096)));
				if ((config2.RealVoices != config.RealVoices || config2.VirtualVoices != config.VirtualVoices) && !delegateForFunctionPointer2(ref config2))
				{
					delegateForFunctionPointer2(ref config);
					throw new InvalidOperationException("Unity rejected the requested voice limits; rollback requested");
				}
				delegateForFunctionPointer(out var config3);
				voiceStatus = $"real {config.RealVoices}->{config3.RealVoices}, virtual {config.VirtualVoices}->{config3.VirtualVoices}";
				((MelonBase)this).LoggerInstance.Msg("Unity voice capacity: " + voiceStatus + ". Speaker mode, sample rate and buffer preserved.");
			}
			catch (Exception ex)
			{
				voiceStatus = "capacity change unavailable";
				((MelonBase)this).LoggerInstance.Warning("Voice adjustment skipped: " + ex.Message + ". Pool expansion remains active.");
			}
		}

		public override void OnUpdate()
		{
			if (!enabled.Value || !diagnostics.Value || reportFailed || Time.realtimeSinceStartup < nextReport)
			{
				return;
			}
			nextReport = Time.realtimeSinceStartup + 60f;
			try
			{
				Il2CppArrayBase<AudioSource> val = Object.FindObjectsOfType<AudioSource>();
				int num = 0;
				int num2 = 0;
				int num3 = 0;
				List<string> list = new List<string>();
				foreach (AudioSource item in val)
				{
					if ((Object)(object)item == (Object)null || !item.isPlaying)
					{
						continue;
					}
					num++;
					if (item.loop)
					{
						num3++;
					}
					if (item.isVirtual)
					{
						num2++;
						if (list.Count < 8)
						{
							DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(13, 3);
							defaultInterpolatedStringHandler.AppendFormatted(((Object)item).name);
							defaultInterpolatedStringHandler.AppendLiteral("/");
							AudioClip clip = item.clip;
							defaultInterpolatedStringHandler.AppendFormatted(((clip != null) ? ((Object)clip).name : null) ?? "one-shot");
							defaultInterpolatedStringHandler.AppendLiteral(" (priority ");
							defaultInterpolatedStringHandler.AppendFormatted(item.priority);
							defaultInterpolatedStringHandler.AppendLiteral(")");
							list.Add(defaultInterpolatedStringHandler.ToStringAndClear());
						}
					}
				}
				int value = -1;
				int value2 = -1;
				if ((Object)(object)manager != (Object)null)
				{
					value = manager._soundPool?.Count ?? (-1);
					value2 = manager._soundsInUse?.Count ?? (-1);
				}
				((MelonBase)this).LoggerInstance.Msg($"[AudioSummary] activeSources={val.Length}, playingSources={num}, virtualizedSources={num2}, loopingSources={num3}; poolFree={value}, poolBusy={value2}, poolPeak={poolHighWater}; poolRequests={requests}, droppedPoolRequests={misses}; {voiceStatus}.");
				if (list.Count > 0)
				{
					((MelonBase)this).LoggerInstance.Msg("[VirtualizedExamples] " + string.Join("; ", list));
				}
			}
			catch (Exception ex)
			{
				reportFailed = true;
				((MelonBase)this).LoggerInstance.Warning("Audio reporting disabled after error: " + ex.Message + ". Capacity patches remain active.");
			}
		}
	}
}