Decompiled source of BreakAnkles v1.0.1

Mods\BreakAnkles.dll

Decompiled 16 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using BreakAnkles;
using HarmonyLib;
using Il2CppFemur;
using Il2CppGB.Game;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem.Collections.Generic;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Users;
using UnityEngine.InputSystem.Utilities;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Mod), "Break Ankles", "1.0.0", "BigPickle", null)]
[assembly: MelonGame("Boneloaf", "Gang Beasts")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("BigPickle")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Backdash with Whoosh + Meme SFX. Press Right Shift / RT to backdash: 3s cooldown, attacks landing during the dash deal 0 damage. Local play only.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("BreakAnkles")]
[assembly: AssemblyTitle("BreakAnkles")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BreakAnkles
{
	public static class Config
	{
		private static MelonPreferences_Category _cat;

		public static MelonPreferences_Entry<bool> Enabled;

		public static MelonPreferences_Entry<bool> RequireIncomingAttack;

		public static MelonPreferences_Entry<bool> LocalOnly;

		public static MelonPreferences_Entry<float> CooldownSeconds;

		public static MelonPreferences_Entry<float> DashSpeed;

		public static MelonPreferences_Entry<float> HopSpeed;

		public static MelonPreferences_Entry<float> IFrameSeconds;

		public static MelonPreferences_Entry<float> AttackRange;

		public static MelonPreferences_Entry<float> Volume;

		public static MelonPreferences_Entry<string> KeyboardButton;

		public static MelonPreferences_Entry<string> GamepadButton;

		public static void Init()
		{
			_cat = MelonPreferences.CreateCategory("BreakAnkles", "Break Ankles");
			Enabled = _cat.CreateEntry<bool>("Enabled", true, "Enabled", (string)null, false, false, (ValueValidator)null, (string)null);
			RequireIncomingAttack = _cat.CreateEntry<bool>("RequireIncomingAttack", false, "Only dodge when an attack is incoming", (string)null, false, false, (ValueValidator)null, (string)null);
			LocalOnly = _cat.CreateEntry<bool>("LocalOnly", true, "Only work in local play (disabled online)", (string)null, false, false, (ValueValidator)null, (string)null);
			CooldownSeconds = _cat.CreateEntry<float>("CooldownSeconds", 3f, "Cooldown between dodges (seconds)", (string)null, false, false, (ValueValidator)null, (string)null);
			DashSpeed = _cat.CreateEntry<float>("DashSpeed", 8f, "Backdash strength (velocity)", (string)null, false, false, (ValueValidator)null, (string)null);
			HopSpeed = _cat.CreateEntry<float>("HopSpeed", 3f, "Hop height (upward velocity)", (string)null, false, false, (ValueValidator)null, (string)null);
			IFrameSeconds = _cat.CreateEntry<float>("IFrameSeconds", 0.8f, "0-damage window during a backdash (seconds)", (string)null, false, false, (ValueValidator)null, (string)null);
			AttackRange = _cat.CreateEntry<float>("AttackRange", 3.2f, "Attack detection range (meters)", (string)null, false, false, (ValueValidator)null, (string)null);
			Volume = _cat.CreateEntry<float>("Volume", 1f, "Sound effect volume", (string)null, false, false, (ValueValidator)null, (string)null);
			KeyboardButton = _cat.CreateEntry<string>("KeyboardButton", "rightShift", "Keyboard button (Unity Input System path)", (string)null, false, false, (ValueValidator)null, (string)null);
			GamepadButton = _cat.CreateEntry<string>("GamepadButton", "rightTrigger", "Gamepad button (Unity Input System path)", (string)null, false, false, (ValueValidator)null, (string)null);
			MelonPreferences.Save();
		}
	}
	public static class Dodge
	{
		private sealed class DodgeState
		{
			public float CooldownUntil = float.MinValue;

			public float InvulnUntil = float.MinValue;

			public bool WeSetInvuln;

			public bool LastInputDown;
		}

		private static readonly List<Actor> LocalList = new List<Actor>();

		private static readonly List<DodgeState> StateList = new List<DodgeState>();

		public static bool IsInvulnerable(Actor actor)
		{
			if ((Object)(object)actor == (Object)null || !actor.IsLocal)
			{
				return false;
			}
			DodgeState dodgeState = FindState(actor);
			if (dodgeState != null)
			{
				return Time.time < dodgeState.InvulnUntil;
			}
			return false;
		}

		public static void Update()
		{
			if (!Config.Enabled.Value || (Config.LocalOnly.Value && !IsLocalGame()))
			{
				return;
			}
			List<Actor> cachedActors = Actor.CachedActors;
			if (cachedActors == null || cachedActors.Count == 0)
			{
				return;
			}
			for (int i = 0; i < cachedActors.Count; i++)
			{
				Actor val = cachedActors[i];
				if (!((Object)(object)val == (Object)null) && val.IsLocal && !((Object)(object)val.statusHandeler == (Object)null))
				{
					ProcessLocal(val);
				}
			}
		}

		private static void ProcessLocal(Actor local)
		{
			DodgeState state = GetState(local);
			if (state.WeSetInvuln && Time.time >= state.InvulnUntil)
			{
				state.WeSetInvuln = false;
				local.statusHandeler.invulnerable = false;
			}
			if (Time.time < state.CooldownUntil)
			{
				return;
			}
			bool flag = ReadDodgeInput(local);
			bool num = flag && !state.LastInputDown;
			state.LastInputDown = flag;
			if (num)
			{
				Actor val = FindAttacker(local);
				if (!Config.RequireIncomingAttack.Value || !((Object)(object)val == (Object)null))
				{
					DoDodge(local, val);
				}
			}
		}

		private static void DoDodge(Actor local, Actor attacker)
		{
			DodgeState state = GetState(local);
			state.CooldownUntil = Time.time + Config.CooldownSeconds.Value;
			state.InvulnUntil = Time.time + Config.IFrameSeconds.Value;
			if (!local.statusHandeler.invulnerable)
			{
				local.statusHandeler.invulnerable = true;
				state.WeSetInvuln = true;
			}
			ApplyDash(local, attacker);
			Mod.Log.Msg("BACKDASH! (" + ((Object)local).name + ")");
			SoundBank.PlayWeave();
			SoundBank.PlayBreakAnkles();
			if ((Object)(object)attacker != (Object)null)
			{
				Mod.Log.Msg("BREAK ANKLES! weaved " + ((Object)attacker).name + "'s attack");
			}
		}

		private static void ApplyDash(Actor local, Actor attacker)
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			BodyHandeler bodyHandeler = local.bodyHandeler;
			if ((Object)(object)bodyHandeler == (Object)null)
			{
				Mod.Log.Warning("Backdash skipped: no BodyHandeler on " + ((Object)local).name);
				return;
			}
			Il2CppReferenceArray<Rigidbody> allRigidbodies = bodyHandeler.GetAllRigidbodies();
			if (allRigidbodies == null || ((Il2CppArrayBase<Rigidbody>)(object)allRigidbodies).Count == 0)
			{
				Mod.Log.Warning("Backdash skipped: no rigidbodies on " + ((Object)local).name);
				return;
			}
			List<Rigidbody> list = new List<Rigidbody>(((Il2CppArrayBase<Rigidbody>)(object)allRigidbodies).Count);
			for (int i = 0; i < ((Il2CppArrayBase<Rigidbody>)(object)allRigidbodies).Count; i++)
			{
				Rigidbody val = ((Il2CppArrayBase<Rigidbody>)(object)allRigidbodies)[i];
				if ((Object)(object)val != (Object)null)
				{
					list.Add(val);
				}
			}
			if (list.Count != 0)
			{
				MelonCoroutines.Start(DashCoroutine(DashDirection(local, attacker), list));
			}
		}

		private static IEnumerator DashCoroutine(Vector3 dir, List<Rigidbody> rbs)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			float dash = Mathf.Max(1f, Config.DashSpeed.Value);
			float hop = Mathf.Max(0f, Config.HopSpeed.Value);
			for (int f = 0; f < 18; f++)
			{
				float num = ((f < 8) ? 1f : (1f - (float)(f - 8) / 10f));
				Vector3 velocity = dir * (dash * num) + Vector3.up * (hop * num);
				try
				{
					for (int i = 0; i < rbs.Count; i++)
					{
						Rigidbody val = rbs[i];
						if (!((Object)(object)val == (Object)null))
						{
							val.velocity = velocity;
						}
					}
				}
				catch
				{
					break;
				}
				yield return null;
			}
		}

		private static Vector3 DashDirection(Actor local, Actor attacker)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: 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_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: 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)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val;
			if ((Object)(object)attacker != (Object)null)
			{
				val = ((Component)local).transform.position - ((Component)attacker).transform.position;
				val.y = 0f;
				if (((Vector3)(ref val)).sqrMagnitude < 0.001f)
				{
					val = -((Component)local).transform.forward;
				}
			}
			else
			{
				val = -((Component)local).transform.forward;
			}
			if (!(((Vector3)(ref val)).sqrMagnitude > 0.001f))
			{
				return Vector3.back;
			}
			return ((Vector3)(ref val)).normalized;
		}

		private static Actor FindAttacker(Actor local)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			List<Actor> cachedActors = Actor.CachedActors;
			if (cachedActors == null || cachedActors.Count == 0)
			{
				return null;
			}
			Actor result = null;
			float num = float.MaxValue;
			float num2 = Mathf.Max(0f, Config.AttackRange.Value);
			Vector3 position = ((Component)local).transform.position;
			for (int i = 0; i < cachedActors.Count; i++)
			{
				Actor val = cachedActors[i];
				if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)local)
				{
					continue;
				}
				ControlHandeler controlHandeler = val.controlHandeler;
				if (controlHandeler != null && IsAttacking(controlHandeler))
				{
					float num3 = Vector3.Distance(((Component)val).transform.position, position);
					if (!(num3 > num2) && num3 < num)
					{
						num = num3;
						result = val;
					}
				}
			}
			return result;
		}

		private static bool IsAttacking(ControlHandeler ch)
		{
			if (!ch.leftPunch && !ch.rightPunch && !ch.leftKick && !ch.rightKick && !ch.headbutt && !ch.leftGrab)
			{
				return ch.rightGrab;
			}
			return true;
		}

		private static bool ReadDodgeInput(Actor local)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				InputUser inputPlayer = local.InputPlayer;
				ReadOnlyArray<InputDevice> pairedDevices = ((InputUser)(ref inputPlayer)).pairedDevices;
				if (pairedDevices.Count > 0)
				{
					for (int i = 0; i < pairedDevices.Count; i++)
					{
						InputDevice val = pairedDevices[i];
						if (val != null)
						{
							InputControl val2 = ((InputControl)val).TryGetChildControl(Config.GamepadButton.Value);
							if (val2 == null)
							{
								val2 = ((InputControl)val).TryGetChildControl(Config.KeyboardButton.Value);
							}
							ButtonControl val3 = (ButtonControl)(object)((val2 is ButtonControl) ? val2 : null);
							if (val3 != null && val3.isPressed)
							{
								return true;
							}
						}
					}
				}
			}
			catch
			{
			}
			try
			{
				Keyboard current = Keyboard.current;
				if (current != null && current.rightShiftKey != null && ((ButtonControl)current.rightShiftKey).isPressed)
				{
					return true;
				}
				Gamepad current2 = Gamepad.current;
				if (current2 != null && current2.rightTrigger != null && current2.rightTrigger.isPressed)
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static DodgeState GetState(Actor a)
		{
			DodgeState dodgeState = FindState(a);
			if (dodgeState != null)
			{
				return dodgeState;
			}
			dodgeState = new DodgeState();
			LocalList.Add(a);
			StateList.Add(dodgeState);
			return dodgeState;
		}

		private static DodgeState FindState(Actor a)
		{
			for (int i = 0; i < LocalList.Count; i++)
			{
				if ((Object)(object)LocalList[i] == (Object)(object)a)
				{
					return StateList[i];
				}
			}
			return null;
		}

		private static bool IsLocalGame()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameManagerNew instance = GameManagerNew.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return true;
				}
				long num = Convert.ToInt64(instance.CurrentGameType);
				return num == 0L || num == 1;
			}
			catch
			{
				return true;
			}
		}
	}
	public class Mod : MelonMod
	{
		private bool _loggedError;

		public static Mod Instance { get; private set; }

		public static Instance Log => Melon<Mod>.Logger;

		public override void OnInitializeMelon()
		{
			Instance = this;
			try
			{
				Config.Init();
				SoundBank.Init();
				Patches.Apply(((MelonBase)this).HarmonyInstance);
				Log.Msg("Break Ankles initialized!");
				Log.Msg("Press Right Shift (keyboard) or RT (gamepad) to backdash (3s cooldown). Attacks during the dash deal 0 damage.");
				Log.Msg("Drop .wav/.ogg/.mp3 files into:");
				Log.Msg("  " + SoundBank.WeaveFolder + "   -> dodge 'whoosh' sounds");
				Log.Msg("  " + SoundBank.BreakFolder + "   -> 'break ankles' sounds");
			}
			catch (Exception ex)
			{
				Log.Error("Break Ankles failed to initialize: " + ex);
			}
		}

		public override void OnUpdate()
		{
			if (Instance != this)
			{
				return;
			}
			try
			{
				Dodge.Update();
			}
			catch (Exception ex)
			{
				if (!_loggedError)
				{
					_loggedError = true;
					Log.Error("Break Ankles error in OnUpdate: " + ex);
				}
			}
		}
	}
	public static class Patches
	{
		public static void Apply(Harmony harmony)
		{
			try
			{
				harmony.PatchAll(Assembly.GetExecutingAssembly());
				Mod.Log.Msg("Harmony patches applied (damage weave active).");
			}
			catch (Exception ex)
			{
				Mod.Log.Warning("Could not apply Harmony patches, using invulnerability-flag weaving only: " + ex.Message);
			}
		}
	}
	[HarmonyPatch(typeof(StatusHandeler), "AddDamage")]
	public static class AddDamagePatch
	{
		public static bool Prefix(StatusHandeler __instance)
		{
			return !Dodge.IsInvulnerable(__instance.actor);
		}
	}
	public static class SoundBank
	{
		private sealed class SoundEntry
		{
			public string Path;

			public bool IsWav;

			public string Kind;

			public SoundEntry(string path, bool isWav, string kind)
			{
				Path = path;
				IsWav = isWav;
				Kind = kind;
			}
		}

		private const uint SND_FILENAME = 131072u;

		private const uint SND_ASYNC = 1u;

		private const uint SND_NODEFAULT = 2u;

		private const uint PlayFlags = 131075u;

		public static readonly string Root = Path.Combine(MelonEnvironment.UserDataDirectory, "BreakAnkles", "Sounds");

		public static readonly string WeaveFolder = Path.Combine(Root, "Weave");

		public static readonly string BreakFolder = Path.Combine(Root, "BreakAnkles");

		private static readonly string CacheDir = Path.Combine(MelonEnvironment.UserDataDirectory, "BreakAnkles", "Cache");

		private static readonly List<SoundEntry> WeaveSounds = new List<SoundEntry>();

		private static readonly List<SoundEntry> BreakSounds = new List<SoundEntry>();

		private static int _lastWeave = -1;

		private static int _lastBreak = -1;

		private static readonly HashSet<string> Warned = new HashSet<string>();

		public static bool HasWeaveSounds => WeaveSounds.Count > 0;

		public static bool HasBreakSounds => BreakSounds.Count > 0;

		[DllImport("winmm.dll", CharSet = CharSet.Unicode, SetLastError = true)]
		private static extern bool PlaySound(string pszSound, IntPtr hmod, uint fdwSound);

		[DllImport("winmm.dll", CharSet = CharSet.Unicode)]
		private static extern int mciSendString(string command, StringBuilder ret, int retLen, IntPtr cb);

		public static void Init()
		{
			try
			{
				Directory.CreateDirectory(CacheDir);
			}
			catch
			{
			}
			MelonCoroutines.Start(LoadCoroutine());
		}

		private static IEnumerator LoadCoroutine()
		{
			yield return null;
			if (Directory.Exists(WeaveFolder))
			{
				string[] files = Directory.GetFiles(WeaveFolder);
				foreach (string file in files)
				{
					yield return LoadFileInto(file, "weave_", WeaveSounds);
				}
			}
			if (Directory.Exists(BreakFolder))
			{
				string[] files = Directory.GetFiles(BreakFolder);
				foreach (string file2 in files)
				{
					yield return LoadFileInto(file2, "ankle_", BreakSounds);
				}
			}
			Mod.Log.Msg($"Loaded {WeaveSounds.Count} weave sound(s), {BreakSounds.Count} break-ankles sound(s).");
			if (WeaveSounds.Count == 0 || BreakSounds.Count == 0)
			{
				Mod.Log.Warning("Missing sounds! Drop files into " + Root + " (folders 'Weave' and 'BreakAnkles') and restart the game.");
			}
		}

		private static IEnumerator LoadFileInto(string file, string prefix, List<SoundEntry> pool)
		{
			string fileName = Path.GetFileName(file);
			string text = SniffKind(file);
			switch (text)
			{
			case "wav":
			{
				string text2 = CacheWav(file, prefix);
				if (text2 != null)
				{
					pool.Add(new SoundEntry(text2, isWav: true, "wav"));
					Mod.Log.Msg("Loaded sound: " + fileName);
				}
				break;
			}
			case "mp3":
			case "m4a":
			case "ogg":
			case "media":
				pool.Add(new SoundEntry(file, isWav: false, text));
				Mod.Log.Msg("Loaded sound: " + fileName + " (" + text + ")");
				break;
			default:
				Mod.Log.Warning("Skipped '" + fileName + "': unknown file type.");
				break;
			}
			yield break;
		}

		private static string SniffKind(string path)
		{
			try
			{
				using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
				fileStream.Position = 0L;
				byte[] array = new byte[12];
				int num = fileStream.Read(array, 0, 12);
				if (num >= 4 && array[0] == 82 && array[1] == 73 && array[2] == 70 && array[3] == 70)
				{
					return "wav";
				}
				if (num >= 3 && array[0] == 73 && array[1] == 68 && array[2] == 51)
				{
					return "mp3";
				}
				if (num >= 8 && array[4] == 102 && array[5] == 116 && array[6] == 121 && array[7] == 112)
				{
					return "m4a";
				}
				if (num >= 4 && array[0] == 79 && array[1] == 103 && array[2] == 103 && array[3] == 83)
				{
					return "ogg";
				}
				return "media";
			}
			catch
			{
				return "unknown";
			}
		}

		private static string CacheWav(string source, string prefix)
		{
			try
			{
				if (!ParseWav(source, out var rate, out var channels, out var samples))
				{
					return null;
				}
				return WriteWav(prefix + Path.GetFileNameWithoutExtension(source) + ".wav", samples, channels, rate);
			}
			catch (Exception ex)
			{
				return WarnBad(source, ex.Message);
			}
		}

		private static bool ParseWav(string path, out int rate, out ushort channels, out float[] samples)
		{
			rate = 0;
			channels = 0;
			samples = null;
			byte[] array = File.ReadAllBytes(path);
			if (array.Length < 44)
			{
				WarnBad(path, "file too small");
				return false;
			}
			if (array[0] != 82 || array[1] != 73 || array[2] != 70 || array[3] != 70 || array[8] != 87 || array[9] != 65 || array[10] != 86 || array[11] != 69)
			{
				WarnBad(path, "not a RIFF/WAVE file");
				return false;
			}
			int num = -1;
			int num2 = -1;
			int num3 = 0;
			int num5;
			for (int i = 12; i + 8 <= array.Length; i += num5)
			{
				string text = Encoding.ASCII.GetString(array, i, 4);
				int num4 = BitConverter.ToInt32(array, i + 4);
				if (text == "fmt ")
				{
					num = i + 8;
				}
				else if (text == "data")
				{
					num2 = i + 8;
					num3 = num4;
					break;
				}
				num5 = 8 + num4;
				if (num5 <= 0)
				{
					break;
				}
			}
			if (num < 0 || num2 < 0)
			{
				WarnBad(path, "missing fmt/data chunk");
				return false;
			}
			if (num3 <= 0)
			{
				WarnBad(path, "empty data chunk");
				return false;
			}
			ushort num6 = BitConverter.ToUInt16(array, num);
			channels = BitConverter.ToUInt16(array, num + 2);
			rate = BitConverter.ToInt32(array, num + 4);
			ushort num7 = BitConverter.ToUInt16(array, num + 14);
			if (channels == 0 || rate <= 0)
			{
				WarnBad(path, "bad header");
				return false;
			}
			if (num6 != 1 && num6 != 3)
			{
				WarnBad(path, "compressed WAV not supported (use PCM .wav)");
				return false;
			}
			int num8 = array.Length - num2;
			if (num3 > num8)
			{
				num3 = num8;
			}
			int num9 = (num7 + 7) / 8;
			if (num9 == 0)
			{
				WarnBad(path, "bad bit depth");
				return false;
			}
			int num10 = num3 / (num9 * channels);
			if (num10 <= 0)
			{
				WarnBad(path, "no samples");
				return false;
			}
			samples = new float[num10 * channels];
			if (num6 == 3)
			{
				if (num7 != 32)
				{
					WarnBad(path, "float WAV must be 32-bit");
					return false;
				}
				int num11 = 0;
				int num12 = num2;
				while (num11 < samples.Length)
				{
					samples[num11] = BitConverter.ToSingle(array, num12);
					num11++;
					num12 += 4;
				}
			}
			else
			{
				switch (num7)
				{
				case 8:
				{
					int num20 = 0;
					int num21 = num2;
					while (num20 < samples.Length)
					{
						samples[num20] = ((float)(int)array[num21] - 128f) / 128f;
						num20++;
						num21++;
					}
					break;
				}
				case 16:
				{
					int num15 = 0;
					int num16 = num2;
					while (num15 < samples.Length)
					{
						samples[num15] = (float)BitConverter.ToInt16(array, num16) / 32768f;
						num15++;
						num16 += 2;
					}
					break;
				}
				case 24:
				{
					int num17 = 0;
					int num18 = num2;
					while (num17 < samples.Length)
					{
						int num19 = array[num18] | (array[num18 + 1] << 8) | (array[num18 + 2] << 16);
						if ((num19 & 0x800000) != 0)
						{
							num19 |= -16777216;
						}
						samples[num17] = (float)num19 / 8388608f;
						num17++;
						num18 += 3;
					}
					break;
				}
				case 32:
				{
					int num13 = 0;
					int num14 = num2;
					while (num13 < samples.Length)
					{
						samples[num13] = (float)BitConverter.ToInt32(array, num14) / 2.1474836E+09f;
						num13++;
						num14 += 4;
					}
					break;
				}
				default:
					WarnBad(path, "unsupported bit depth " + num7);
					return false;
				}
			}
			return true;
		}

		private static string WriteWav(string cacheName, float[] samples, int channels, int rate)
		{
			if (samples == null || samples.Length == 0 || channels <= 0 || rate <= 0)
			{
				return null;
			}
			float num = 0f;
			for (int i = 0; i < samples.Length; i++)
			{
				float num2 = Math.Abs(samples[i]);
				if (num2 > num)
				{
					num = num2;
				}
			}
			float num3 = ((num > 0.0001f && num < 0.9f) ? (0.9f / num) : 1f);
			if (num3 > 1f)
			{
				num3 = 1f;
			}
			string text = Path.Combine(CacheDir, cacheName);
			using FileStream output = new FileStream(text, FileMode.Create);
			using BinaryWriter binaryWriter = new BinaryWriter(output);
			int num4 = samples.Length * 2;
			binaryWriter.Write(Encoding.ASCII.GetBytes("RIFF"));
			binaryWriter.Write(36 + num4);
			binaryWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
			binaryWriter.Write(Encoding.ASCII.GetBytes("fmt "));
			binaryWriter.Write(16);
			binaryWriter.Write((short)1);
			binaryWriter.Write((short)channels);
			binaryWriter.Write(rate);
			binaryWriter.Write(rate * channels * 2);
			binaryWriter.Write((short)(channels * 2));
			binaryWriter.Write((short)16);
			binaryWriter.Write(Encoding.ASCII.GetBytes("data"));
			binaryWriter.Write(num4);
			for (int j = 0; j < samples.Length; j++)
			{
				float num5 = samples[j] * num3;
				if (num5 < -1f)
				{
					num5 = -1f;
				}
				else if (num5 > 1f)
				{
					num5 = 1f;
				}
				binaryWriter.Write((short)(num5 * 32767f));
			}
			return text;
		}

		private static string WarnBad(string path, string why)
		{
			Mod.Log.Warning("Skipped '" + Path.GetFileName(path) + "': " + why);
			return null;
		}

		public static void PlayWeave()
		{
			PlayRandom(WeaveSounds, ref _lastWeave, 0f, "bkweave");
		}

		public static void PlayBreakAnkles()
		{
			PlayRandom(BreakSounds, ref _lastBreak, 0.35f, "bkmeme");
		}

		private static void PlayRandom(List<SoundEntry> pool, ref int lastIdx, float delay, string mciAlias)
		{
			if (pool.Count == 0)
			{
				return;
			}
			int num;
			if (pool.Count == 1)
			{
				num = 0;
			}
			else
			{
				num = Random.Range(0, pool.Count);
				if (num == lastIdx)
				{
					num = (num + 1) % pool.Count;
				}
			}
			lastIdx = num;
			SoundEntry entry = pool[num];
			if (delay <= 0f)
			{
				PlayNow(entry, mciAlias);
			}
			else
			{
				MelonCoroutines.Start(PlayDelayed(entry, mciAlias, delay));
			}
		}

		private static IEnumerator PlayDelayed(SoundEntry entry, string mciAlias, float delay)
		{
			yield return (object)new WaitForSeconds(delay);
			PlayNow(entry, mciAlias);
		}

		private static void PlayNow(SoundEntry entry, string mciAlias)
		{
			if (entry.IsWav)
			{
				try
				{
					PlaySound(entry.Path, IntPtr.Zero, 131075u);
					return;
				}
				catch (Exception ex)
				{
					WarnOnce(entry, "could not play: " + ex.Message);
					return;
				}
			}
			try
			{
				mciSendString("close " + mciAlias, null, 0, IntPtr.Zero);
				if (mciSendString("open \"" + entry.Path.Replace("\"", "") + "\" alias " + mciAlias + " type mpegvideo", null, 0, IntPtr.Zero) != 0 && mciSendString("open \"" + entry.Path.Replace("\"", "") + "\" alias " + mciAlias, null, 0, IntPtr.Zero) != 0)
				{
					WarnOnce(entry, "this format can't be played on this PC (" + entry.Kind + "). Convert or re-download it as .wav and it will work.");
				}
				else
				{
					mciSendString("play " + mciAlias, null, 0, IntPtr.Zero);
				}
			}
			catch (Exception ex2)
			{
				WarnOnce(entry, "could not play: " + ex2.Message);
			}
		}

		private static void WarnOnce(SoundEntry entry, string why)
		{
			if (Warned.Add(entry.Path))
			{
				Mod.Log.Warning("'" + Path.GetFileName(entry.Path) + "' " + why);
			}
		}
	}
}