Decompiled source of In Falsus ChartAudioFix v0.4.1

BepInEx/plugins/InFalsusChartAudioFix/InFalsusChartAudio.dll

Decompiled 9 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using BepInEx.Unity.IL2CPP.Hook;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(/*Could not decode attribute arguments.*/)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : System.Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(/*Could not decode attribute arguments.*/)]
	internal sealed class NullableAttribute : System.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(/*Could not decode attribute arguments.*/)]
	internal sealed class NullableContextAttribute : System.Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(/*Could not decode attribute arguments.*/)]
	internal sealed class RefSafetyRulesAttribute : System.Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace InFalsusChartAudio
{
	public static class AudioSourceState
	{
		public static bool IsReady(int result, int state)
		{
			if (result == 0)
			{
				if (state != 0)
				{
					return state == 6;
				}
				return true;
			}
			return false;
		}
	}
	public enum AudioRoute
	{
		Attack,
		Hold,
		MouseHold,
		MouseTransition,
		KeyboardHoldHit,
		SideKeyHit
	}
	public struct AudioCue
	{
		public long Id;

		public double TimeMs;

		public double EndMs;

		public AudioRoute Route;

		public bool Loop;
	}
	public sealed class AudioTimeline
	{
		public readonly List<AudioCue> Events = new List<AudioCue>();

		private int position;

		private double previous = 0.0 / 0.0;

		private bool armed;

		private bool skipCurrent;

		public int Position => position;

		public void Sort()
		{
			Events.Sort((Comparison<AudioCue>)delegate(AudioCue a, AudioCue b)
			{
				int num = a.TimeMs.CompareTo(b.TimeMs);
				return (num == 0) ? a.Id.CompareTo(b.Id) : num;
			});
		}

		private int LowerBound(double time)
		{
			int num = 0;
			int num2 = Events.Count;
			while (num < num2)
			{
				int num3 = num + (num2 - num) / 2;
				if (Events[num3].TimeMs < time)
				{
					num = num3 + 1;
				}
				else
				{
					num2 = num3;
				}
			}
			return num;
		}

		public void Reset(bool skipAtCurrentTime = false)
		{
			armed = false;
			previous = 0.0 / 0.0;
			position = 0;
			skipCurrent = skipAtCurrentTime;
		}

		public void Advance(double now, double ahead, Action<AudioCue, double> submit, Action cancel)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			if (double.IsNaN(now) || double.IsInfinity(now) || ahead < 0.0 || double.IsNaN(ahead) || double.IsInfinity(ahead))
			{
				throw new ArgumentOutOfRangeException();
			}
			bool flag = armed && (now - previous < -20.0 || now - previous > 250.0);
			if (flag)
			{
				cancel.Invoke();
				armed = false;
			}
			if (!armed)
			{
				position = LowerBound(skipCurrent ? (now + 0.001) : ((!flag && now >= 0.0 && now <= 50.0) ? 0.0 : now));
				Enumerator<AudioCue> enumerator = Events.GetEnumerator();
				try
				{
					while (enumerator.MoveNext())
					{
						AudioCue current = enumerator.Current;
						if (current.Loop && (current.TimeMs < now || (skipCurrent && current.TimeMs == now)) && current.EndMs > now)
						{
							submit.Invoke(current, now);
						}
					}
				}
				finally
				{
					((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
				}
				armed = true;
				skipCurrent = false;
			}
			else if (now < previous)
			{
				return;
			}
			while (position < Events.Count && Events[position].TimeMs <= now + ahead)
			{
				AudioCue audioCue = Events[position];
				submit.Invoke(audioCue, now);
				position++;
			}
			previous = now;
		}

		public static List<AudioCue> CoalesceAttacks(List<AudioCue> source, double windowMs, out int merged)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			source.Sort((Comparison<AudioCue>)delegate(AudioCue a, AudioCue b)
			{
				int num2 = a.TimeMs.CompareTo(b.TimeMs);
				return (num2 == 0) ? a.Id.CompareTo(b.Id) : num2;
			});
			List<AudioCue> val = new List<AudioCue>();
			Dictionary<AudioRoute, double> val2 = new Dictionary<AudioRoute, double>();
			merged = 0;
			Enumerator<AudioCue> enumerator = source.GetEnumerator();
			try
			{
				double num = default(double);
				while (enumerator.MoveNext())
				{
					AudioCue current = enumerator.Current;
					if (!current.Loop)
					{
						if (val2.TryGetValue(current.Route, ref num) && current.TimeMs - num <= windowMs)
						{
							merged++;
							continue;
						}
						val2[current.Route] = current.TimeMs;
					}
					val.Add(current);
				}
				return val;
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
		}

		public static List<AudioCue> MergeLoops(System.Collections.Generic.IEnumerable<AudioCue> source, AudioRoute route)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			List<AudioCue> val = new List<AudioCue>();
			System.Collections.Generic.IEnumerator<AudioCue> enumerator = source.GetEnumerator();
			try
			{
				while (((System.Collections.IEnumerator)enumerator).MoveNext())
				{
					AudioCue current = enumerator.Current;
					if (current.Route == route && current.Loop && current.EndMs > current.TimeMs)
					{
						val.Add(current);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator)?.Dispose();
			}
			val.Sort((Comparison<AudioCue>)((AudioCue a, AudioCue b) => a.TimeMs.CompareTo(b.TimeMs)));
			List<AudioCue> val2 = new List<AudioCue>();
			Enumerator<AudioCue> enumerator2 = val.GetEnumerator();
			try
			{
				while (enumerator2.MoveNext())
				{
					AudioCue current2 = enumerator2.Current;
					if (val2.Count == 0 || current2.TimeMs > val2[val2.Count - 1].EndMs)
					{
						val2.Add(current2);
						continue;
					}
					AudioCue audioCue = val2[val2.Count - 1];
					audioCue.EndMs = Math.Max(audioCue.EndMs, current2.EndMs);
					val2[val2.Count - 1] = audioCue;
				}
				return val2;
			}
			finally
			{
				((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
			}
		}
	}
	public static class DspTiming
	{
		public static ulong Target(ulong parentClock, double deltaMs, int sampleRate, double rate)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			if (sampleRate <= 0 || !Finite(deltaMs) || !Finite(rate) || rate <= 0.0)
			{
				throw new ArgumentOutOfRangeException();
			}
			double num = Math.Max(0.0, deltaMs) * (double)sampleRate / (1000.0 * rate);
			if (num > (double)(18446744073709551615uL - parentClock))
			{
				throw new ArgumentOutOfRangeException();
			}
			return checked(parentClock + (ulong)Math.Round(num));
		}

		private static bool Finite(double value)
		{
			if (!double.IsNaN(value))
			{
				return !double.IsInfinity(value);
			}
			return false;
		}
	}
	internal sealed class FmodScheduler : System.IDisposable
	{
		private const string Dll = "fmodstudio";

		private static System.IntPtr nativeModule;

		private static readonly object moduleGate;

		private readonly System.IntPtr system;

		private readonly System.IntPtr[] sounds;

		private readonly System.IntPtr[] groups;

		private readonly int sampleRate;

		private readonly List<System.IntPtr> owned = new List<System.IntPtr>();

		public int SampleRate => sampleRate;

		public int OwnedCount => owned.Count;

		private static System.IntPtr ResolveModule()
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			if (nativeModule != System.IntPtr.Zero)
			{
				return nativeModule;
			}
			lock (moduleGate)
			{
				if (nativeModule != System.IntPtr.Zero)
				{
					return nativeModule;
				}
				foreach (ProcessModule item in (ReadOnlyCollectionBase)Process.GetCurrentProcess().Modules)
				{
					ProcessModule val = item;
					if (val.ModuleName.Equals("fmodstudio.dll", (StringComparison)5))
					{
						nativeModule = val.BaseAddress;
						return nativeModule;
					}
				}
				throw new DllNotFoundException("The game's fmodstudio.dll has not loaded yet.");
			}
		}

		static FmodScheduler()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			moduleGate = new object();
			NativeLibrary.SetDllImportResolver(typeof(FmodScheduler).Assembly, (DllImportResolver)((string name, Assembly assembly, DllImportSearchPath? path) => (!(name == "fmodstudio")) ? System.IntPtr.Zero : ResolveModule()));
		}

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_System_GetSoftwareFormat(System.IntPtr system, out int rate, out int mode, out int speakers);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_System_PlaySound(System.IntPtr system, System.IntPtr sound, System.IntPtr group, int paused, out System.IntPtr channel);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_Channel_GetDSPClock(System.IntPtr channel, out ulong own, out ulong parent);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_Channel_SetDelay(System.IntPtr channel, ulong start, ulong end, int stop);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_Channel_SetPaused(System.IntPtr channel, int paused);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_Channel_SetMode(System.IntPtr channel, uint mode);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_Channel_SetPitch(System.IntPtr channel, float pitch);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_Channel_SetVolume(System.IntPtr channel, float volume);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_Channel_SetPosition(System.IntPtr channel, uint position, uint unit);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_Channel_Stop(System.IntPtr channel);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_Sound_GetMode(System.IntPtr sound, out uint mode);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_Sound_GetLength(System.IntPtr sound, out uint length, uint unit);

		[DllImport("fmodstudio", CallingConvention = (CallingConvention)2)]
		private static extern int FMOD5_Sound_GetOpenState(System.IntPtr sound, out int state, out uint percent, out int starving, out int busy);

		public static bool IsReady(System.IntPtr sound)
		{
			int result;
			int state;
			return Probe(sound, out result, out state);
		}

		public static bool Probe(System.IntPtr sound, out int result, out int state)
		{
			state = -1;
			result = -1;
			if (sound == System.IntPtr.Zero)
			{
				return false;
			}
			result = FMOD5_Sound_GetOpenState(sound, out state, out var _, out var _, out var _);
			return AudioSourceState.IsReady(result, state);
		}

		private static void Check(int result, string operation)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			if (result != 0)
			{
				throw new InvalidOperationException(operation + " failed, FMOD result=" + result);
			}
		}

		public FmodScheduler(System.IntPtr system, System.IntPtr[] sounds, System.IntPtr[] groups, bool[] used)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: 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)
			this.system = system;
			this.sounds = sounds;
			this.groups = groups;
			Check(FMOD5_System_GetSoftwareFormat(system, out sampleRate, out var _, out var _), "GetSoftwareFormat");
			if (sampleRate < 8000 || sampleRate > 384000)
			{
				throw new InvalidOperationException("Unexpected FMOD sample rate");
			}
			for (int i = 0; i < sounds.Length; i++)
			{
				if (used[i])
				{
					if (groups[i] == System.IntPtr.Zero || !IsReady(sounds[i]))
					{
						throw new InvalidOperationException("Sound/group is not ready for route " + i);
					}
					Check(FMOD5_Sound_GetMode(sounds[i], out var mode2), "GetMode");
					if ((mode2 & 0x80) != 0)
					{
						throw new InvalidOperationException("Stream source cannot be safely shared; route " + i);
					}
				}
			}
		}

		public void WarmUp(bool[] used)
		{
			for (int i = 0; i < used.Length; i++)
			{
				if (used[i])
				{
					Check(FMOD5_System_PlaySound(system, sounds[i], groups[i], 1, out var channel), "WarmUp.PlaySound(paused)");
					try
					{
						Check(FMOD5_Channel_SetMode(channel, 1u), "WarmUp.SetMode");
						Check(FMOD5_Channel_SetPitch(channel, 1f), "WarmUp.SetPitch");
						Check(FMOD5_Channel_SetVolume(channel, 0f), "WarmUp.SetVolume");
						Check(FMOD5_Sound_GetLength(sounds[i], out var _, 1u), "WarmUp.GetLength");
						Check(FMOD5_Channel_SetPosition(channel, 0u, 1u), "WarmUp.SetPosition");
						Check(FMOD5_Channel_GetDSPClock(channel, out var _, out var _), "WarmUp.GetDSPClock");
						Check(FMOD5_Channel_SetDelay(channel, 0uL, 0uL, 1), "WarmUp.SetDelay");
						Check(FMOD5_Channel_SetPaused(channel, 1), "WarmUp.KeepPaused");
					}
					finally
					{
						Check(FMOD5_Channel_Stop(channel), "WarmUp.Stop");
					}
				}
			}
		}

		public void Submit(AudioCue cue, double now, double offset, double rate)
		{
			int route = (int)cue.Route;
			double num = cue.TimeMs + offset;
			double num2 = cue.EndMs + offset;
			if (cue.Loop && num2 <= now)
			{
				return;
			}
			Check(FMOD5_System_PlaySound(system, sounds[route], groups[route], 1, out var channel), "PlaySound(paused)");
			try
			{
				Check(FMOD5_Channel_SetMode(channel, (!cue.Loop) ? 1u : 2u), "SetMode");
				Check(FMOD5_Channel_SetPitch(channel, (float)rate), "SetPitch");
				Check(FMOD5_Channel_SetVolume(channel, 1f), "SetVolume");
				if (cue.Loop && now > num)
				{
					Check(FMOD5_Sound_GetLength(sounds[route], out var length, 1u), "GetLength");
					if (length != 0)
					{
						Check(FMOD5_Channel_SetPosition(channel, (uint)((now - num) % (double)length), 1u), "SetPosition");
					}
				}
				Check(FMOD5_Channel_GetDSPClock(channel, out var _, out var parent), "GetDSPClock");
				ulong num3 = DspTiming.Target(parent, num - now, sampleRate, rate);
				ulong num4 = (cue.Loop ? DspTiming.Target(parent, num2 - now, sampleRate, rate) : 0);
				if (cue.Loop && num4 <= num3)
				{
					num4 = num3 + 1;
				}
				Check(FMOD5_Channel_SetDelay(channel, num3, num4, 1), "SetDelay");
				Check(FMOD5_Channel_SetPaused(channel, 0), "Unpause");
				owned.Add(channel);
			}
			catch
			{
				FMOD5_Channel_Stop(channel);
				throw;
			}
		}

		public void StopAll()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			Enumerator<System.IntPtr> enumerator = owned.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					FMOD5_Channel_Stop(enumerator.Current);
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			owned.Clear();
		}

		public void Dispose()
		{
			StopAll();
		}
	}
	public sealed class HoldHeadScope
	{
		private readonly HashSet<long> ids = new HashSet<long>();

		private System.IntPtr queue;

		public int Count => ids.Count;

		public void Register(long id)
		{
			ids.Add(id);
		}

		public System.IntPtr Enter(bool active, long id, System.IntPtr target)
		{
			System.IntPtr result = queue;
			queue = ((active && ids.Contains(id)) ? target : System.IntPtr.Zero);
			return result;
		}

		public void Restore(System.IntPtr previous)
		{
			queue = previous;
		}

		public bool Owns(System.IntPtr target, byte eventType, int keyIndex)
		{
			if (queue != System.IntPtr.Zero && queue == target && eventType == 1 && keyIndex >= 0)
			{
				return keyIndex < 6;
			}
			return false;
		}
	}
	public static class NoteAudioPolicy
	{
		public static bool IsSide(int lane)
		{
			if (lane != 2)
			{
				return lane == 3;
			}
			return true;
		}

		public static bool OwnsKeyboardFeedback(int type, int lane)
		{
			if (lane >= 1 && lane <= 3)
			{
				if (type != 1)
				{
					return type == 2;
				}
				return true;
			}
			return false;
		}

		public static AudioRoute FeedbackRoute(int firstKey, int lastKey)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			if (firstKey < 0 || lastKey < firstKey || lastKey >= 6)
			{
				throw new InvalidOperationException("Invalid keyboard key range");
			}
			if (firstKey != 0 && firstKey != 5)
			{
				return AudioRoute.KeyboardHoldHit;
			}
			return AudioRoute.SideKeyHit;
		}
	}
	public static class NoteJudgements
	{
		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2)]
		private static extern System.IntPtr il2cpp_object_get_class(System.IntPtr obj);

		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2)]
		private static extern System.IntPtr il2cpp_class_get_element_class(System.IntPtr klass);

		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2)]
		private static extern System.IntPtr il2cpp_class_get_field_from_name(System.IntPtr klass, string name);

		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2)]
		private static extern int il2cpp_field_get_offset(System.IntPtr field);

		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2)]
		private static extern int il2cpp_class_value_size(System.IntPtr klass, out uint alignment);

		private static int Offset(System.IntPtr klass, string field)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			System.IntPtr intPtr = il2cpp_class_get_field_from_name(klass, field);
			if (intPtr == System.IntPtr.Zero)
			{
				throw new InvalidOperationException("Missing dictionary field " + field);
			}
			return il2cpp_field_get_offset(intPtr);
		}

		public static Dictionary<long, List<AudioCue>> Snapshot(System.IntPtr dictionary, int total)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			if (dictionary == System.IntPtr.Zero)
			{
				throw new InvalidOperationException("Missing per-note judgement dictionary");
			}
			System.IntPtr klass = il2cpp_object_get_class(dictionary);
			int num = Marshal.ReadInt32(dictionary, Offset(klass, "_count"));
			if (num < 0 || num > 200000 || Marshal.ReadInt32(dictionary, Offset(klass, "_freeCount")) != 0)
			{
				throw new InvalidOperationException("Unexpected chart dictionary state");
			}
			System.IntPtr intPtr = Marshal.ReadIntPtr(dictionary, Offset(klass, "_entries"));
			if (intPtr == System.IntPtr.Zero || Marshal.ReadInt64(intPtr, 24) < num)
			{
				throw new InvalidOperationException("Invalid per-note entries");
			}
			System.IntPtr klass2 = il2cpp_class_get_element_class(il2cpp_object_get_class(intPtr));
			uint alignment;
			int size = il2cpp_class_value_size(klass2, out alignment);
			int num2 = Offset(klass2, "hashCode");
			int key = Offset(klass2, "key") - num2;
			int value = Offset(klass2, "value") - num2;
			return ReadEntries(intPtr, num, size, key, value, total);
		}

		public static Dictionary<long, List<AudioCue>> ReadEntries(System.IntPtr entries, int count, int size, int key, int value, int total)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			if (size != 32 || key != 8 || value != 16 || count < 0 || count > 200000 || entries == System.IntPtr.Zero || Marshal.ReadInt64(entries, 24) < count)
			{
				throw new InvalidOperationException("Unsupported per-note entry layout");
			}
			Dictionary<long, List<AudioCue>> val = new Dictionary<long, List<AudioCue>>();
			HashSet<long> val2 = new HashSet<long>();
			for (int i = 0; i < count; i++)
			{
				System.IntPtr intPtr = System.IntPtr.Add(entries, checked(32 + i * size));
				long num = Marshal.ReadInt64(intPtr, key);
				System.IntPtr intPtr2 = Marshal.ReadIntPtr(intPtr, value);
				int num2 = Marshal.ReadInt32(intPtr, value + 8) & 0x7FFFFFFF;
				int num3 = Marshal.ReadInt32(intPtr, value + 12) & 0x7FFFFFFF;
				if (num3 == 0)
				{
					val.Add(num, new List<AudioCue>());
					continue;
				}
				if (intPtr2 == System.IntPtr.Zero || num2 > Marshal.ReadInt64(intPtr2, 24) || num3 > total || (long)num2 + (long)num3 > Marshal.ReadInt64(intPtr2, 24))
				{
					throw new InvalidOperationException("Invalid per-note judgement memory");
				}
				List<AudioCue> val3 = new List<AudioCue>();
				for (int j = 0; j < num3; j++)
				{
					System.IntPtr intPtr3 = System.IntPtr.Add(intPtr2, checked(32 + (num2 + j) * 40));
					long num4 = Marshal.ReadInt64(intPtr3);
					int num5 = Marshal.ReadInt32(intPtr3, 24);
					if (num4 >= 0 && num4 < total && val2.Add(num4))
					{
						switch (num5)
						{
						case 1:
						{
							double num6 = BitConverter.Int64BitsToDouble(Marshal.ReadInt64(intPtr3, 16));
							if (double.IsNaN(num6) || double.IsInfinity(num6) || num6 < -60000.0 || num6 > 86400000.0)
							{
								throw new InvalidOperationException("Invalid per-note judgement time");
							}
							val3.Add(new AudioCue
							{
								Id = num4,
								TimeMs = num6,
								Route = AudioRoute.KeyboardHoldHit
							});
							continue;
						}
						case 0:
						case 2:
						case 3:
						case 4:
						case 5:
							continue;
						}
					}
					throw new InvalidOperationException("Invalid per-note judgement identity");
				}
				val.Add(num, val3);
			}
			return val;
		}
	}
	[BepInPlugin("local.infalsus.chartaudio", "In Falsus Chart Audio", "0.4.1")]
	public sealed class Plugin : BasePlugin
	{
		[UnmanagedFunctionPointer(/*Could not decode attribute arguments.*/)]
		private delegate void VoidMethod(System.IntPtr self, System.IntPtr mi);

		[UnmanagedFunctionPointer(/*Could not decode attribute arguments.*/)]
		private delegate System.IntPtr LoadSourceMethod(System.IntPtr self, byte kind, int group, System.IntPtr callback, [MarshalAs((UnmanagedType)3)] bool range, double from, double to, [MarshalAs((UnmanagedType)3)] bool flag, int extra, System.IntPtr mi);

		[UnmanagedFunctionPointer(/*Could not decode attribute arguments.*/)]
		private delegate double DoubleMethod(System.IntPtr self, System.IntPtr mi);

		[UnmanagedFunctionPointer(/*Could not decode attribute arguments.*/)]
		private delegate System.IntPtr GroupMethod(System.IntPtr groups, int group, System.IntPtr mi);

		[UnmanagedFunctionPointer(/*Could not decode attribute arguments.*/)]
		private delegate int NoteOffsetMethod(System.IntPtr note, System.IntPtr mi);

		[UnmanagedFunctionPointer(/*Could not decode attribute arguments.*/)]
		[return: MarshalAs((UnmanagedType)3)]
		private delegate bool BoolMethod(System.IntPtr self, System.IntPtr mi);

		[UnmanagedFunctionPointer(/*Could not decode attribute arguments.*/)]
		private delegate void HitMethod(System.IntPtr self, byte kind, double offset, System.IntPtr mi);

		[UnmanagedFunctionPointer(/*Could not decode attribute arguments.*/)]
		private delegate void DispatchMethod(System.IntPtr self, System.IntPtr results, System.IntPtr evt, System.IntPtr mi);

		[UnmanagedFunctionPointer(/*Could not decode attribute arguments.*/)]
		private delegate void ProcessNoteMethod(System.IntPtr self, System.IntPtr records, System.IntPtr note, System.IntPtr queue, System.IntPtr mi);

		[UnmanagedFunctionPointer(/*Could not decode attribute arguments.*/)]
		private delegate void EnqueueMethod(System.IntPtr self, System.IntPtr evt, System.IntPtr mi);

		private const string SupportedHash = "375F3A8DB0E5DFF69B78A74E0518B98D83214EB7E5BCDC428386615DDBCBC04F";

		private readonly List<INativeDetour> hooks = new List<INativeDetour>();

		private readonly List<System.Delegate> roots = new List<System.Delegate>();

		private ConfigEntry<bool> enabled;

		private ConfigEntry<bool> removeMuteLimit;

		private ConfigEntry<bool> debug;

		private ConfigEntry<double> soundOffset;

		private ConfigEntry<double> scheduleAhead;

		private System.IntPtr module;

		private System.IntPtr logicalField;

		private System.IntPtr controllerField;

		private System.IntPtr systemField;

		private System.IntPtr groupsField;

		private System.IntPtr soundManagerField;

		private System.IntPtr scene;

		private System.IntPtr chartIdentity;

		private RuntimePatch mutePatch;

		private AudioTimeline timeline;

		private FmodScheduler scheduler;

		private SourceLeases sources;

		private AudioTimeline preparedTimeline;

		private HoldHeadScope preparedHeads;

		private System.IntPtr preparedChart;

		private bool[] preparedUsed;

		private string preparedDescription;

		private long nextPrepareAttempt;

		private HoldHeadScope holdHeads = new HoldHeadScope();

		private bool ready;

		private bool faulted;

		private bool wasPlaying;

		private double appliedOffset;

		private double lastRate = 1.0;

		private long lastStatus;

		private int scheduled;

		private int suppressed;

		private int feedbackSuppressed;

		private readonly int[] scheduledByRoute = new int[System.Enum.GetValues(typeof(AudioRoute)).Length];

		private VoidMethod originalUpdate;

		private VoidMethod originalStart;

		private VoidMethod originalDisable;

		private VoidMethod originalDestroy;

		private VoidMethod originalAudioInit;

		private VoidMethod loopStart;

		private VoidMethod loopStop;

		private VoidMethod disposeSource;

		private LoadSourceMethod loadSource;

		private DispatchMethod originalDispatch;

		private HitMethod originalHit;

		private ProcessNoteMethod originalProcessNote;

		private EnqueueMethod originalEnqueue;

		private DoubleMethod musicPosition;

		private GroupMethod groupSelector;

		private NoteOffsetMethod noteOffset;

		private BoolMethod mouseHoldActive;

		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2)]
		private static extern System.IntPtr il2cpp_domain_get();

		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2)]
		private static extern System.IntPtr il2cpp_domain_assembly_open(System.IntPtr domain, string name);

		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2)]
		private static extern System.IntPtr il2cpp_assembly_get_image(System.IntPtr assembly);

		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2)]
		private static extern System.IntPtr il2cpp_class_from_name(System.IntPtr image, string ns, string name);

		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2)]
		private static extern System.IntPtr il2cpp_class_get_field_from_name(System.IntPtr klass, string name);

		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2)]
		private static extern void il2cpp_field_static_get_value(System.IntPtr field, out System.IntPtr value);

		[DllImport("GameAssembly", CallingConvention = (CallingConvention)2, EntryPoint = "il2cpp_field_static_get_value")]
		private static extern void ReadStaticValue(System.IntPtr field, System.IntPtr buffer);

		private static System.IntPtr Ptr(System.IntPtr p, int off)
		{
			return Marshal.ReadIntPtr(p, off);
		}

		private static int I32(System.IntPtr p, int off)
		{
			return Marshal.ReadInt32(p, off);
		}

		private static double F64(System.IntPtr p, int off)
		{
			return BitConverter.Int64BitsToDouble(Marshal.ReadInt64(p, off));
		}

		private static float F32(System.IntPtr p, int off)
		{
			return BitConverter.Int32BitsToSingle(I32(p, off));
		}

		private static bool Finite(double t)
		{
			if (!double.IsNaN(t))
			{
				return !double.IsInfinity(t);
			}
			return false;
		}

		private System.IntPtr At(int rva)
		{
			return System.IntPtr.Add(module, rva);
		}

		private T Function<T>(int rva) where T : System.Delegate
		{
			return Marshal.GetDelegateForFunctionPointer<T>(At(rva));
		}

		private void Hook<T>(int rva, T callback, out T original) where T : System.Delegate
		{
			RuntimeHelpers.PrepareDelegate((System.Delegate)callback);
			roots.Add((System.Delegate)callback);
			hooks.Add(INativeDetour.CreateAndApply<T>(At(rva), callback, ref original));
		}

		private void WarmManagedPaths()
		{
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			Stopwatch val = Stopwatch.StartNew();
			int num = 0;
			int num2 = 0;
			System.Type[] types = typeof(Plugin).Assembly.GetTypes();
			for (int i = 0; i < types.Length; i++)
			{
				MethodInfo[] methods = types[i].GetMethods((BindingFlags)62);
				foreach (MethodInfo val2 in methods)
				{
					if (!((MethodBase)val2).ContainsGenericParameters && !((MethodBase)val2).IsAbstract && ((MethodBase)val2).GetMethodBody() != null)
					{
						try
						{
							RuntimeHelpers.PrepareMethod(((MethodBase)val2).MethodHandle);
							num++;
						}
						catch (NotSupportedException)
						{
							num2++;
						}
						catch (ArgumentException)
						{
							num2++;
						}
					}
				}
			}
			ManualLogSource log = ((BasePlugin)this).Log;
			string[] obj = new string[6]
			{
				"Managed audio paths prepared: methods=",
				num.ToString(),
				" skipped=",
				num2.ToString(),
				" elapsedMs=",
				null
			};
			TimeSpan elapsed = val.Elapsed;
			obj[5] = ((TimeSpan)(ref elapsed)).TotalMilliseconds.ToString();
			log.LogInfo((object)string.Concat(obj));
		}

		private System.IntPtr Field(string asm, string ns, string type, string name)
		{
			//IL_0023: 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_0079: Unknown result type (might be due to invalid IL or missing references)
			System.IntPtr intPtr = il2cpp_domain_assembly_open(il2cpp_domain_get(), asm);
			if (intPtr == System.IntPtr.Zero)
			{
				throw new InvalidOperationException("Missing assembly " + asm);
			}
			System.IntPtr intPtr2 = il2cpp_class_from_name(il2cpp_assembly_get_image(intPtr), ns, type);
			if (intPtr2 == System.IntPtr.Zero)
			{
				throw new InvalidOperationException("Missing type " + type);
			}
			System.IntPtr intPtr3 = il2cpp_class_get_field_from_name(intPtr2, name);
			if (intPtr3 == System.IntPtr.Zero)
			{
				throw new InvalidOperationException("Missing field " + type + "." + name);
			}
			return intPtr3;
		}

		private System.IntPtr StaticObject(System.IntPtr field)
		{
			il2cpp_field_static_get_value(field, out var value);
			return value;
		}

		public override void Load()
		{
			//IL_043f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0444: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Expected O, but got Unknown
			//IL_0185: Unknown result type (might be due to invalid IL or missing references)
			enabled = ((BasePlugin)this).Config.Bind<bool>("General", "Enabled", true, "Chart-driven audio. Restart after changing configuration.");
			removeMuteLimit = ((BasePlugin)this).Config.Bind<bool>("General", "RemoveOffsetMuteLimit", true, "Remove abs(offset)>100ms automatic hitsound mute; keep user offsets and volume settings.");
			soundOffset = ((BasePlugin)this).Config.Bind<double>("Audio", "SoundOffsetMs", 0.0, "Extra offset relative to music: positive delays, negative advances. Applied on next scene/retry.");
			scheduleAhead = ((BasePlugin)this).Config.Bind<double>("Audio", "ScheduleAheadMs", 250.0, "Submit known chart sounds ahead of time to FMOD DSP scheduling (50..1000ms). Pending sounds are cancelled on pause/seek/exit.");
			debug = ((BasePlugin)this).Config.Bind<bool>("Debug", "DebugLogging", true, "Record the first 16 scheduled sounds and periodic counters. Slow submissions and preparation timings are always logged.");
			SHA256 val = SHA256.Create();
			try
			{
				if (BitConverter.ToString(((HashAlgorithm)val).ComputeHash(File.ReadAllBytes(Path.Combine(Paths.GameRootPath, "GameAssembly.dll")))).Replace("-", "") != "375F3A8DB0E5DFF69B78A74E0518B98D83214EB7E5BCDC428386615DDBCBC04F")
				{
					((BasePlugin)this).Log.LogError((object)"Unsupported build; no patches applied.");
					return;
				}
			}
			finally
			{
				((System.IDisposable)val)?.Dispose();
			}
			foreach (ProcessModule item in (ReadOnlyCollectionBase)Process.GetCurrentProcess().Modules)
			{
				ProcessModule val2 = item;
				if (val2.ModuleName.Equals("GameAssembly.dll", (StringComparison)5))
				{
					module = val2.BaseAddress;
				}
			}
			if (module == System.IntPtr.Zero)
			{
				throw new InvalidOperationException("Missing GameAssembly");
			}
			try
			{
				logicalField = Field("Game", "ifapp.Game", "LogicalNotePlayer", "_Qe");
				controllerField = Field("Game", "ifapp.Game.Scenes", "GameScene", "_DN");
				systemField = Field("Game.Audio", "_J", "_IF", "_ZSA");
				groupsField = Field("Game.Audio", "_J", "_IF", "_DtA");
				soundManagerField = Field("Game.Audio", "_J", "_zf", "_psA");
				loadSource = Function<LoadSourceMethod>(4498624);
				disposeSource = Function<VoidMethod>(4439936);
				musicPosition = Function<DoubleMethod>(4444576);
				groupSelector = Function<GroupMethod>(4489632);
				noteOffset = Function<NoteOffsetMethod>(5397200);
				loopStart = Function<VoidMethod>(4482080);
				loopStop = Function<VoidMethod>(4481616);
				mouseHoldActive = Function<BoolMethod>(6282944);
				if (removeMuteLimit.Value)
				{
					mutePatch = new RuntimePatch(At(7372871), new byte[2] { 127, 11 }, new byte[2] { 144, 144 });
					((BasePlugin)this).Log.LogInfo((object)"Offset mute limit removed; user offset/volume settings preserved.");
				}
				Hook<VoidMethod>(7354544, Update, out originalUpdate);
				Hook<VoidMethod>(7349536, Start, out originalStart);
				Hook<VoidMethod>(7349328, Disable, out originalDisable);
				Hook<VoidMethod>(7348624, Destroy, out originalDestroy);
				Hook<VoidMethod>(7371024, AudioInit, out originalAudioInit);
				Hook<DispatchMethod>(7356464, Dispatch, out originalDispatch);
				Hook<HitMethod>(7368224, Hit, out originalHit);
				Hook<ProcessNoteMethod>(6258240, ProcessNote, out originalProcessNote);
				Hook<EnqueueMethod>(13700784, Enqueue, out originalEnqueue);
				WarmManagedPaths();
				((BasePlugin)this).Log.LogInfo((object)"Plugin initialized; chart takeover remains inactive until DSP queue ready is logged.");
			}
			catch (System.Exception ex)
			{
				((BasePlugin)this).Log.LogError((object)ex);
				Enumerator<INativeDetour> enumerator2 = hooks.GetEnumerator();
				try
				{
					while (enumerator2.MoveNext())
					{
						((System.IDisposable)enumerator2.Current).Dispose();
					}
				}
				finally
				{
					((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
				}
				hooks.Clear();
				mutePatch?.Dispose();
			}
		}

		private void ClearAudio()
		{
			scheduler?.Dispose();
			scheduler = null;
			sources?.Dispose();
			sources = null;
		}

		private void Reset(System.IntPtr self)
		{
			ClearAudio();
			ready = false;
			timeline = null;
			preparedTimeline = null;
			preparedChart = System.IntPtr.Zero;
			preparedUsed = null;
			preparedHeads = null;
			nextPrepareAttempt = 0L;
			holdHeads = new HoldHeadScope();
			scene = self;
			chartIdentity = System.IntPtr.Zero;
			faulted = false;
			wasPlaying = false;
			scheduled = 0;
			suppressed = 0;
			feedbackSuppressed = 0;
			System.Array.Clear((System.Array)scheduledByRoute, 0, scheduledByRoute.Length);
		}

		private void SetOriginalLoop(int offset, bool active)
		{
			System.IntPtr intPtr = Ptr(scene, offset);
			if (!(intPtr == System.IntPtr.Zero) && !(Ptr(intPtr, 24) == System.IntPtr.Zero))
			{
				if (active)
				{
					loopStart(intPtr, System.IntPtr.Zero);
				}
				else
				{
					loopStop(intPtr, System.IntPtr.Zero);
				}
			}
		}

		private void RestoreOriginalLoops()
		{
			if (!(scene == System.IntPtr.Zero))
			{
				System.IntPtr intPtr = StaticObject(controllerField);
				if (!(intPtr == System.IntPtr.Zero))
				{
					System.IntPtr intPtr2 = Ptr(intPtr, 160);
					System.IntPtr intPtr3 = Ptr(intPtr, 168);
					SetOriginalLoop(368, intPtr2 != System.IntPtr.Zero && I32(intPtr2, 32) > 0);
					SetOriginalLoop(376, intPtr3 != System.IntPtr.Zero && I32(intPtr3, 32) > 0);
					SetOriginalLoop(384, mouseHoldActive(System.IntPtr.Add(intPtr, 24), System.IntPtr.Zero));
				}
			}
		}

		private void Fail(System.Exception e)
		{
			ClearAudio();
			if (ready)
			{
				try
				{
					RestoreOriginalLoops();
				}
				catch (System.Exception ex)
				{
					((BasePlugin)this).Log.LogError((object)("Original loop restore: " + (object)ex));
				}
			}
			ready = false;
			timeline = null;
			if (!faulted)
			{
				((BasePlugin)this).Log.LogError((object)("Chart audio disabled for this scene; original audio restored. " + (object)e));
			}
			faulted = true;
		}

		private static System.IntPtr MemoryData(System.IntPtr owner, int off, int size, out int count)
		{
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			System.IntPtr intPtr = Ptr(owner, off);
			int num = I32(owner, off + 8) & 0x7FFFFFFF;
			count = I32(owner, off + 12) & 0x7FFFFFFF;
			if (intPtr == System.IntPtr.Zero)
			{
				if (count == 0)
				{
					return System.IntPtr.Zero;
				}
				throw new InvalidOperationException("Missing chart array");
			}
			long num2 = Marshal.ReadInt64(intPtr, 24);
			if (num2 < 0 || num2 > 2000000 || num > num2 || count > num2 - num)
			{
				throw new InvalidOperationException("Invalid chart memory range");
			}
			return System.IntPtr.Add(intPtr, checked(32 + num * size));
		}

		private static System.IntPtr SoundOf(System.IntPtr instance)
		{
			if (!(instance == System.IntPtr.Zero))
			{
				return Ptr(instance, 32);
			}
			return System.IntPtr.Zero;
		}

		private static System.IntPtr LoopSound(System.IntPtr self, int offset)
		{
			System.IntPtr intPtr = Ptr(self, offset);
			if (!(intPtr == System.IntPtr.Zero))
			{
				return SoundOf(Ptr(intPtr, 24));
			}
			return System.IntPtr.Zero;
		}

		private System.IntPtr AcquireSource(int route)
		{
			System.IntPtr intPtr = StaticObject(soundManagerField);
			System.IntPtr intPtr2 = Ptr(scene, 448);
			if (intPtr == System.IntPtr.Zero || intPtr2 == System.IntPtr.Zero)
			{
				return System.IntPtr.Zero;
			}
			System.IntPtr intPtr3 = Ptr(intPtr2, 64);
			if (intPtr3 == System.IntPtr.Zero)
			{
				return System.IntPtr.Zero;
			}
			byte kind = route switch
			{
				4 => Marshal.ReadByte(scene, 408), 
				3 => Marshal.ReadByte(scene, 424), 
				2 => 57, 
				1 => 63, 
				0 => Marshal.ReadByte(scene, 440), 
				_ => Marshal.ReadByte(scene, 392), 
			};
			System.IntPtr result = loadSource(intPtr, kind, (route >= 4) ? 1 : ((route == 1) ? 2 : 3), intPtr3, range: false, -1.0, -1.0, flag: false, 0, System.IntPtr.Zero);
			ManualLogSource log = ((BasePlugin)this).Log;
			string[] obj = new string[6] { "Retained paused source: route=", null, null, null, null, null };
			AudioRoute audioRoute = (AudioRoute)route;
			obj[1] = ((object)audioRoute/*cast due to .constrained prefix*/).ToString();
			obj[2] = " kind=";
			obj[3] = kind.ToString();
			obj[4] = " wrapper=";
			obj[5] = result.ToString("X");
			log.LogInfo((object)string.Concat(obj));
			return result;
		}

		private bool BuildTimeline()
		{
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_09d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bb7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bbc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bdc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0be1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0505: Unknown result type (might be due to invalid IL or missing references)
			//IL_050a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0342: Unknown result type (might be due to invalid IL or missing references)
			//IL_054a: Unknown result type (might be due to invalid IL or missing references)
			//IL_054f: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0392: Unknown result type (might be due to invalid IL or missing references)
			//IL_0497: Unknown result type (might be due to invalid IL or missing references)
			//IL_064f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0654: Unknown result type (might be due to invalid IL or missing references)
			//IL_07e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_07ee: Unknown result type (might be due to invalid IL or missing references)
			long timestamp = Stopwatch.GetTimestamp();
			if (timestamp < nextPrepareAttempt)
			{
				return false;
			}
			nextPrepareAttempt = timestamp + Stopwatch.Frequency / 10;
			Stopwatch val = Stopwatch.StartNew();
			System.IntPtr intPtr = StaticObject(logicalField);
			if (intPtr == System.IntPtr.Zero)
			{
				return false;
			}
			System.IntPtr intPtr2 = Ptr(intPtr, 64);
			if (intPtr2 == System.IntPtr.Zero)
			{
				return false;
			}
			System.IntPtr[] array = new System.IntPtr[6]
			{
				SoundOf(Ptr(scene, 448)),
				LoopSound(scene, 368),
				LoopSound(scene, 384),
				SoundOf(Ptr(scene, 432)),
				SoundOf(Ptr(scene, 416)),
				SoundOf(Ptr(scene, 400))
			};
			TimeSpan elapsed;
			if (preparedChart != intPtr2)
			{
				if (preparedChart != System.IntPtr.Zero)
				{
					ClearAudio();
				}
				int num = I32(intPtr2, 216);
				if (num == 0)
				{
					return false;
				}
				if (num < 0 || num > 200000)
				{
					throw new InvalidOperationException("Invalid judgement count");
				}
				int count;
				System.IntPtr intPtr3 = MemoryData(intPtr2, 176, 40, out count);
				if (num > count)
				{
					throw new InvalidOperationException("Incomplete judgement array");
				}
				AudioTimeline audioTimeline = new AudioTimeline();
				HashSet<long> val2 = new HashSet<long>();
				int[] array2 = new int[6];
				int merged = 0;
				Dictionary<long, List<AudioCue>> val3 = NoteJudgements.Snapshot(Ptr(intPtr2, 56), num);
				HoldHeadScope holdHeadScope = new HoldHeadScope();
				for (int i = 0; i < num; i++)
				{
					System.IntPtr intPtr4 = System.IntPtr.Add(intPtr3, i * 40);
					long num2 = Marshal.ReadInt64(intPtr4, 0);
					int num3 = I32(intPtr4, 24);
					double num4 = F64(intPtr4, 16);
					if (num2 < 0 || num2 >= num || num3 < 0 || num3 >= 6 || !val2.Add(num2) || !Finite(num4) || num4 < -60000.0 || num4 > 86400000.0)
					{
						throw new InvalidOperationException("Unexpected judgement record");
					}
					array2[num3]++;
					if (num3 == 1 || num3 == 2 || num3 == 5)
					{
						audioTimeline.Events.Add(new AudioCue
						{
							Id = num2,
							TimeMs = num4,
							Route = AudioRoute.Attack
						});
					}
				}
				List<AudioCue> val4 = new List<AudioCue>();
				System.IntPtr intPtr5 = Ptr(intPtr, 48);
				int num5 = 0;
				int num6 = 0;
				int num7 = 0;
				int num8 = 0;
				int[] array3 = new int[5];
				int[] array4 = new int[30];
				if (intPtr5 == System.IntPtr.Zero)
				{
					return false;
				}
				System.IntPtr intPtr6 = Ptr(intPtr5, 16);
				int num9 = I32(intPtr5, 24);
				if (intPtr6 == System.IntPtr.Zero || num9 < 0 || num9 > 200000 || num9 > Marshal.ReadInt64(intPtr6, 24))
				{
					throw new InvalidOperationException("Invalid note list");
				}
				List<AudioCue> val5 = default(List<AudioCue>);
				Enumerator<AudioCue> enumerator;
				for (int j = 0; j < num9; j++)
				{
					System.IntPtr intPtr7 = System.IntPtr.Add(intPtr6, checked(32 + j * 128));
					int num10 = I32(intPtr7, 20);
					int num11 = I32(intPtr7, 16);
					if (num11 < 0 || num11 >= 5 || num10 < 0 || num10 >= 6)
					{
						throw new InvalidOperationException("Unrecognised chart note category/type; refusing partial takeover");
					}
					array3[num11]++;
					array4[num11 * 6 + num10]++;
					if (NoteAudioPolicy.OwnsKeyboardFeedback(num10, num11))
					{
						long num12 = Marshal.ReadInt64(intPtr7);
						if (!val3.TryGetValue(num12, ref val5))
						{
							throw new InvalidOperationException("Missing keyboard note judgements");
						}
						holdHeadScope.Register(num12);
						AudioRoute audioRoute = NoteAudioPolicy.FeedbackRoute(I32(intPtr7, 120), I32(intPtr7, 124));
						enumerator = val5.GetEnumerator();
						try
						{
							while (enumerator.MoveNext())
							{
								AudioCue current = enumerator.Current;
								current.Route = audioRoute;
								audioTimeline.Events.Add(current);
								if (audioRoute == AudioRoute.SideKeyHit)
								{
									num8++;
								}
							}
						}
						finally
						{
							((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
						}
						if (NoteAudioPolicy.IsSide(num11))
						{
							num5++;
						}
						if (num10 == 2)
						{
							num6++;
							if (NoteAudioPolicy.IsSide(num11))
							{
								num7++;
							}
						}
					}
					if (num10 == 2 || num10 == 5)
					{
						double num13 = noteOffset(intPtr7, System.IntPtr.Zero);
						double num14 = (double)I32(intPtr7, 24) + num13;
						double num15 = (double)I32(intPtr7, 28) + num13;
						if (num14 < -60000.0 || num15 > 86400000.0 || num15 < num14)
						{
							throw new InvalidOperationException("Invalid hold interval");
						}
						if (num15 > num14)
						{
							val4.Add(new AudioCue
							{
								Id = -1 - (long)j * 2L,
								TimeMs = num14,
								EndMs = num15,
								Loop = true,
								Route = ((num10 == 2) ? AudioRoute.Hold : AudioRoute.MouseHold)
							});
						}
					}
				}
				enumerator = AudioTimeline.MergeLoops((System.Collections.Generic.IEnumerable<AudioCue>)val4, AudioRoute.Hold).GetEnumerator();
				try
				{
					while (enumerator.MoveNext())
					{
						AudioCue current2 = enumerator.Current;
						audioTimeline.Events.Add(current2);
					}
				}
				finally
				{
					((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
				}
				List<AudioCue> val6 = AudioTimeline.MergeLoops((System.Collections.Generic.IEnumerable<AudioCue>)val4, AudioRoute.MouseHold);
				enumerator = val6.GetEnumerator();
				try
				{
					while (enumerator.MoveNext())
					{
						AudioCue current3 = enumerator.Current;
						audioTimeline.Events.Add(current3);
						audioTimeline.Events.Add(new AudioCue
						{
							Id = current3.Id - 1,
							TimeMs = current3.TimeMs,
							Route = AudioRoute.MouseTransition
						});
						audioTimeline.Events.Add(new AudioCue
						{
							Id = current3.Id - 2,
							TimeMs = current3.EndMs,
							Route = AudioRoute.MouseTransition
						});
					}
				}
				finally
				{
					((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
				}
				AudioTimeline audioTimeline2 = new AudioTimeline();
				audioTimeline2.Events.AddRange((System.Collections.Generic.IEnumerable<AudioCue>)AudioTimeline.CoalesceAttacks(audioTimeline.Events, 10.0, out merged));
				if (audioTimeline2.Events.Count == 0)
				{
					return false;
				}
				bool[] array5 = new bool[array.Length];
				enumerator = audioTimeline2.Events.GetEnumerator();
				try
				{
					while (enumerator.MoveNext())
					{
						array5[(int)enumerator.Current.Route] = true;
					}
				}
				finally
				{
					((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
				}
				preparedTimeline = audioTimeline2;
				preparedHeads = holdHeadScope;
				preparedUsed = array5;
				preparedChart = intPtr2;
				preparedDescription = string.Concat(new string[24]
				{
					"events=",
					audioTimeline2.Events.Count.ToString(),
					" judgementKinds=",
					string.Join<int>(",", (System.Collections.Generic.IEnumerable<int>)array2),
					" mouseAttacks=",
					array2[5].ToString(),
					" holdIntervals=",
					val4.Count.ToString(),
					" mouseLoopUnions=",
					val6.Count.ToString(),
					" mergedAttacks=",
					merged.ToString(),
					" keyboardHolds=",
					num6.ToString(),
					" sideNotes=",
					num5.ToString(),
					" sideHolds=",
					num7.ToString(),
					" sideHeads=",
					num8.ToString(),
					" laneCounts=",
					string.Join<int>(",", (System.Collections.Generic.IEnumerable<int>)array3),
					" laneTypeCounts=",
					string.Join<int>(",", (System.Collections.Generic.IEnumerable<int>)array4)
				});
				ManualLogSource log = ((BasePlugin)this).Log;
				string text = preparedDescription;
				elapsed = val.Elapsed;
				log.LogInfo((object)("Chart snapshot prepared: " + text + " elapsedMs=" + ((TimeSpan)(ref elapsed)).TotalMilliseconds));
			}
			if (sources == null)
			{
				sources = new SourceLeases(AcquireSource, SoundOf, [CompilerGenerated] (System.IntPtr p) =>
				{
					disposeSource(p, System.IntPtr.Zero);
				}, FmodScheduler.IsReady);
			}
			for (int num16 = 0; num16 < preparedUsed.Length; num16++)
			{
				if (!preparedUsed[num16])
				{
					continue;
				}
				array[num16] = sources.Resolve(num16, array[num16]);
				if (!FmodScheduler.Probe(array[num16], out var result, out var state))
				{
					long timestamp2 = Stopwatch.GetTimestamp();
					if (timestamp2 - lastStatus > Stopwatch.Frequency * 5)
					{
						lastStatus = timestamp2;
						ManualLogSource log2 = ((BasePlugin)this).Log;
						string[] obj = new string[9] { "Waiting for game sound source: ", null, null, null, null, null, null, null, null };
						AudioRoute audioRoute2 = (AudioRoute)num16;
						obj[1] = ((object)audioRoute2/*cast due to .constrained prefix*/).ToString();
						obj[2] = " sound=";
						obj[3] = array[num16].ToString("X");
						obj[4] = " result=";
						obj[5] = result.ToString();
						obj[6] = " openState=";
						obj[7] = state.ToString();
						obj[8] = "; original audio remains active.";
						log2.LogInfo((object)string.Concat(obj));
					}
					return false;
				}
			}
			if (!Finite(soundOffset.Value) || Math.Abs(soundOffset.Value) > 5000.0 || !Finite(scheduleAhead.Value) || scheduleAhead.Value < 50.0 || scheduleAhead.Value > 1000.0)
			{
				throw new InvalidOperationException("Invalid SoundOffsetMs/ScheduleAheadMs");
			}
			System.IntPtr intPtr8 = StaticObject(systemField);
			if (intPtr8 == System.IntPtr.Zero)
			{
				return false;
			}
			System.IntPtr intPtr9 = Marshal.AllocHGlobal(80);
			System.IntPtr[] array6 = new System.IntPtr[array.Length];
			try
			{
				ReadStaticValue(groupsField, intPtr9);
				array6[0] = groupSelector(intPtr9, 3, System.IntPtr.Zero);
				array6[1] = groupSelector(intPtr9, 2, System.IntPtr.Zero);
				array6[2] = array6[0];
				array6[3] = array6[0];
				array6[4] = groupSelector(intPtr9, 1, System.IntPtr.Zero);
				array6[5] = array6[4];
			}
			finally
			{
				Marshal.FreeHGlobal(intPtr9);
			}
			FmodScheduler fmodScheduler = new FmodScheduler(intPtr8, array, array6, preparedUsed);
			Stopwatch val7 = Stopwatch.StartNew();
			try
			{
				fmodScheduler.WarmUp(preparedUsed);
				SetOriginalLoop(368, active: false);
				SetOriginalLoop(376, active: false);
				SetOriginalLoop(384, active: false);
			}
			catch
			{
				fmodScheduler.Dispose();
				throw;
			}
			scheduler = fmodScheduler;
			timeline = preparedTimeline;
			holdHeads = preparedHeads;
			chartIdentity = intPtr2;
			appliedOffset = soundOffset.Value;
			ready = true;
			ManualLogSource log3 = ((BasePlugin)this).Log;
			string[] obj3 = new string[14]
			{
				"DSP queue ready: ",
				preparedDescription,
				" sampleRate=",
				fmodScheduler.SampleRate.ToString(),
				" aheadMs=",
				scheduleAhead.Value.ToString(),
				" offsetMs=",
				appliedOffset.ToString(),
				" gameOffsetMs=",
				I32(scene, 556).ToString(),
				" warmUpMs=",
				null,
				null,
				null
			};
			elapsed = val7.Elapsed;
			obj3[11] = ((TimeSpan)(ref elapsed)).TotalMilliseconds.ToString();
			obj3[12] = " attemptMs=";
			elapsed = val.Elapsed;
			obj3[13] = ((TimeSpan)(ref elapsed)).TotalMilliseconds.ToString();
			log3.LogInfo((object)string.Concat(obj3));
			return true;
		}

		private void Start(System.IntPtr self, System.IntPtr mi)
		{
			Reset(self);
			originalStart(self, mi);
		}

		private void AudioInit(System.IntPtr self, System.IntPtr mi)
		{
			Reset(self);
			originalAudioInit(self, mi);
		}

		private void Disable(System.IntPtr self, System.IntPtr mi)
		{
			if (scene == self)
			{
				Reset(System.IntPtr.Zero);
			}
			originalDisable(self, mi);
		}

		private void Destroy(System.IntPtr self, System.IntPtr mi)
		{
			if (scene == self)
			{
				Reset(System.IntPtr.Zero);
			}
			originalDestroy(self, mi);
		}

		private void Update(System.IntPtr self, System.IntPtr mi)
		{
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Expected O, but got Unknown
			try
			{
				if (scene != self)
				{
					Reset(self);
				}
				if (enabled.Value && !faulted && !ready)
				{
					BuildTimeline();
				}
			}
			catch (System.Exception e)
			{
				Fail(e);
			}
			originalUpdate(self, mi);
			if (!ready)
			{
				return;
			}
			try
			{
				if (!enabled.Value)
				{
					RestoreOriginalLoops();
					Reset(self);
					return;
				}
				System.IntPtr intPtr = StaticObject(logicalField);
				if (intPtr == System.IntPtr.Zero || Ptr(intPtr, 64) != chartIdentity)
				{
					Reset(self);
					return;
				}
				System.IntPtr audio = Ptr(self, 488);
				if (!(audio != System.IntPtr.Zero) || I32(audio, 48) != 2)
				{
					if (wasPlaying)
					{
						scheduler.StopAll();
						timeline.Reset(skipAtCurrentTime: true);
						wasPlaying = false;
					}
					return;
				}
				double num = musicPosition(audio, System.IntPtr.Zero) * 1000.0;
				double rate = F32(audio, 72);
				if (!Finite(num) || !Finite(rate) || rate <= 0.0 || rate > 10.0)
				{
					throw new InvalidOperationException("Invalid music clock/rate");
				}
				if (wasPlaying && Math.Abs(rate - lastRate) > 0.0001)
				{
					scheduler.StopAll();
					timeline.Reset(skipAtCurrentTime: true);
				}
				wasPlaying = true;
				lastRate = rate;
				timeline.Advance(num - appliedOffset, scheduleAhead.Value * rate, delegate(AudioCue cue, double _)
				{
					double num3 = musicPosition(audio, System.IntPtr.Zero) * 1000.0;
					long timestamp2 = Stopwatch.GetTimestamp();
					scheduler.Submit(cue, num3, appliedOffset, rate);
					scheduled++;
					scheduledByRoute[(int)cue.Route]++;
					double num4 = (double)(Stopwatch.GetTimestamp() - timestamp2) * 1000.0 / (double)Stopwatch.Frequency;
					if (num4 > 20.0)
					{
						((BasePlugin)this).Log.LogWarning((object)string.Concat(new string[6]
						{
							"Slow chart submit: route=",
							((object)cue.Route/*cast due to .constrained prefix*/).ToString(),
							" elapsedMs=",
							num4.ToString(),
							" musicMs=",
							num3.ToString()
						}));
					}
					if (debug.Value && scheduled <= 16)
					{
						((BasePlugin)this).Log.LogInfo((object)string.Concat(new string[14]
						{
							"SCHEDULE id=",
							cue.Id.ToString(),
							" route=",
							((object)cue.Route/*cast due to .constrained prefix*/).ToString(),
							" startMs=",
							cue.TimeMs.ToString(),
							" endMs=",
							(cue.Loop ? cue.EndMs : 0.0).ToString(),
							" submittedMs=",
							num3.ToString(),
							" leadMs=",
							(cue.TimeMs + appliedOffset - num3).ToString(),
							" submitCostMs=",
							num4.ToString()
						}));
					}
				}, (Action)([CompilerGenerated] () =>
				{
					scheduler.StopAll();
				}));
				long timestamp = Stopwatch.GetTimestamp();
				if (debug.Value && timestamp - lastStatus > Stopwatch.Frequency * 5)
				{
					lastStatus = timestamp;
					System.IntPtr intPtr2 = StaticObject(controllerField);
					double num2 = ((intPtr2 == System.IntPtr.Zero) ? (0.0 / 0.0) : (F64(intPtr2, 208) * 1000.0));
					((BasePlugin)this).Log.LogInfo((object)string.Concat(new string[16]
					{
						"CLOCK musicMs=",
						num.ToString(),
						" calibratedMs=",
						num2.ToString(),
						" deltaMs=",
						(num2 - num).ToString(),
						" scheduled=",
						scheduled.ToString(),
						" byRoute=",
						string.Join<int>(",", (System.Collections.Generic.IEnumerable<int>)scheduledByRoute),
						" suppressed=",
						suppressed.ToString(),
						" keyboardFeedbackSuppressed=",
						feedbackSuppressed.ToString(),
						" rate=",
						rate.ToString()
					}));
				}
			}
			catch (System.Exception e2)
			{
				Fail(e2);
			}
		}

		private static bool IsOwnedEvent(byte kind)
		{
			if (kind != 0 && kind != 2 && kind != 4 && kind != 5)
			{
				return kind == 6;
			}
			return true;
		}

		private void ProcessNote(System.IntPtr self, System.IntPtr records, System.IntPtr note, System.IntPtr queue, System.IntPtr mi)
		{
			HoldHeadScope holdHeadScope = holdHeads;
			bool flag = ready && enabled.Value && note != System.IntPtr.Zero && NoteAudioPolicy.OwnsKeyboardFeedback(I32(note, 20), I32(note, 16));
			System.IntPtr previous = holdHeadScope.Enter(flag, flag ? Marshal.ReadInt64(note) : 0, queue);
			try
			{
				originalProcessNote(self, records, note, queue, mi);
			}
			finally
			{
				holdHeadScope.Restore(previous);
			}
		}

		private void Enqueue(System.IntPtr self, System.IntPtr evt, System.IntPtr mi)
		{
			if (ready && enabled.Value && evt != System.IntPtr.Zero && holdHeads.Owns(self, Marshal.ReadByte(evt), I32(evt, 4)))
			{
				suppressed++;
				feedbackSuppressed++;
			}
			else
			{
				originalEnqueue(self, evt, mi);
			}
		}

		private void Dispatch(System.IntPtr self, System.IntPtr results, System.IntPtr evt, System.IntPtr mi)
		{
			if (ready && enabled.Value && scene == self && evt != System.IntPtr.Zero && IsOwnedEvent(Marshal.ReadByte(evt, 0)))
			{
				suppressed++;
			}
			else
			{
				originalDispatch(self, results, evt, mi);
			}
		}

		private void Hit(System.IntPtr self, byte kind, double offset, System.IntPtr mi)
		{
			if (ready && enabled.Value && scene == self)
			{
				suppressed++;
			}
			else
			{
				originalHit(self, kind, offset, mi);
			}
		}

		public override bool Unload()
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			ClearAudio();
			if (ready)
			{
				try
				{
					RestoreOriginalLoops();
				}
				catch (System.Exception ex)
				{
					((BasePlugin)this).Log.LogError((object)ex);
				}
			}
			ready = false;
			Enumerator<INativeDetour> enumerator = hooks.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					((System.IDisposable)enumerator.Current).Dispose();
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			hooks.Clear();
			mutePatch?.Dispose();
			return true;
		}
	}
	internal sealed class RuntimePatch : System.IDisposable
	{
		private readonly System.IntPtr address;

		private readonly byte[] before;

		private readonly byte[] after;

		private bool applied;

		[DllImport("kernel32", SetLastError = true)]
		private static extern bool VirtualProtect(System.IntPtr address, System.UIntPtr size, uint protection, out uint old);

		[DllImport("kernel32", SetLastError = true)]
		private static extern bool FlushInstructionCache(System.IntPtr process, System.IntPtr address, System.UIntPtr size);

		[DllImport("kernel32")]
		private static extern System.IntPtr GetCurrentProcess();

		private static bool Matches(System.IntPtr p, byte[] value)
		{
			for (int i = 0; i < value.Length; i++)
			{
				if (Marshal.ReadByte(p, i) != value[i])
				{
					return false;
				}
			}
			return true;
		}

		public RuntimePatch(System.IntPtr address, byte[] before, byte[] after)
		{
			//IL_0013: 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)
			if (before.Length != after.Length)
			{
				throw new ArgumentException("Patch sizes differ");
			}
			this.address = address;
			this.before = before;
			this.after = after;
			if (!Matches(address, before))
			{
				throw new InvalidOperationException("Runtime patch bytes differ; refusing patch.");
			}
			Write(after);
			applied = true;
		}

		private void Write(byte[] value)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: 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)
			if (!VirtualProtect(address, (System.UIntPtr)(ulong)value.Length, 64u, out var old))
			{
				throw new Win32Exception();
			}
			try
			{
				Marshal.Copy(value, 0, address, value.Length);
				if (!FlushInstructionCache(GetCurrentProcess(), address, (System.UIntPtr)(ulong)value.Length))
				{
					throw new Win32Exception();
				}
			}
			finally
			{
				if (!VirtualProtect(address, (System.UIntPtr)(ulong)value.Length, old, out var _))
				{
					throw new Win32Exception();
				}
			}
		}

		public void Dispose()
		{
			if (applied && Matches(address, after))
			{
				Write(before);
				applied = false;
			}
		}
	}
	public sealed class SourceLeases : System.IDisposable
	{
		private readonly System.IntPtr[] leases = new System.IntPtr[System.Enum.GetValues(typeof(AudioRoute)).Length];

		private readonly Func<int, System.IntPtr> acquire;

		private readonly Func<System.IntPtr, System.IntPtr> soundOf;

		private readonly Action<System.IntPtr> release;

		private readonly Func<System.IntPtr, bool> ready;

		public SourceLeases(Func<int, System.IntPtr> acquire, Func<System.IntPtr, System.IntPtr> soundOf, Action<System.IntPtr> release, Func<System.IntPtr, bool> ready)
		{
			this.acquire = acquire;
			this.soundOf = soundOf;
			this.release = release;
			this.ready = ready;
		}

		public System.IntPtr Resolve(int route, System.IntPtr borrowed)
		{
			if (leases[route] != System.IntPtr.Zero)
			{
				return soundOf.Invoke(leases[route]);
			}
			if (ready.Invoke(borrowed))
			{
				return borrowed;
			}
			System.IntPtr intPtr = acquire.Invoke(route);
			if (intPtr != System.IntPtr.Zero)
			{
				leases[route] = intPtr;
			}
			if (!(intPtr == System.IntPtr.Zero))
			{
				return soundOf.Invoke(intPtr);
			}
			return System.IntPtr.Zero;
		}

		public void Dispose()
		{
			for (int i = 0; i < leases.Length; i++)
			{
				System.IntPtr intPtr = leases[i];
				leases[i] = System.IntPtr.Zero;
				if (intPtr != System.IntPtr.Zero)
				{
					release.Invoke(intPtr);
				}
			}
		}
	}
}