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.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using CustomMusic;
using HarmonyLib;
using Il2Cpp;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Reflection;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using NLayer;
using NVorbis;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(CustomMusicMod), "CustomMusic", "1.0.8", "misha", null)]
[assembly: MelonGame("Kubunautilus", "BrutalistickVR")]
[assembly: MelonPriority(-100)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("CustomMusic")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+2d1a60d6d74d1b7059d3220dce1f21958635cd5c")]
[assembly: AssemblyProduct("CustomMusic")]
[assembly: AssemblyTitle("CustomMusic")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially or fully created with the assistance of Generative AI (e.g., Code Suggestions, Refactoring, Documentation Generation).")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 CustomMusic
{
internal sealed class DecodedAudio
{
public string Name;
public int Channels;
public int SampleRate;
public float[] Samples;
public int LengthMs
{
get
{
if (Channels <= 0 || SampleRate <= 0 || Samples == null)
{
return 0;
}
return (int)((long)(Samples.Length / Channels) * 1000L / SampleRate);
}
}
}
internal sealed class PreparedTrack
{
public string PlayPath;
public string DeviceType;
public bool Temporary;
}
internal static class AudioDecode
{
private const string Tag = "[CustomMusic]";
public static PreparedTrack Prepare(string path)
{
string text = Path.GetExtension(path).ToLowerInvariant();
string text2 = TryGetCachedWav(path);
if (text2 != null)
{
return new PreparedTrack
{
PlayPath = text2,
DeviceType = "waveaudio",
Temporary = true
};
}
switch (text)
{
case ".ogg":
{
DecodedAudio audio3 = OggDecoder.Decode(path);
return new PreparedTrack
{
PlayPath = WriteTempWav(path, audio3),
DeviceType = "waveaudio",
Temporary = true
};
}
case ".mp3":
{
DecodedAudio audio2 = Mp3Decoder.Decode(path);
return new PreparedTrack
{
PlayPath = WriteTempWav(path, audio2),
DeviceType = "waveaudio",
Temporary = true
};
}
case ".wav":
{
if (WavDecoder.IsMciFriendly(path) && IsAscii(path))
{
return new PreparedTrack
{
PlayPath = path,
DeviceType = "waveaudio",
Temporary = false
};
}
DecodedAudio audio = WavDecoder.Decode(path);
return new PreparedTrack
{
PlayPath = WriteTempWav(path, audio),
DeviceType = "waveaudio",
Temporary = true
};
}
default:
return new PreparedTrack
{
PlayPath = path,
DeviceType = "waveaudio",
Temporary = false
};
}
}
private static bool IsAscii(string s)
{
for (int i = 0; i < s.Length; i++)
{
if (s[i] > '\u007f')
{
return false;
}
}
return true;
}
private static string TryGetCachedWav(string source)
{
try
{
string text = Path.Combine(MelonEnvironment.UserDataDirectory, "CustomMusicCache");
if (!Directory.Exists(text))
{
return null;
}
string text2 = Hash(source + "|" + File.GetLastWriteTimeUtc(source).Ticks + "|" + new FileInfo(source).Length);
string text3 = Path.Combine(text, text2 + ".wav");
if (File.Exists(text3) && new FileInfo(text3).Length > 44)
{
return text3;
}
}
catch
{
}
return null;
}
private static string WriteTempWav(string source, DecodedAudio audio)
{
if (audio == null || audio.Samples == null || audio.Samples.Length == 0)
{
throw new InvalidDataException("empty PCM data");
}
if (audio.Channels <= 0 || audio.SampleRate <= 0)
{
throw new InvalidDataException($"bad audio format: {audio.Channels} ch, {audio.SampleRate} Hz");
}
string text = Path.Combine(MelonEnvironment.UserDataDirectory, "CustomMusicCache");
Directory.CreateDirectory(text);
string text2 = Hash(source + "|" + File.GetLastWriteTimeUtc(source).Ticks + "|" + new FileInfo(source).Length);
string text3 = Path.Combine(text, text2 + ".wav");
if (File.Exists(text3) && new FileInfo(text3).Length > 44)
{
return text3;
}
string text4 = text3 + ".tmp";
using (FileStream fileStream = new FileStream(text4, FileMode.Create, FileAccess.Write, FileShare.None, 65536))
{
using BinaryWriter binaryWriter = new BinaryWriter(fileStream, Encoding.ASCII);
int channels = audio.Channels;
int num = audio.Samples.Length / channels * channels * 2;
binaryWriter.Write(Encoding.ASCII.GetBytes("RIFF"));
binaryWriter.Write(36 + num);
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(audio.SampleRate);
binaryWriter.Write(audio.SampleRate * channels * 2);
binaryWriter.Write((short)(channels * 2));
binaryWriter.Write((short)16);
binaryWriter.Write(Encoding.ASCII.GetBytes("data"));
binaryWriter.Write(num);
byte[] array = new byte[16384];
int num2 = audio.Samples.Length;
int num3;
for (int i = 0; i < num2; i += num3)
{
num3 = Math.Min(8192, num2 - i);
for (int j = 0; j < num3; j++)
{
float num4 = audio.Samples[i + j];
if (num4 > 1f)
{
num4 = 1f;
}
else if (num4 < -1f)
{
num4 = -1f;
}
short num5 = (short)Math.Round((num4 >= 0f) ? (num4 * 32767f) : (num4 * 32768f));
array[j * 2] = (byte)(num5 & 0xFF);
array[j * 2 + 1] = (byte)((num5 >> 8) & 0xFF);
}
fileStream.Write(array, 0, num3 * 2);
}
}
File.Move(text4, text3, overwrite: true);
MelonLogger.Msg($"{"[CustomMusic]"} cached WAV: {Path.GetFileName(text3)} ({new FileInfo(text3).Length / 1024} KB)");
return text3;
}
private static string Hash(string text)
{
using SHA1 sHA = SHA1.Create();
byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(text));
StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
byte[] array2 = array;
foreach (byte b in array2)
{
stringBuilder.Append(b.ToString("x2"));
}
return stringBuilder.ToString();
}
}
internal sealed class FloatBuffer
{
private float[] _data;
public int Count;
public FloatBuffer(int capacity)
{
_data = new float[Math.Max(4096, capacity)];
}
public void AddRange(float[] src, int count)
{
if (count > 0)
{
Ensure(Count + count);
Array.Copy(src, 0, _data, Count, count);
Count += count;
}
}
public float[] Finish()
{
if (Count != _data.Length)
{
Array.Resize(ref _data, Count);
}
return _data;
}
private void Ensure(int needed)
{
if (needed > _data.Length)
{
int num;
for (num = _data.Length; num < needed; num = ((num > 1073741823) ? int.MaxValue : (num * 2)))
{
}
Array.Resize(ref _data, num);
}
}
}
internal static class CassetteSfx
{
private const string Tag = "[CustomMusic]";
private const uint SND_ASYNC = 1u;
private const uint SND_NODEFAULT = 2u;
private static float _cachedInsertDuration = -1f;
[DllImport("winmm.dll", CharSet = CharSet.Unicode)]
private static extern bool PlaySoundW(string pszSound, IntPtr hmod, uint fdwSound);
public static void EnsureSfxFiles()
{
if (!string.IsNullOrEmpty(TrackLibrary.Root))
{
EnsureSfxFilesTo(Path.Combine(TrackLibrary.Root, "sfx"));
}
}
public static void EnsureSfxFilesTo(string sfxDir)
{
try
{
if (!string.IsNullOrEmpty(sfxDir))
{
if (!Directory.Exists(sfxDir))
{
Directory.CreateDirectory(sfxDir);
}
ExtractResourceIfMissing("tape_insert.wav", Path.Combine(sfxDir, "tape_insert.wav"));
ExtractResourceIfMissing("tape_eject.wav", Path.Combine(sfxDir, "tape_eject.wav"));
}
}
catch (Exception ex)
{
MelonLogger.Warning("[CustomMusic] auto-extract sfx failed: " + ex.Message);
}
}
private static void ExtractResourceIfMissing(string resourceFileName, string destPath)
{
if (File.Exists(destPath))
{
return;
}
Assembly assembly = typeof(CassetteSfx).Assembly;
string text = null;
string[] manifestResourceNames = assembly.GetManifestResourceNames();
foreach (string text2 in manifestResourceNames)
{
if (text2.EndsWith(resourceFileName, StringComparison.OrdinalIgnoreCase))
{
text = text2;
break;
}
}
if (text == null)
{
return;
}
using Stream stream = assembly.GetManifestResourceStream(text);
if (stream != null)
{
using (FileStream destination = new FileStream(destPath, FileMode.Create, FileAccess.Write))
{
stream.CopyTo(destination);
}
MelonLogger.Msg("[CustomMusic] auto-extracted missing '" + resourceFileName + "' into UserData\\CustomMusic\\sfx");
}
}
public static float GetInsertDuration()
{
if (_cachedInsertDuration > 0f)
{
return _cachedInsertDuration;
}
float num = ReadWavDuration("tape_insert.wav");
if (num <= 0f)
{
num = 1.735f;
}
_cachedInsertDuration = num;
MelonLogger.Msg($"{"[CustomMusic]"} tape_insert.wav duration: {num:F3}s");
return num;
}
public static void InvalidateCache()
{
_cachedInsertDuration = -1f;
}
public static void PlayInsert()
{
if (Cfg.CassetteSfx.Value)
{
Play("tape_insert.wav");
}
}
public static void PlayEject()
{
if (Cfg.CassetteSfx.Value)
{
Play("tape_eject.wav");
}
}
private static void Play(string filename)
{
try
{
if (!string.IsNullOrEmpty(TrackLibrary.Root))
{
string text = Path.Combine(Path.Combine(TrackLibrary.Root, "sfx"), filename);
if (!File.Exists(text))
{
text = Path.Combine(TrackLibrary.Root, filename);
}
if (File.Exists(text))
{
PlaySoundW(text, IntPtr.Zero, 3u);
}
}
}
catch (Exception ex)
{
MelonLogger.Warning($"{"[CustomMusic]"} SFX error '{filename}': {ex.Message}");
}
}
private static float ReadWavDuration(string filename)
{
try
{
if (string.IsNullOrEmpty(TrackLibrary.Root))
{
return 0f;
}
string path = Path.Combine(TrackLibrary.Root, "sfx", filename);
if (!File.Exists(path))
{
path = Path.Combine(TrackLibrary.Root, filename);
}
if (!File.Exists(path))
{
return 0f;
}
using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
using BinaryReader binaryReader = new BinaryReader(fileStream);
if (fileStream.Length < 44)
{
return 0f;
}
byte[] bytes = binaryReader.ReadBytes(4);
if (Encoding.ASCII.GetString(bytes) != "RIFF")
{
return 0f;
}
binaryReader.ReadInt32();
byte[] bytes2 = binaryReader.ReadBytes(4);
if (Encoding.ASCII.GetString(bytes2) != "WAVE")
{
return 0f;
}
int num = 0;
int num2 = 0;
int num3 = 0;
while (fileStream.Position + 8 <= fileStream.Length)
{
byte[] bytes3 = binaryReader.ReadBytes(4);
int num4 = binaryReader.ReadInt32();
if (num4 < 0)
{
break;
}
string text = Encoding.ASCII.GetString(bytes3);
long val = fileStream.Position + num4 + (num4 & 1);
if (text == "fmt ")
{
binaryReader.ReadInt16();
binaryReader.ReadInt16();
num = binaryReader.ReadInt32();
binaryReader.ReadInt32();
num2 = binaryReader.ReadInt16();
}
else if (text == "data")
{
num3 = num4;
break;
}
fileStream.Position = Math.Min(val, fileStream.Length);
}
if (num > 0 && num2 > 0 && num3 > 0)
{
return (float)num3 / (float)(num * num2);
}
}
catch
{
}
return 0f;
}
}
public static class Cfg
{
public const string Category = "CustomMusic";
private const int Schema = 2;
public static MelonPreferences_Category Prefs;
public static MelonPreferences_Entry<bool> Enabled;
public static MelonPreferences_Entry<bool> ReplaceStock;
public static MelonPreferences_Entry<bool> Autoplay;
public static MelonPreferences_Entry<bool> Shuffle;
public static MelonPreferences_Entry<int> Loop;
public static MelonPreferences_Entry<bool> CassetteSfx;
public static MelonPreferences_Entry<float> Crossfade;
public static MelonPreferences_Entry<float> Volume;
public static MelonPreferences_Entry<bool> Verbose;
public static MelonPreferences_Entry<int> ConfigSchema;
public static void Init()
{
Prefs = MelonPreferences.CreateCategory("CustomMusic", "Custom Music");
ConfigSchema = Prefs.CreateEntry<int>("Schema", 2, "Config schema (do not edit)", (string)null, false, false, (ValueValidator)null, (string)null);
Enabled = Prefs.CreateEntry<bool>("Enabled", true, "Mod enabled", (string)null, false, false, (ValueValidator)null, (string)null);
ReplaceStock = Prefs.CreateEntry<bool>("ReplaceStock", true, "Silence the game's own music (menu, hub, missions)", (string)null, false, false, (ValueValidator)null, (string)null);
Autoplay = Prefs.CreateEntry<bool>("Autoplay", true, "Start the playlist automatically once the game reaches the first scene (false = start paused)", (string)null, false, false, (ValueValidator)null, (string)null);
Shuffle = Prefs.CreateEntry<bool>("Shuffle", false, "true = random order, false = play the list in file-name order (1, 2, 3, ...)", (string)null, false, false, (ValueValidator)null, (string)null);
Loop = Prefs.CreateEntry<int>("Loop", 0, "Loop mode: 0 = Loop All (playlist), 1 = Loop Single (repeat track), 2 = Loop Off", (string)null, false, false, (ValueValidator)null, (string)null);
CassetteSfx = Prefs.CreateEntry<bool>("CassetteSfx", true, "Play retro VHS cassette mechanical sounds on play, pause and stop", (string)null, false, false, (ValueValidator)null, (string)null);
Crossfade = Prefs.CreateEntry<float>("Crossfade", 1f, "Fade time in seconds between tracks (0 = hard cut)", (string)null, false, false, (ValueValidator)null, (string)null);
Volume = Prefs.CreateEntry<float>("Volume", 1f, "Extra volume multiplier on top of the game's Music slider (0..1)", (string)null, false, false, (ValueValidator)null, (string)null);
Verbose = Prefs.CreateEntry<bool>("Verbose", true, "Log every track change and every blocked stock-music call", (string)null, false, false, (ValueValidator)null, (string)null);
if (ConfigSchema.Value != 2)
{
MelonLogger.Msg("[CustomMusic] config schema changed, rewriting defaults");
ConfigSchema.Value = 2;
}
MelonPreferences.Save();
}
}
public class CustomMusicMod : MelonMod
{
public const string Tag = "[CustomMusic]";
internal static PreparedTrack Current;
internal static string CurrentName;
internal static bool WantPlay;
internal static bool Paused;
private static readonly Random Rng;
private static readonly HashSet<string> Bad;
private static int _index;
private static bool _loading;
private static bool _waitingForInsert;
private static bool _playbackActive;
private static bool _booted;
private static float _fade;
private static float _fadeTarget;
private static float _fadeTime;
private static float _retryAt;
private static float _playStartedAt;
private static float _nextVolume;
private static float _nextGameVolume;
private static float _nextStatusLog;
private static bool _uiProbed;
private static float _nextEnsure;
private static bool _resolverRegistered;
public static bool IsPlaying
{
get
{
if (MciPlayer.IsOpen)
{
return MciPlayer.Mode == "playing";
}
return false;
}
}
public static bool IsWaitingForInsert => _waitingForInsert;
public static int PositionMs => MciPlayer.PositionMs;
public static int LengthMs => MciPlayer.LengthMs;
public static int CurrentIndex => _index;
public static bool IsLoading => _loading;
public static int BadCount => Bad.Count;
static CustomMusicMod()
{
Rng = new Random();
Bad = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
_index = -1;
_fade = 1f;
_fadeTarget = 1f;
_fadeTime = 0.25f;
_retryAt = -1f;
RegisterAssemblyResolver();
}
public override void OnEarlyInitializeMelon()
{
RegisterAssemblyResolver();
}
private static void RegisterAssemblyResolver()
{
if (!_resolverRegistered)
{
_resolverRegistered = true;
AppDomain.CurrentDomain.AssemblyResolve += ResolveEmbeddedDependency;
}
}
private static Assembly ResolveEmbeddedDependency(object sender, ResolveEventArgs args)
{
try
{
string name = new AssemblyName(args.Name).Name;
if (string.Equals(name, "NLayer", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "NVorbis", StringComparison.OrdinalIgnoreCase))
{
Assembly assembly = typeof(CustomMusicMod).Assembly;
string text = "CustomMusic.libs." + name + ".dll";
using (Stream stream = assembly.GetManifestResourceStream(text))
{
if (stream != null)
{
byte[] array = new byte[stream.Length];
int num;
for (int i = 0; i < array.Length; i += num)
{
num = stream.Read(array, i, array.Length - i);
if (num <= 0)
{
break;
}
}
MelonLogger.Msg($"{"[CustomMusic]"} successfully loaded embedded '{name}' ({array.Length} bytes)");
return Assembly.Load(array);
}
}
MelonLogger.Warning("[CustomMusic] embedded resource '" + text + "' is missing");
}
}
catch (Exception ex)
{
MelonLogger.Warning($"{"[CustomMusic]"} ResolveEmbeddedDependency for {args.Name} failed: {ex.Message}");
}
return null;
}
public override void OnInitializeMelon()
{
RegisterAssemblyResolver();
Cfg.Init();
TrackLibrary.Root = Path.Combine(MelonEnvironment.UserDataDirectory, "CustomMusic");
TrackLibrary.EnsureStarterFiles();
TrackLibrary.Scan();
MelonLogger.Msg("[CustomMusic] folder: " + TrackLibrary.Root);
MelonLogger.Msg($"{"[CustomMusic]"} tracks: {TrackLibrary.Files.Count}");
foreach (string file in TrackLibrary.Files)
{
MelonLogger.Msg("[CustomMusic] " + Path.GetFileName(file));
}
PatchStock();
PatchTabs();
PatchClicks();
MelonLogger.Msg($"{"[CustomMusic]"} ready (replaceStock={Cfg.ReplaceStock.Value}, shuffle={Cfg.Shuffle.Value})");
}
private void PatchTabs()
{
//IL_004a: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Expected O, but got Unknown
try
{
MethodInfo method = typeof(MenuUIManager).GetMethod("ChangeActiveTab", BindingFlags.Instance | BindingFlags.Public);
if (method == null)
{
MelonLogger.Warning("[CustomMusic] MenuUIManager.ChangeActiveTab not found - settings plate will still work through pointer handlers");
return;
}
((MelonBase)this).HarmonyInstance.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(typeof(CustomMusicMod).GetMethod("ChangeActiveTabPostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
MelonLogger.Msg("[CustomMusic] patched MenuUIManager.ChangeActiveTab");
}
catch (Exception ex)
{
MelonLogger.Warning("[CustomMusic] ChangeActiveTab patch failed (plate will use pointer handlers only): " + ex.Message);
}
}
private static void ChangeActiveTabPostfix(int tabIndex)
{
MusicPanel.Hide();
}
private void PatchClicks()
{
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
//IL_006c: Expected O, but got Unknown
try
{
MethodInfo method = typeof(Button).GetMethod("OnPointerClick", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(PointerEventData) }, null);
if (method == null)
{
MelonLogger.Warning("[CustomMusic] Button.OnPointerClick not found - settings plate buttons will not respond");
return;
}
((MelonBase)this).HarmonyInstance.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(typeof(CustomMusicMod).GetMethod("OnPointerClickPostfix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
MelonLogger.Msg("[CustomMusic] patched Button.OnPointerClick");
}
catch (Exception ex)
{
MelonLogger.Warning("[CustomMusic] Button.OnPointerClick patch failed: " + ex.Message);
}
}
private static void OnPointerClickPostfix(Button __instance)
{
try
{
MusicPanel.OnButtonClicked(__instance);
}
catch (Exception ex)
{
MelonLogger.Warning("[CustomMusic] click dispatch failed: " + ex.Message);
}
}
private void PatchStock()
{
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Expected O, but got Unknown
try
{
MethodInfo method = typeof(MusicManager).GetMethod("StartMusic", BindingFlags.Instance | BindingFlags.Public);
if (method == null)
{
MelonLogger.Warning("[CustomMusic] MusicManager.StartMusic not found - stock music will keep playing");
return;
}
((MelonBase)this).HarmonyInstance.Patch((MethodBase)method, new HarmonyMethod(typeof(CustomMusicMod).GetMethod("StartMusicPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
MelonLogger.Msg("[CustomMusic] patched MusicManager.StartMusic");
}
catch (Exception value)
{
MelonLogger.Error($"{"[CustomMusic]"} patch failed: {value}");
}
}
private static bool StartMusicPrefix(bool __0)
{
if (!Cfg.Enabled.Value || !Cfg.ReplaceStock.Value)
{
return true;
}
if (TrackLibrary.Files.Count == 0)
{
return true;
}
if (Cfg.Verbose.Value)
{
MelonLogger.Msg($"{"[CustomMusic]"} blocked stock StartMusic(isMainMenu={__0})");
}
return false;
}
public override void OnUpdate()
{
if (Time.unscaledTime >= _nextEnsure)
{
_nextEnsure = Time.unscaledTime + 1f;
MusicPanel.Ensure();
}
MusicPanel.Tick();
if (Time.unscaledTime >= _nextGameVolume)
{
_nextGameVolume = Time.unscaledTime + 1f;
MusicPanel.UpdateGameVolume();
}
MciPlayer.Poll();
if (!Cfg.Enabled.Value)
{
if (MciPlayer.IsOpen)
{
Stop();
}
return;
}
UpdateFade();
if (Cfg.Verbose.Value && MciPlayer.IsOpen && Time.unscaledTime >= _nextStatusLog)
{
_nextStatusLog = Time.unscaledTime + 10f;
MelonLogger.Msg($"{"[CustomMusic]"} mci mode={MciPlayer.Mode} pos={MciPlayer.PositionMs}ms len={MciPlayer.LengthMs}ms");
}
if (!WantPlay || _loading || _waitingForInsert || Paused || Time.unscaledTime < _retryAt)
{
return;
}
if (!MciPlayer.IsOpen)
{
Next();
}
else if (_playbackActive && MciPlayer.Mode == "stopped")
{
bool flag = Time.unscaledTime - _playStartedAt > 2f;
if ((MciPlayer.LengthMs > 0 && MciPlayer.PositionMs >= MciPlayer.LengthMs - 1500) || (MciPlayer.LengthMs <= 0 && flag) || (flag && MciPlayer.PositionMs == 0))
{
_playbackActive = false;
MelonLogger.Msg($"{"[CustomMusic]"} track reached end (pos={MciPlayer.PositionMs}ms, len={MciPlayer.LengthMs}ms), moving to next");
OnTrackFinished();
}
}
}
public override void OnApplicationQuit()
{
MciPlayer.Close();
}
private static void UpdateFade()
{
_fade = Mathf.MoveTowards(_fade, _fadeTarget, Time.unscaledDeltaTime / Mathf.Max(0.01f, _fadeTime));
if (MciPlayer.IsOpen && !(Time.unscaledTime < _nextVolume))
{
_nextVolume = Time.unscaledTime + 0.05f;
MciPlayer.SetVolume(Mathf.Clamp01(Cfg.Volume.Value) * MusicPanel.GameMusicVolume * _fade);
}
}
public override void OnSceneWasInitialized(int buildIndex, string sceneName)
{
MelonLogger.Msg("[CustomMusic] scene '" + sceneName + "'");
if (!_booted)
{
_booted = true;
if (TrackLibrary.Files.Count > 0)
{
int idx = (Cfg.Shuffle.Value ? Rng.Next(TrackLibrary.Files.Count) : 0);
if (Cfg.Autoplay.Value)
{
WantPlay = true;
MelonCoroutines.Start(LoadAndPlay(idx, startPlaying: true));
}
else
{
WantPlay = false;
Paused = true;
MelonCoroutines.Start(LoadAndPlay(idx, startPlaying: false));
}
}
}
if (WantPlay && !_loading && !MciPlayer.IsOpen)
{
Next();
}
if (!_uiProbed)
{
MelonCoroutines.Start(ProbeLater(6f));
}
}
private static IEnumerator ProbeLater(float delay)
{
yield return (object)new WaitForSecondsRealtime(delay);
_uiProbed = true;
MusicPanel.Ensure();
}
public static void Next()
{
int num = PickNext();
if (num >= 0)
{
SwitchTrack(num);
}
}
public static void Prev()
{
int num = PickPrev();
if (num >= 0)
{
SwitchTrack(num);
}
}
public static void SelectTrack(int idx)
{
if (idx >= 0 && idx < TrackLibrary.Files.Count)
{
SwitchTrack(idx);
}
}
public static void SwitchTrack(int idx)
{
if (idx >= 0 && idx < TrackLibrary.Files.Count && !_loading)
{
bool num = (MciPlayer.IsOpen && MciPlayer.Mode == "playing") || _waitingForInsert || (WantPlay && !Paused);
_waitingForInsert = false;
_playbackActive = false;
WantPlay = false;
Paused = true;
if (MciPlayer.IsOpen)
{
MciPlayer.Pause();
}
if (num)
{
CassetteSfx.PlayEject();
}
MelonCoroutines.Start(LoadAndPlay(idx, startPlaying: false));
}
}
public static void TogglePlay()
{
if (!MciPlayer.IsOpen)
{
Resume();
}
else if (MciPlayer.Mode == "playing" || _waitingForInsert || (WantPlay && !Paused))
{
_waitingForInsert = false;
_playbackActive = false;
MciPlayer.Pause();
Paused = true;
WantPlay = false;
CassetteSfx.PlayEject();
MelonLogger.Msg("[CustomMusic] paused");
}
else
{
Paused = false;
WantPlay = true;
_waitingForInsert = true;
_playbackActive = false;
CassetteSfx.PlayInsert();
float insertDuration = CassetteSfx.GetInsertDuration();
MelonCoroutines.Start(DelayedPlay(insertDuration));
MelonLogger.Msg($"{"[CustomMusic]"} starting: waiting {insertDuration:F3}s for tape_insert sfx");
}
}
private static IEnumerator DelayedPlay(float delay)
{
_waitingForInsert = true;
_playbackActive = false;
yield return (object)new WaitForSecondsRealtime(delay);
_waitingForInsert = false;
if (WantPlay && !Paused && MciPlayer.IsOpen)
{
_fade = 0f;
_fadeTarget = 1f;
_fadeTime = Mathf.Max(0.01f, Cfg.Crossfade.Value);
_nextVolume = 0f;
MciPlayer.SetVolume(0f);
if (MciPlayer.Mode == "paused")
{
MciPlayer.Resume();
}
else
{
MciPlayer.Play();
}
_playStartedAt = Time.unscaledTime;
_playbackActive = true;
MelonLogger.Msg("[CustomMusic] track playing after tape insert finished");
}
}
private static void OnTrackFinished()
{
if (Cfg.Loop.Value == 1)
{
if (_index >= 0 && _index < TrackLibrary.Files.Count && !Bad.Contains(TrackLibrary.Files[_index]))
{
MelonCoroutines.Start(LoadAndPlay(_index, startPlaying: true));
return;
}
}
else if (Cfg.Loop.Value == 2 && !Cfg.Shuffle.Value && _index >= TrackLibrary.Files.Count - 1)
{
Stop();
MelonLogger.Msg("[CustomMusic] playlist finished (Loop OFF)");
return;
}
int num = PickNext();
if (num >= 0)
{
MelonCoroutines.Start(LoadAndPlay(num, startPlaying: true));
}
else
{
Stop();
}
}
public static void CycleLoop()
{
int num = (Cfg.Loop.Value + 1) % 3;
Cfg.Loop.Value = num;
MelonPreferences.Save();
MelonLogger.Msg("[CustomMusic] loop=" + LoopModeName(num));
}
public static string LoopModeName(int mode)
{
return mode switch
{
1 => "ONE",
2 => "OFF",
_ => "ALL",
};
}
public static void ToggleShuffle()
{
Cfg.Shuffle.Value = !Cfg.Shuffle.Value;
MelonPreferences.Save();
MelonLogger.Msg("[CustomMusic] shuffle=" + (Cfg.Shuffle.Value ? "on" : "off"));
}
public static void ReloadLibrary()
{
TrackLibrary.Scan();
Bad.Clear();
CassetteSfx.InvalidateCache();
MelonLogger.Msg($"{"[CustomMusic]"} rescan: {TrackLibrary.Files.Count} track(s)");
if (WantPlay && !_loading && !MciPlayer.IsOpen)
{
Next();
}
}
public static void Stop()
{
bool num = (MciPlayer.IsOpen && MciPlayer.Mode == "playing") || _waitingForInsert || (WantPlay && !Paused);
_waitingForInsert = false;
_playbackActive = false;
WantPlay = false;
Paused = true;
_fade = 0f;
_fadeTarget = 0f;
MciPlayer.Stop();
MciPlayer.Close();
if (num)
{
CassetteSfx.PlayEject();
}
Current = null;
CurrentName = null;
MelonLogger.Msg("[CustomMusic] stopped");
}
public static void Resume()
{
WantPlay = true;
Paused = false;
if (!MciPlayer.IsOpen)
{
if (!_loading)
{
int num = ((_index >= 0) ? _index : PickNext());
if (num >= 0)
{
PlayIndex(num);
}
}
}
else
{
_waitingForInsert = true;
_playbackActive = false;
CassetteSfx.PlayInsert();
float insertDuration = CassetteSfx.GetInsertDuration();
_playStartedAt = Time.unscaledTime;
MelonCoroutines.Start(DelayedPlay(insertDuration));
}
}
private static int PickPrev()
{
int count = TrackLibrary.Files.Count;
if (count == 0 || Bad.Count >= count)
{
return -1;
}
int num = ((_index < 0) ? count : _index);
for (int i = 1; i <= count; i++)
{
int num2 = ((num - i) % count + count) % count;
if (!Bad.Contains(TrackLibrary.Files[num2]))
{
return num2;
}
}
return -1;
}
private static int PickNext()
{
int count = TrackLibrary.Files.Count;
if (count == 0 || Bad.Count >= count)
{
return -1;
}
if (count == 1)
{
if (!Bad.Contains(TrackLibrary.Files[0]))
{
return 0;
}
return -1;
}
if (!Cfg.Shuffle.Value)
{
for (int i = 1; i <= count; i++)
{
int num = (_index + i) % count;
if (!Bad.Contains(TrackLibrary.Files[num]))
{
return num;
}
}
}
else
{
for (int j = 0; j < count * 2; j++)
{
int num2 = Rng.Next(count);
if ((num2 != _index || count <= 1) && !Bad.Contains(TrackLibrary.Files[num2]))
{
return num2;
}
}
}
for (int k = 0; k < count; k++)
{
if (!Bad.Contains(TrackLibrary.Files[k]))
{
return k;
}
}
return -1;
}
public static void PlayIndex(int idx)
{
if (idx >= 0 && idx < TrackLibrary.Files.Count && !_loading)
{
WantPlay = true;
Paused = false;
_waitingForInsert = true;
_playbackActive = false;
CassetteSfx.PlayInsert();
float insertDuration = CassetteSfx.GetInsertDuration();
MelonCoroutines.Start(LoadAndPlayDelayed(idx, insertDuration));
}
}
private static IEnumerator LoadAndPlayDelayed(int idx, float delay)
{
_loading = true;
_waitingForInsert = true;
_playbackActive = false;
float startTime = Time.unscaledTime;
string path = TrackLibrary.Files[idx];
MelonLogger.Msg($"{"[CustomMusic]"} loading '{Path.GetFileName(path)}' (waiting for tape_insert {delay:F2}s)");
MciPlayer.Close();
PreparedTrack prepared = null;
yield return TrackLibrary.Prepare(path, delegate(PreparedTrack p)
{
prepared = p;
});
_loading = false;
if (prepared == null)
{
_waitingForInsert = false;
Bad.Add(path);
yield break;
}
if (!MciPlayer.Open(prepared.PlayPath, prepared.DeviceType))
{
_waitingForInsert = false;
Bad.Add(path);
yield break;
}
_index = idx;
Current = prepared;
CurrentName = Path.GetFileNameWithoutExtension(path);
float num = Time.unscaledTime - startTime;
float num2 = delay - num;
if (num2 > 0f)
{
yield return (object)new WaitForSecondsRealtime(num2);
}
_waitingForInsert = false;
if (WantPlay && !Paused && MciPlayer.IsOpen)
{
_fade = 0f;
_fadeTarget = 1f;
_fadeTime = Mathf.Max(0.01f, Cfg.Crossfade.Value);
_nextVolume = 0f;
MciPlayer.SetVolume(0f);
MciPlayer.Play();
_playStartedAt = Time.unscaledTime;
_playbackActive = true;
MelonLogger.Msg($"{"[CustomMusic]"} playing '{CurrentName}' after tape insert ({(double)MciPlayer.LengthMs / 1000.0:F1}s)");
}
}
private static IEnumerator LoadAndPlay(int idx, bool startPlaying)
{
_loading = true;
_waitingForInsert = false;
_playbackActive = false;
string path = TrackLibrary.Files[idx];
MelonLogger.Msg($"{"[CustomMusic]"} loading '{Path.GetFileName(path)}' (startPlaying={startPlaying})");
MciPlayer.Close();
PreparedTrack prepared = null;
yield return TrackLibrary.Prepare(path, delegate(PreparedTrack p)
{
prepared = p;
});
_loading = false;
if (prepared == null)
{
Bad.Add(path);
if (Bad.Count >= TrackLibrary.Files.Count)
{
WantPlay = false;
MelonLogger.Warning($"{"[CustomMusic]"} all {TrackLibrary.Files.Count} track(s) failed to load - playback stopped");
}
else
{
MelonLogger.Warning("[CustomMusic] '" + Path.GetFileName(path) + "' skipped, trying next in 0.75s");
_retryAt = Time.unscaledTime + 0.75f;
}
yield break;
}
if (!MciPlayer.Open(prepared.PlayPath, prepared.DeviceType))
{
Bad.Add(path);
if (Bad.Count >= TrackLibrary.Files.Count)
{
WantPlay = false;
MelonLogger.Warning($"{"[CustomMusic]"} all {TrackLibrary.Files.Count} track(s) failed to play - playback stopped");
}
else
{
MelonLogger.Warning("[CustomMusic] '" + Path.GetFileName(path) + "' skipped, trying next in 0.75s");
_retryAt = Time.unscaledTime + 0.75f;
}
yield break;
}
_index = idx;
Current = prepared;
CurrentName = Path.GetFileNameWithoutExtension(path);
if (startPlaying)
{
Paused = false;
WantPlay = true;
_fade = 0f;
_fadeTarget = 1f;
_fadeTime = Mathf.Max(0.01f, Cfg.Crossfade.Value);
_nextVolume = 0f;
MciPlayer.SetVolume(0f);
MciPlayer.Play();
_playStartedAt = Time.unscaledTime;
_playbackActive = true;
MelonLogger.Msg($"{"[CustomMusic]"} playing '{CurrentName}' ({(double)MciPlayer.LengthMs / 1000.0:F1}s, {MciPlayer.DeviceType})");
}
else
{
Paused = true;
WantPlay = false;
_playbackActive = false;
_fade = 1f;
_fadeTarget = 1f;
MciPlayer.SetVolume(Mathf.Clamp01(Cfg.Volume.Value) * MusicPanel.GameMusicVolume);
MciPlayer.Stop();
MelonLogger.Msg($"{"[CustomMusic]"} loaded '{CurrentName}' on pause ({(double)MciPlayer.LengthMs / 1000.0:F1}s)");
}
}
}
internal static class MciPlayer
{
private const string Tag = "[CustomMusic]";
private const string Alias = "cm_track";
private static int _lastVolume = -1;
private static float _nextPoll;
public static bool IsOpen { get; private set; }
public static string CurrentPath { get; private set; }
public static string DeviceType { get; private set; }
public static int LengthMs { get; private set; }
public static int PositionMs { get; private set; }
public static string Mode { get; private set; } = "stopped";
public static bool VolumeSupported { get; private set; } = true;
[DllImport("winmm.dll", CharSet = CharSet.Unicode)]
private static extern int mciSendStringW(string command, StringBuilder ret, int retLen, IntPtr hwnd);
[DllImport("winmm.dll", CharSet = CharSet.Unicode)]
private static extern bool mciGetErrorStringW(int error, StringBuilder buf, int len);
[DllImport("winmm.dll")]
private static extern int waveOutSetVolume(IntPtr hwo, uint dwVolume);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern int GetShortPathNameW(string longPath, StringBuilder shortPath, int size);
public static bool Open(string path, string preferredType)
{
Close();
if (string.IsNullOrEmpty(path) || !File.Exists(path))
{
MelonLogger.Warning("[CustomMusic] MCI: file not found: " + path);
return false;
}
string text = MciPath(path);
string text2 = "\"" + text + "\"";
string text3 = (string.IsNullOrEmpty(preferredType) ? "waveaudio" : preferredType);
int num = Send($"open {text2} type {text3} alias {"cm_track"}");
string value = ((num == 0) ? null : Describe(num));
if (num != 0 && text3 != "waveaudio")
{
Send("close cm_track");
int num2 = Send("open " + text2 + " type waveaudio alias cm_track");
if (num2 == 0)
{
num = 0;
text3 = "waveaudio";
}
else
{
value = Describe(num2);
}
}
if (num != 0 && text3 != "mpegvideo")
{
Send("close cm_track");
int num3 = Send("open " + text2 + " type mpegvideo alias cm_track");
if (num3 == 0)
{
num = 0;
text3 = "mpegvideo";
}
else
{
value = Describe(num3);
}
}
if (num != 0)
{
Send("close cm_track");
int num4 = Send("open " + text2 + " alias cm_track");
if (num4 == 0)
{
num = 0;
text3 = "auto";
}
else
{
value = Describe(num4);
}
}
if (num != 0)
{
MelonLogger.Warning($"{"[CustomMusic]"} MCI open failed for '{Path.GetFileName(path)}': {value}");
return false;
}
IsOpen = true;
CurrentPath = path;
DeviceType = text3;
Mode = "stopped";
PositionMs = 0;
LengthMs = 0;
VolumeSupported = true;
_lastVolume = -1;
_nextPoll = 0f;
Send("set cm_track time format milliseconds");
LengthMs = ParseInt(SendWithRet("status cm_track length"));
MelonLogger.Msg($"{"[CustomMusic]"} MCI open '{Path.GetFileName(path)}' type={text3} length={LengthMs}ms");
return true;
}
public static void Play()
{
if (IsOpen)
{
if (Mode == "stopped")
{
Send("seek cm_track to start");
}
int num = Send("play cm_track");
if (num != 0)
{
MelonLogger.Warning("[CustomMusic] MCI play failed: " + Describe(num));
return;
}
Mode = "playing";
PositionMs = 0;
_nextPoll = 0f;
}
}
public static void Pause()
{
if (IsOpen && Send("pause cm_track") == 0)
{
Mode = "paused";
}
}
public static void Resume()
{
if (IsOpen)
{
int num = Send("resume cm_track");
if (num != 0)
{
num = Send("play cm_track");
}
if (num == 0)
{
Mode = "playing";
_nextPoll = 0f;
}
}
}
public static void Stop()
{
if (IsOpen)
{
Send("stop cm_track");
Send("seek cm_track to start");
Mode = "stopped";
PositionMs = 0;
}
}
public static void Close()
{
if (IsOpen)
{
Send("close cm_track");
}
IsOpen = false;
CurrentPath = null;
DeviceType = null;
LengthMs = 0;
PositionMs = 0;
Mode = "stopped";
_lastVolume = -1;
try
{
waveOutSetVolume(IntPtr.Zero, uint.MaxValue);
}
catch
{
}
}
public static void Poll()
{
if (!IsOpen || Time.unscaledTime < _nextPoll)
{
return;
}
_nextPoll = Time.unscaledTime + 0.2f;
if (Send("status cm_track mode", out var ret) != 0)
{
Mode = "stopped";
PositionMs = LengthMs;
return;
}
ret = (ret ?? "").Trim();
if (ret.Length > 0)
{
Mode = ret;
}
PositionMs = ParseInt(SendWithRet("status cm_track position"));
}
public static void SetVolume(float volume01)
{
if (!IsOpen)
{
return;
}
float num = Math.Clamp(volume01, 0f, 1f);
int num2 = (int)Math.Round(num * 1000f);
if (num2 == _lastVolume)
{
return;
}
_lastVolume = num2;
int num3 = Send($"setaudio {"cm_track"} volume to {num2}");
if (num3 != 0 && VolumeSupported && DeviceType == "mpegvideo")
{
VolumeSupported = false;
MelonLogger.Warning("[CustomMusic] MCI setaudio volume unavailable: " + Describe(num3));
}
uint num4 = (uint)Math.Round(num * 65535f);
uint dwVolume = (num4 & 0xFFFF) | (num4 << 16);
try
{
waveOutSetVolume(IntPtr.Zero, dwVolume);
}
catch
{
}
}
private static string MciPath(string path)
{
try
{
StringBuilder stringBuilder = new StringBuilder(1024);
int shortPathNameW = GetShortPathNameW(path, stringBuilder, stringBuilder.Capacity);
if (shortPathNameW > 0 && shortPathNameW < stringBuilder.Capacity && stringBuilder.Length > 0)
{
return stringBuilder.ToString();
}
}
catch
{
}
return path;
}
private static int Send(string command)
{
string ret;
return Send(command, out ret);
}
private static int Send(string command, out string ret)
{
StringBuilder stringBuilder = new StringBuilder(512);
int result = mciSendStringW(command, stringBuilder, stringBuilder.Capacity, IntPtr.Zero);
ret = stringBuilder.ToString();
return result;
}
private static string SendWithRet(string command)
{
if (Send(command, out var ret) != 0)
{
return "";
}
return (ret ?? "").Trim();
}
private static int ParseInt(string s)
{
if (!int.TryParse((s ?? "").Trim(), out var result))
{
return 0;
}
return result;
}
private static string Describe(int rc)
{
StringBuilder stringBuilder = new StringBuilder(256);
try
{
mciGetErrorStringW(rc, stringBuilder, stringBuilder.Capacity);
}
catch
{
}
string text = stringBuilder.ToString();
return $"MCI error {rc}{((text.Length > 0) ? (": " + text) : "")}";
}
}
internal static class Mp3Decoder
{
public static DecodedAudio Decode(string path)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Expected O, but got Unknown
MpegFile val = new MpegFile(path);
try
{
int channels = val.Channels;
int sampleRate = val.SampleRate;
if (channels <= 0 || sampleRate <= 0)
{
throw new InvalidDataException("bad MP3 stream");
}
FloatBuffer floatBuffer = new FloatBuffer(InitialCapacity(val.Length, channels));
float[] array = new float[channels * 4096];
int count;
while ((count = val.ReadSamples(array, 0, array.Length)) > 0)
{
floatBuffer.AddRange(array, count);
}
if (floatBuffer.Count == 0)
{
throw new InvalidDataException("MP3 decoded to zero samples");
}
return new DecodedAudio
{
Name = Path.GetFileNameWithoutExtension(path),
Channels = channels,
SampleRate = sampleRate,
Samples = floatBuffer.Finish()
};
}
finally
{
((IDisposable)val)?.Dispose();
}
}
private static int InitialCapacity(long length, int channels)
{
if (length <= 0)
{
return 1048576;
}
long num = length * channels;
if (num <= 0)
{
return 1048576;
}
if (num > 33554432)
{
num = 33554432L;
}
return (int)num;
}
}
public static class MusicPanel
{
private const string Tag = "[CustomMusic]";
private const int KindPlayPause = 0;
private const int KindNext = 1;
private const int KindPrev = 2;
private const int KindShuffle = 3;
private const int KindStop = 4;
private const int KindReload = 5;
private const int KindLoop = 6;
private const int KindToggleView = 7;
private const int KindPagePrev = 8;
private const int KindPageNext = 9;
private const int KindVolDown = 10;
private const int KindVolUp = 11;
private const int KindVolReset = 12;
private const int KindSlotBase = 20;
private const int SlotsPerPage = 5;
public static GameObject Panel;
private static MenuUIManager _ui;
private static Button _tabButton;
private static Image _tabImage;
private static Color _tabNormalColor = Color.white;
private static readonly Dictionary<int, int> ButtonKinds = new Dictionary<int, int>();
private static readonly HashSet<int> StockTabIds = new HashSet<int>();
private static readonly List<Image> StockTabImages = new List<Image>();
private static readonly Dictionary<int, float> LastClick = new Dictionary<int, float>();
private static int _tabId;
private static bool _listenerErrorLogged;
private static GameObject _playerView;
private static GameObject _playlistView;
private static bool _showingPlaylist;
private static int _playlistPage;
private static TextMeshProUGUI _trackText;
private static TextMeshProUGUI _statusText;
private static TextMeshProUGUI _shuffleText;
private static TextMeshProUGUI _loopText;
private static TextMeshProUGUI _playText;
private static TextMeshProUGUI _volText;
private static TextMeshProUGUI _viewPlaylistBtnText;
private static TextMeshProUGUI _playlistTitle;
private static readonly Button[] _slotButtons = (Button[])(object)new Button[5];
private static readonly TextMeshProUGUI[] _slotTexts = (TextMeshProUGUI[])(object)new TextMeshProUGUI[5];
private static Button _pagePrevBtn;
private static Button _pageNextBtn;
private static float _nextRefresh;
private static float _nextAttempt;
private static bool _buildErrorLogged;
private static GameObject _buildingPanel;
private static GameObject _buildingTab;
public static float GameMusicVolume { get; private set; } = 1f;
public static void UpdateGameVolume()
{
try
{
MenuUIManager ui = _ui;
Slider val = (((Object)(object)ui != (Object)null) ? ui.musicVolumeSlider : null);
if ((Object)(object)val != (Object)null)
{
float num = Mathf.Clamp01(val.normalizedValue);
if (Mathf.Abs(num - GameMusicVolume) > 0.01f)
{
MelonLogger.Msg($"{"[CustomMusic]"} game music slider: {num:F2}");
}
GameMusicVolume = num;
}
}
catch
{
}
}
public static void Ensure()
{
if ((Object)(object)Panel != (Object)null || Time.unscaledTime < _nextAttempt)
{
return;
}
MenuUIManager val = UiProbe.FindMenu();
if ((Object)(object)val == (Object)null || (Object)(object)val.settingsTab == (Object)null || val.settingsTabs == null || ((Il2CppArrayBase<GameObject>)(object)val.settingsTabs).Length == 0 || val.settingsTabButtons == null || ((Il2CppArrayBase<Button>)(object)val.settingsTabButtons).Length == 0)
{
return;
}
try
{
Build(val);
_buildErrorLogged = false;
}
catch (Exception value)
{
DestroyPartial();
Panel = null;
_nextAttempt = Time.unscaledTime + 10f;
if (!_buildErrorLogged)
{
_buildErrorLogged = true;
MelonLogger.Error($"{"[CustomMusic]"} settings plate build failed: {value}");
}
}
}
public static void Tick()
{
if (!((Object)(object)Panel == (Object)null) && Panel.activeInHierarchy)
{
if (AnyStockPanelActive())
{
Hide();
}
else if (!(Time.unscaledTime < _nextRefresh))
{
_nextRefresh = Time.unscaledTime + 0.25f;
Refresh();
}
}
}
public static void Show()
{
//IL_006f: Unknown result type (might be due to invalid IL or missing references)
//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)Panel == (Object)null || (Object)(object)_ui == (Object)null)
{
return;
}
Il2CppReferenceArray<GameObject> settingsTabs = _ui.settingsTabs;
if (settingsTabs != null)
{
for (int i = 0; i < ((Il2CppArrayBase<GameObject>)(object)settingsTabs).Length; i++)
{
GameObject val = ((Il2CppArrayBase<GameObject>)(object)settingsTabs)[i];
if ((Object)(object)val != (Object)null)
{
val.SetActive(false);
}
}
}
for (int j = 0; j < StockTabImages.Count; j++)
{
Image val2 = StockTabImages[j];
if ((Object)(object)val2 != (Object)null)
{
((Graphic)val2).color = _tabNormalColor;
}
}
_showingPlaylist = false;
UpdateViewVisibility();
Panel.SetActive(true);
if ((Object)(object)_tabImage != (Object)null)
{
((Graphic)_tabImage).color = _ui.activeColor;
}
Refresh();
}
public static void Hide()
{
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)Panel != (Object)null)
{
Panel.SetActive(false);
}
if ((Object)(object)_tabImage != (Object)null)
{
((Graphic)_tabImage).color = _tabNormalColor;
}
}
public static void OnButtonClicked(Button btn)
{
if ((Object)(object)btn == (Object)null || (Object)(object)Panel == (Object)null)
{
return;
}
GameObject gameObject = ((Component)btn).gameObject;
if ((Object)(object)gameObject == (Object)null)
{
return;
}
int instanceID = ((Object)gameObject).GetInstanceID();
if (!LastClick.TryGetValue(instanceID, out var value) || !(Time.unscaledTime - value < 0.35f))
{
LastClick[instanceID] = Time.unscaledTime;
int value2;
if (instanceID == _tabId)
{
Show();
}
else if (ButtonKinds.TryGetValue(instanceID, out value2))
{
Dispatch(value2);
}
else if (StockTabIds.Contains(instanceID))
{
Hide();
}
}
}
private static void UpdateViewVisibility()
{
if ((Object)(object)_playerView != (Object)null)
{
_playerView.SetActive(!_showingPlaylist);
}
if ((Object)(object)_playlistView != (Object)null)
{
_playlistView.SetActive(_showingPlaylist);
}
}
private static void Dispatch(int kind)
{
switch (kind)
{
case 0:
CustomMusicMod.TogglePlay();
break;
case 1:
CustomMusicMod.Next();
break;
case 2:
CustomMusicMod.Prev();
break;
case 3:
CustomMusicMod.ToggleShuffle();
break;
case 6:
CustomMusicMod.CycleLoop();
break;
case 4:
CustomMusicMod.Stop();
break;
case 5:
{
CustomMusicMod.ReloadLibrary();
int num3 = Math.Max(0, (TrackLibrary.Files.Count - 1) / 5);
if (_playlistPage > num3)
{
_playlistPage = num3;
}
break;
}
case 7:
_showingPlaylist = !_showingPlaylist;
UpdateViewVisibility();
break;
case 8:
if (_playlistPage > 0)
{
_playlistPage--;
}
break;
case 9:
{
int num4 = Math.Max(0, (TrackLibrary.Files.Count - 1) / 5);
if (_playlistPage < num4)
{
_playlistPage++;
}
break;
}
case 10:
Cfg.Volume.Value = Mathf.Clamp(Cfg.Volume.Value - 0.1f, 0f, 1f);
MelonPreferences.Save();
MelonLogger.Msg($"{"[CustomMusic]"} volume={Mathf.RoundToInt(Cfg.Volume.Value * 100f)}%");
break;
case 11:
Cfg.Volume.Value = Mathf.Clamp(Cfg.Volume.Value + 0.1f, 0f, 1f);
MelonPreferences.Save();
MelonLogger.Msg($"{"[CustomMusic]"} volume={Mathf.RoundToInt(Cfg.Volume.Value * 100f)}%");
break;
case 12:
Cfg.Volume.Value = ((Cfg.Volume.Value > 0.05f) ? 0f : 1f);
MelonPreferences.Save();
MelonLogger.Msg($"{"[CustomMusic]"} volume={Mathf.RoundToInt(Cfg.Volume.Value * 100f)}%");
break;
case 20:
case 21:
case 22:
case 23:
case 24:
{
int num = kind - 20;
int num2 = _playlistPage * 5 + num;
if (num2 >= 0 && num2 < TrackLibrary.Files.Count)
{
CustomMusicMod.SelectTrack(num2);
}
break;
}
}
Refresh();
}
private static bool AnyStockPanelActive()
{
Il2CppReferenceArray<GameObject> val = (((Object)(object)_ui != (Object)null) ? _ui.settingsTabs : null);
if (val == null)
{
return false;
}
for (int i = 0; i < ((Il2CppArrayBase<GameObject>)(object)val).Length; i++)
{
GameObject val2 = ((Il2CppArrayBase<GameObject>)(object)val)[i];
if ((Object)(object)val2 != (Object)null && val2.activeSelf)
{
return true;
}
}
return false;
}
private static void Build(MenuUIManager ui)
{
//IL_008f: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
//IL_00be: Unknown result type (might be due to invalid IL or missing references)
//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
//IL_01da: Unknown result type (might be due to invalid IL or missing references)
//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
//IL_0204: Unknown result type (might be due to invalid IL or missing references)
//IL_0287: Unknown result type (might be due to invalid IL or missing references)
//IL_0292: Unknown result type (might be due to invalid IL or missing references)
//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
//IL_0402: Unknown result type (might be due to invalid IL or missing references)
//IL_0411: Unknown result type (might be due to invalid IL or missing references)
//IL_0439: Unknown result type (might be due to invalid IL or missing references)
//IL_0448: Unknown result type (might be due to invalid IL or missing references)
//IL_02f7: Unknown result type (might be due to invalid IL or missing references)
//IL_0306: Unknown result type (might be due to invalid IL or missing references)
//IL_0338: Unknown result type (might be due to invalid IL or missing references)
//IL_0347: Unknown result type (might be due to invalid IL or missing references)
//IL_037d: Unknown result type (might be due to invalid IL or missing references)
//IL_038c: Unknown result type (might be due to invalid IL or missing references)
//IL_03c2: Unknown result type (might be due to invalid IL or missing references)
//IL_03d1: Unknown result type (might be due to invalid IL or missing references)
//IL_048b: Unknown result type (might be due to invalid IL or missing references)
//IL_049a: Unknown result type (might be due to invalid IL or missing references)
//IL_04c2: Unknown result type (might be due to invalid IL or missing references)
//IL_04d1: Unknown result type (might be due to invalid IL or missing references)
//IL_0514: Unknown result type (might be due to invalid IL or missing references)
//IL_0523: Unknown result type (might be due to invalid IL or missing references)
//IL_0566: Unknown result type (might be due to invalid IL or missing references)
//IL_0575: Unknown result type (might be due to invalid IL or missing references)
//IL_059e: Unknown result type (might be due to invalid IL or missing references)
//IL_05ad: Unknown result type (might be due to invalid IL or missing references)
//IL_05f1: Unknown result type (might be due to invalid IL or missing references)
//IL_0600: Unknown result type (might be due to invalid IL or missing references)
//IL_0629: Unknown result type (might be due to invalid IL or missing references)
//IL_0638: Unknown result type (might be due to invalid IL or missing references)
//IL_0660: Unknown result type (might be due to invalid IL or missing references)
//IL_066f: Unknown result type (might be due to invalid IL or missing references)
//IL_0697: Unknown result type (might be due to invalid IL or missing references)
//IL_06a6: Unknown result type (might be due to invalid IL or missing references)
//IL_06f7: Unknown result type (might be due to invalid IL or missing references)
//IL_0706: Unknown result type (might be due to invalid IL or missing references)
//IL_073c: Unknown result type (might be due to invalid IL or missing references)
//IL_074b: Unknown result type (might be due to invalid IL or missing references)
//IL_07da: 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_0882: Unknown result type (might be due to invalid IL or missing references)
//IL_0891: Unknown result type (might be due to invalid IL or missing references)
//IL_08bd: Unknown result type (might be due to invalid IL or missing references)
//IL_08cc: Unknown result type (might be due to invalid IL or missing references)
//IL_08f4: Unknown result type (might be due to invalid IL or missing references)
//IL_0903: Unknown result type (might be due to invalid IL or missing references)
//IL_0844: Unknown result type (might be due to invalid IL or missing references)
//IL_0a41: Unknown result type (might be due to invalid IL or missing references)
//IL_0a50: Unknown result type (might be due to invalid IL or missing references)
//IL_095b: Unknown result type (might be due to invalid IL or missing references)
//IL_097b: Unknown result type (might be due to invalid IL or missing references)
//IL_0aa5: Unknown result type (might be due to invalid IL or missing references)
//IL_0a08: Unknown result type (might be due to invalid IL or missing references)
//IL_0a0d: Unknown result type (might be due to invalid IL or missing references)
_ui = ui;
GameObject settingsTab = ui.settingsTab;
GameObject gameObject = ((Il2CppArrayBase<GameObject>)(object)ui.settingsTabs)[0].gameObject;
GameObject gameObject2 = ((Component)((Il2CppArrayBase<Button>)(object)ui.settingsTabButtons)[0]).gameObject;
ButtonKinds.Clear();
StockTabIds.Clear();
StockTabImages.Clear();
LastClick.Clear();
_tabId = 0;
GameObject val = (_buildingPanel = Object.Instantiate<GameObject>(gameObject, settingsTab.transform, false));
((Object)val).name = "CustomMusicSettings";
RectTransform component = gameObject.GetComponent<RectTransform>();
RectTransform component2 = val.GetComponent<RectTransform>();
component2.anchorMin = component.anchorMin;
component2.anchorMax = component.anchorMax;
component2.pivot = component.pivot;
component2.sizeDelta = new Vector2(340.16f, 433.31f);
component2.anchoredPosition = new Vector2(-2.98f, 1.32f);
for (int num = val.transform.childCount - 1; num >= 0; num--)
{
Object.DestroyImmediate((Object)(object)((Component)val.transform.GetChild(num)).gameObject);
}
Transform val2 = settingsTab.transform.Find("SettingsLabel/SettingsText");
GameObject val3 = (((Object)(object)val2 != (Object)null) ? ((Component)val2).gameObject : null);
if ((Object)(object)val3 == (Object)null)
{
TextMeshProUGUI componentInChildren = settingsTab.GetComponentInChildren<TextMeshProUGUI>(true);
if ((Object)(object)componentInChildren != (Object)null)
{
val3 = ((Component)componentInChildren).gameObject;
}
}
GameObject val4 = Object.Instantiate<GameObject>(gameObject, val.transform, false);
((Object)val4).name = "PlayerView";
for (int num2 = val4.transform.childCount - 1; num2 >= 0; num2--)
{
Object.DestroyImmediate((Object)(object)((Component)val4.transform.GetChild(num2)).gameObject);
}
Image component3 = val4.GetComponent<Image>();
if ((Object)(object)component3 != (Object)null)
{
Object.DestroyImmediate((Object)(object)component3);
}
RectTransform component4 = val4.GetComponent<RectTransform>();
component4.anchorMin = Vector2.zero;
component4.anchorMax = Vector2.one;
component4.pivot = new Vector2(0.5f, 0.5f);
component4.sizeDelta = Vector2.zero;
component4.anchoredPosition = Vector2.zero;
_playerView = val4;
GameObject val5 = Object.Instantiate<GameObject>(gameObject, val.transform, false);
((Object)val5).name = "PlaylistView";
for (int num3 = val5.transform.childCount - 1; num3 >= 0; num3--)
{
Object.DestroyImmediate((Object)(object)((Component)val5.transform.GetChild(num3)).gameObject);
}
Image component5 = val5.GetComponent<Image>();
if ((Object)(object)component5 != (Object)null)
{
Object.DestroyImmediate((Object)(object)component5);
}
RectTransform component6 = val5.GetComponent<RectTransform>();
component6.anchorMin = Vector2.zero;
component6.anchorMax = Vector2.one;
component6.pivot = new Vector2(0.5f, 0.5f);
component6.sizeDelta = Vector2.zero;
component6.anchoredPosition = Vector2.zero;
_playlistView = val5;
if ((Object)(object)val3 != (Object)null)
{
CreateLabel(val4.transform, val3, "CustomMusicTitle", "Custom Music", new Vector2(0f, 160f), new Vector2(320f, 32f), 24f, (TextAlignmentOptions)514);
_trackText = CreateLabel(val4.transform, val3, "TrackLabel", "Track: —", new Vector2(0f, 126f), new Vector2(320f, 30f), 17f, (TextAlignmentOptions)514);
_statusText = CreateLabel(val4.transform, val3, "StatusLabel", "Stopped", new Vector2(0f, 96f), new Vector2(320f, 26f), 14f, (TextAlignmentOptions)514);
CreateLabel(val4.transform, val3, "HintLabel", "UserData\\CustomMusic (mp3 / ogg / wav)", new Vector2(0f, -182f), new Vector2(330f, 26f), 11f, (TextAlignmentOptions)514);
}
CreateButton(val4.transform, gameObject2, "PrevButton", "<< Prev", new Vector2(-105f, 50f), new Vector2(90f, 40f), 2);
Button val6 = CreateButton(val4.transform, gameObject2, "PlayPauseButton", "Play", new Vector2(0f, 50f), new Vector2(100f, 40f), 0);
_playText = (((Object)(object)val6 != (Object)null) ? ((Component)val6).GetComponentInChildren<TextMeshProUGUI>(true) : null);
CreateButton(val4.transform, gameObject2, "NextButton", "Next >>", new Vector2(105f, 50f), new Vector2(90f, 40f), 1);
Button val7 = CreateButton(val4.transform, gameObject2, "ShuffleButton", "Shuffle: ON", new Vector2(-80f, 4f), new Vector2(145f, 40f), 3);
_shuffleText = (((Object)(object)val7 != (Object)null) ? ((Component)val7).GetComponentInChildren<TextMeshProUGUI>(true) : null);
Button val8 = CreateButton(val4.transform, gameObject2, "LoopButton", "Loop: ALL", new Vector2(80f, 4f), new Vector2(145f, 40f), 6);
_loopText = (((Object)(object)val8 != (Object)null) ? ((Component)val8).GetComponentInChildren<TextMeshProUGUI>(true) : null);
CreateButton(val4.transform, gameObject2, "VolDownButton", "Vol -", new Vector2(-105f, -42f), new Vector2(90f, 38f), 10);
Button val9 = CreateButton(val4.transform, gameObject2, "VolButton", "Vol: 100%", new Vector2(0f, -42f), new Vector2(100f, 38f), 12);
_volText = (((Object)(object)val9 != (Object)null) ? ((Component)val9).GetComponentInChildren<TextMeshProUGUI>(true) : null);
CreateButton(val4.transform, gameObject2, "VolUpButton", "Vol +", new Vector2(105f, -42f), new Vector2(90f, 38f), 11);
CreateButton(val4.transform, gameObject2, "StopButton", "Stop", new Vector2(-80f, -88f), new Vector2(145f, 38f), 4);
CreateButton(val4.transform, gameObject2, "ReloadButton", "Reload folder", new Vector2(80f, -88f), new Vector2(145f, 38f), 5);
Button val10 = CreateButton(val4.transform, gameObject2, "ViewPlaylistButton", "Playlist >>", new Vector2(0f, -134f), new Vector2(295f, 42f), 7);
_viewPlaylistBtnText = (((Object)(object)val10 != (Object)null) ? ((Component)val10).GetComponentInChildren<TextMeshProUGUI>(true) : null);
if ((Object)(object)val3 != (Object)null)
{
_playlistTitle = CreateLabel(val5.transform, val3, "PlaylistTitle", "Playlist", new Vector2(0f, 165f), new Vector2(320f, 34f), 20f, (TextAlignmentOptions)514);
CreateLabel(val5.transform, val3, "PlaylistHint", "Click a track to play it immediately", new Vector2(0f, -192f), new Vector2(330f, 26f), 11f, (TextAlignmentOptions)514);
}
float[] array = new float[5] { 112f, 60f, 8f, -44f, -96f };
for (int i = 0; i < 5; i++)
{
Button val11 = CreateButton(val5.transform, gameObject2, $"SlotButton_{i}", $"Track {i + 1}", new Vector2(0f, array[i]), new Vector2(305f, 42f), 20 + i);
_slotButtons[i] = val11;
if ((Object)(object)val11 != (Object)null)
{
TextMeshProUGUI componentInChildren2 = ((Component)val11).GetComponentInChildren<TextMeshProUGUI>(true);
if ((Object)(object)componentInChildren2 != (Object)null)
{
((TMP_Text)componentInChildren2).alignment = (TextAlignmentOptions)513;
((TMP_Text)componentInChildren2).margin = new Vector4(12f, 0f, 12f, 0f);
}
_slotTexts[i] = componentInChildren2;
}
}
_pagePrevBtn = CreateButton(val5.transform, gameObject2, "PagePrevButton", "<< Page", new Vector2(-105f, -148f), new Vector2(90f, 42f), 8);
CreateButton(val5.transform, gameObject2, "BackToPlayerButton", "Player", new Vector2(0f, -148f), new Vector2(100f, 42f), 7);
_pageNextBtn = CreateButton(val5.transform, gameObject2, "PageNextButton", "Page >>", new Vector2(105f, -148f), new Vector2(90f, 42f), 9);
Il2CppReferenceArray<Button> settingsTabButtons = ui.settingsTabButtons;
for (int j = 0; j < ((Il2CppArrayBase<Button>)(object)settingsTabButtons).Length; j++)
{
Button val12 = ((Il2CppArrayBase<Button>)(object)settingsTabButtons)[j];
if (!((Object)(object)val12 == (Object)null))
{
RectTransform component7 = ((Component)val12).GetComponent<RectTransform>();
if ((Object)(object)component7 != (Object)null)
{
component7.sizeDelta = new Vector2(110f, 35.3f);
component7.anchoredPosition = new Vector2(-180f + (float)j * 115f, 194.55f);
}
TextMeshProUGUI componentInChildren3 = ((Component)val12).GetComponentInChildren<TextMeshProUGUI>(true);
if ((Object)(object)componentInChildren3 != (Object)null)
{
((TMP_Text)componentInChildren3).enableAutoSizing = true;
((TMP_Text)componentInChildren3).fontSizeMin = 9f;
((TMP_Text)componentInChildren3).fontSizeMax = 20f;
}
StockTabIds.Add(((Object)((Component)val12).gameObject).GetInstanceID());
Image component8 = ((Component)val12).GetComponent<Image>();
if ((Object)(object)component8 != (Object)null)
{
StockTabImages.Add(component8);
}
if (j == ((Il2CppArrayBase<Button>)(object)settingsTabButtons).Length - 1 && (Object)(object)component8 != (Object)null)
{
_tabNormalColor = ((Graphic)component8).color;
}
}
}
Button val13 = CreateButton(settingsTab.transform, gameObject2, "MusicTabButton", "Music", new Vector2(165f, 194.55f), new Vector2(110f, 35.3f), -1);
if ((Object)(object)val13 != (Object)null)
{
((Selectable)val13).transition = (Transition)0;
_tabButton = val13;
_tabId = ((Object)((Component)val13).gameObject).GetInstanceID();
_tabImage = ((Component)val13).GetComponent<Image>();
if ((Object)(object)_tabImage != (Object)null)
{
((Graphic)_tabImage).color = _tabNormalColor;
}
}
_buildingTab = (((Object)(object)val13 != (Object)null) ? ((Component)val13).gameObject : null);
_buildingPanel = null;
_buildingTab = null;
Panel = val;
_showingPlaylist = false;
UpdateViewVisibility();
val.SetActive(false);
MelonLogger.Msg("[CustomMusic] settings plate built under '" + ((Object)settingsTab).name + "'");
}
private static Button CreateButton(Transform parent, GameObject template, string name, string label, Vector2 pos, Vector2 size, int kind)
{
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0058: 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_0078: Unknown result type (might be due to invalid IL or missing references)
//IL_0080: Unknown result type (might be due to invalid IL or missing references)
//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
//IL_00bd: Expected O, but got Unknown
GameObject val = Object.Instantiate<GameObject>(template, parent, false);
if ((Object)(object)val == (Object)null)
{
return null;
}
((Object)val).name = name;
val.SetActive(true);
RectTransform component = val.GetComponent<RectTransform>();
if ((Object)(object)component != (Object)null)
{
component.anchorMin = new Vector2(0.5f, 0.5f);
component.anchorMax = new Vector2(0.5f, 0.5f);
component.pivot = new Vector2(0.5f, 0.5f);
component.sizeDelta = size;
component.anchoredPosition = pos;
}
Button btn = val.GetComponent<Button>();
if ((Object)(object)btn == (Object)null)
{
btn = val.AddComponent<Button>();
}
btn.onClick = new ButtonClickedEvent();
try
{
((UnityEvent)btn.onClick).AddListener(UnityAction.op_Implicit((Action)delegate
{
OnButtonClicked(btn);
}));
}
catch (Exception ex)
{
if (!_listenerErrorLogged)
{
_listenerErrorLogged = true;
MelonLogger.Warning("[CustomMusic] onClick listener unavailable (" + ex.Message + "); using Harmony clicks only");
}
}
TextMeshProUGUI componentInChildren = val.GetComponentInChildren<TextMeshProUGUI>(true);
if ((Object)(object)componentInChildren != (Object)null)
{
((TMP_Text)componentInChildren).text = label;
((TMP_Text)componentInChildren).alignment = (TextAlignmentOptions)514;
((TMP_Text)componentInChildren).enableAutoSizing = true;
((TMP_Text)componentInChildren).fontSizeMin = 10f;
((TMP_Text)componentInChildren).fontSizeMax = 20f;
((Graphic)componentInChildren).raycastTarget = false;
}
if (kind >= 0)
{
ButtonKinds[((Object)val).GetInstanceID()] = kind;
}
return btn;
}
private static TextMeshProUGUI CreateLabel(Transform parent, GameObject template, string name, string text, Vector2 pos, Vector2 size, float fontSize, TextAlignmentOptions align)
{
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
GameObject val = Object.Instantiate<GameObject>(template, parent, false);
if ((Object)(object)val == (Object)null)
{
return null;
}
((Object)val).name = name;
val.SetActive(true);
RectTransform component = val.GetComponent<RectTransform>();
if ((Object)(object)component != (Object)null)
{
component.anchorMin = new Vector2(0.5f, 0.5f);
component.anchorMax = new Vector2(0.5f, 0.5f);
component.pivot = new Vector2(0.5f, 0.5f);
component.sizeDelta = size;
component.anchoredPosition = pos;
}
TextMeshProUGUI component2 = val.GetComponent<TextMeshProUGUI>();
if ((Object)(object)component2 != (Object)null)
{
((TMP_Text)component2).text = text;
((TMP_Text)component2).fontSize = fontSize;
((TMP_Text)component2).alignment = align;
((TMP_Text)component2).enableAutoSizing = false;
((Graphic)component2).raycastTarget = false;
}
return component2;
}
private static void DestroyPartial()
{
if ((Object)(object)_buildingTab != (Object)null)
{
Object.DestroyImmediate((Object)(object)_buildingTab);
}
if ((Object)(object)_buildingPanel != (Object)null)
{
Object.DestroyImmediate((Object)(object)_buildingPanel);
}
_buildingTab = null;
_buildingPanel = null;
Panel = null;
_playerView = null;
_playlistView = null;
_tabButton = null;
_tabImage = null;
_trackText = null;
_statusText = null;
_shuffleText = null;
_loopText = null;
_playText = null;
_volText = null;
_viewPlaylistBtnText = null;
_playlistTitle = null;
_pagePrevBtn = null;
_pageNextBtn = null;
for (int i = 0; i < 5; i++)
{
_slotButtons[i] = null;
_slotTexts[i] = null;
}
ButtonKinds.Clear();
StockTabIds.Clear();
StockTabImages.Clear();
LastClick.Clear();
}
private static void Refresh()
{
//IL_05bd: Unknown result type (might be due to invalid IL or missing references)
//IL_05a7: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)_trackText != (Object)null)
{
PreparedTrack current = CustomMusicMod.Current;
int currentIndex = CustomMusicMod.CurrentIndex;
int count = TrackLibrary.Files.Count;
if (current != null && currentIndex >= 0 && currentIndex < count)
{
((TMP_Text)_trackText).text = $"[{currentIndex + 1}/{count}] {CustomMusicMod.CurrentName}";
}
else if (current != null)
{
((TMP_Text)_trackText).text = "Track: " + CustomMusicMod.CurrentName;
}
else
{
((TMP_Text)_trackText).text = "Track: —";
}
}
if ((Object)(object)_statusText != (Object)null)
{
if (!Cfg.Enabled.Value)
{
((TMP_Text)_statusText).text = "Mod disabled";
}
else if (TrackLibrary.Files.Count == 0)
{
((TMP_Text)_statusText).text = "No tracks in UserData\\CustomMusic";
}
else if (CustomMusicMod.IsLoading)
{
((TMP_Text)_statusText).text = "Loading...";
}
else if (CustomMusicMod.WantPlay && CustomMusicMod.IsPlaying)
{
int num = CustomMusicMod.PositionMs / 1000;
int num2 = CustomMusicMod.LengthMs / 1000;
string text = ((num2 > 0) ? $"{num / 60}:{num % 60:D2} / {num2 / 60}:{num2 % 60:D2}" : $"{num / 60}:{num % 60:D2}");
((TMP_Text)_statusText).text = "Playing " + text;
}
else if (CustomMusicMod.WantPlay && CustomMusicMod.Paused)
{
((TMP_Text)_statusText).text = "Paused";
}
else if (CustomMusicMod.WantPlay)
{
((TMP_Text)_statusText).text = "Starting...";
}
else
{
((TMP_Text)_statusText).text = "Stopped";
}
if (CustomMusicMod.BadCount > 0)
{
TextMeshProUGUI statusText = _statusText;
((TMP_Text)statusText).text = ((TMP_Text)statusText).text + $" (bad: {CustomMusicMod.BadCount})";
}
}
if ((Object)(object)_playText != (Object)null)
{
((TMP_Text)_playText).text = ((CustomMusicMod.IsPlaying || CustomMusicMod.IsWaitingForInsert) ? "Pause" : "Play");
}
if ((Object)(object)_volText != (Object)null)
{
((TMP_Text)_volText).text = $"Vol: {Mathf.RoundToInt(Cfg.Volume.Value * 100f)}%";
}
if ((Object)(object)_shuffleText != (Object)null)
{
((TMP_Text)_shuffleText).text = (Cfg.Shuffle.Value ? "Shuffle: ON" : "Shuffle: OFF");
}
if ((Object)(object)_loopText != (Object)null)
{
((TMP_Text)_loopText).text = "Loop: " + CustomMusicMod.LoopModeName(Cfg.Loop.Value);
}
if ((Object)(object)_viewPlaylistBtnText != (Object)null)
{
((TMP_Text)_viewPlaylistBtnText).text = $"Playlist ({TrackLibrary.Files.Count} tracks) >>";
}
if (!((Object)(object)_playlistTitle != (Object)null))
{
return;
}
int count2 = TrackLibrary.Files.Count;
int num3 = Math.Max(0, (count2 - 1) / 5);
if (_playlistPage > num3)
{
_playlistPage = num3;
}
if (_playlistPage < 0)
{
_playlistPage = 0;
}
((TMP_Text)_playlistTitle).text = ((count2 > 0) ? $"Playlist ({count2}) — Page {_playlistPage + 1}/{num3 + 1}" : "Playlist is empty");
for (int i = 0; i < 5; i++)
{
int num4 = _playlistPage * 5 + i;
Button val = _slotButtons[i];
TextMeshProUGUI val2 = _slotTexts[i];
if ((Object)(object)val == (Object)null)
{
continue;
}
if (num4 < count2)
{
((Component)val).gameObject.SetActive(true);
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(TrackLibrary.Files[num4]);
bool flag = num4 == CustomMusicMod.CurrentIndex;
if ((Object)(object)val2 != (Object)null)
{
((TMP_Text)val2).text = (flag ? $"▶ {num4 + 1}. {fileNameWithoutExtension}" : $" {num4 + 1}. {fileNameWithoutExtension}");
((Graphic)val2).color = (Color)(flag ? new Color(1f, 0.85f, 0.2f) : Color.white);
}
}
else
{
((Component)val).gameObject.SetActive(false);
}
}
if ((Object)(object)_pagePrevBtn != (Object)null)
{
((Selectable)_pagePrevBtn).interactable = _playlistPage > 0;
}
if ((Object)(object)_pageNextBtn != (Object)null)
{
((Selectable)_pageNextBtn).interactable = _playlistPage < num3;
}
}
}
internal static class OggDecoder
{
public static DecodedAudio Decode(string path)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Expected O, but got Unknown
VorbisReader val = new VorbisReader(path);
try
{
int channels = val.Channels;
int sampleRate = val.SampleRate;
if (channels <= 0 || sampleRate <= 0)
{
throw new InvalidDataException("bad OGG stream");
}
FloatBuffer floatBuffer = new FloatBuffer(InitialCapacity(val.TotalSamples, channels));
float[] array = new float[channels * 4096];
int count;
while ((count = val.ReadSamples(array, 0, array.Length)) > 0)
{
floatBuffer.AddRange(array, count);
}
if (floatBuffer.Count == 0)
{
throw new InvalidDataException("OGG decoded to zero samples");
}
return new DecodedAudio
{
Name = Path.GetFileNameWithoutExtension(path),
Channels = channels,
SampleRate = sampleRate,
Samples = floatBuffer.Finish()
};
}
finally
{
((IDisposable)val)?.Dispose();
}
}
private static int InitialCapacity(long totalSamples, int channels)
{
if (totalSamples <= 0)
{
return 1048576;
}
long num = totalSamples * channels;
if (num <= 0)
{
return 1048576;
}
if (num > 33554432)
{
num = 33554432L;
}
return (int)num;
}
}
internal static class TrackLibrary
{
private const string Tag = "[CustomMusic]";
public static string Root;
public static readonly List<string> Files = new List<string>();
public static void EnsureStarterFiles()
{
try
{
if (!string.IsNullOrEmpty(Root))
{
if (!Directory.Exists(Root))
{
Directory.CreateDirectory(Root);
}
if (!File.Exists(Path.Combine(Root, "README.txt")))
{
WriteReadme();
}
CassetteSfx.EnsureSfxFiles();
}
}
catch (Exception ex)
{
MelonLogger.Warning("[CustomMusic] EnsureStarterFiles error: " + ex.Message);
}
}
public static void Scan()
{
Files.Clear();
if (string.IsNullOrEmpty(Root) || !Directory.Exists(Root))
{
return;
}
string value = Path.Combine(Root, "sfx");
List<string> list = new List<string>();
HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
try
{
string[] files = Directory.GetFiles(Root, "*.*", SearchOption.AllDirectories);
foreach (string text in files)
{
if (text.StartsWith(value, StringComparison.OrdinalIgnoreCase) || text.IndexOf("CustomMusicCache", StringComparison.OrdinalIgnoreCase) >= 0)
{
continue;
}
string text2 = Path.GetExtension(text).ToLowerInvariant();
if (!(text2 != ".mp3") || !(text2 != ".ogg") || !(text2 != ".wav"))
{
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
if (!fileNameWithoutExtension.StartsWith("tape_", StringComparison.OrdinalIgnoreCase) && !fileNameWithoutExtension.StartsWith("sfx_", StringComparison.OrdinalIgnoreCase) && hashSet.Add(text))
{
list.Add(text);
}
}
}
}
catch (Exception ex)
{
MelonLogger.Warning($"{"[CustomMusic]"} folder scan error '{Root}': {ex.Message}");
}
list.Sort(CompareTracks);
Files.AddRange(list);
}
private static int CompareTracks(string a, string b)
{
int num = NaturalCompare(Path.GetFileNameWithoutExtension(a), Path.GetFileNameWithoutExtension(b));
if (num != 0)
{
return num;
}
return string.Compare(a, b, StringComparison.OrdinalIgnoreCase);
}
private static int NaturalCompare(string a, string b)
{
int i = 0;
int j = 0;
while (i < a.Length && j < b.Length)
{
if (char.IsDigit(a[i]) && char.IsDigit(b[j]))
{
int num = i;
int num2 = j;
for (; i < a.Length && char.IsDigit(a[i]); i++)
{
}
for (; j < b.Length && char.IsDigit(b[j]); j++)
{
}
string text = a.Substring(num, i - num).TrimStart('0');
string text2 = b.Substring(num2, j - num2).TrimStart('0');
if (text.Length != text2.Length)
{
return text.Length - text2.Length;
}
int num3 = string.CompareOrdinal(text, text2);
if (num3 != 0)
{
return num3;
}
}
else
{
int num4 = char.ToUpperInvariant(a[i]).CompareTo(char.ToUpperInvariant(b[j]));
if (num4 != 0)
{
return num4;
}
i++;
j++;
}
}
return a.Length - i - (b.Length - j);
}
public static IEnumerator Prepare(string path, Action<PreparedTrack> done)
{
Task<PreparedTrack> task = null;
try
{
task = Task.Run(() => AudioDecode.Prepare(path));
}
catch (Exception ex)
{
MelonLogger.Warning($"{"[CustomMusic]"} '{Path.GetFileName(path)}': prepare start failed: {ex.GetBaseException().Message}");
}
while (task != null && !task.IsCompleted)
{
yield return null;
}
PreparedTrack obj = null;
if (task != null)
{
if (task.IsFaulted)
{
Exception ex2 = ((task.Exception == null) ? null : task.Exception.GetBaseException());
MelonLogger.Warning($"{"[CustomMusic]"} '{Path.GetFileName(path)}': prepare failed: {ex2?.Message ?? "unknown"}");
}
else if (task.IsCanceled)
{
MelonLogger.Warning("[CustomMusic] '" + Path.GetFileName(path) + "': prepare canceled");
}
else
{
obj = task.Result;
}
}
done?.Invoke(obj);
}
private static void WriteReadme()
{
try
{
string contents = string.Join(Environment.NewLine, "CustomMusic - папка для своей музыки", "", "Кидай сюда .mp3, .ogg или .wav: " + Root, "Можно раскладывать по подпапкам - поиск рекурсивный, порядок по имени файла (2 раньше 10).", "", "Эта папка зависит от того, как запущена игра:", " Thunderstore Mod Manager -> ...\\profiles\\<профиль>\\UserData\\CustomMusic", " обычный запуск exe -> <папка игры>\\UserData\\CustomMusic", "", "Настройки мода: UserData\\MelonPreferences.cfg, категория CustomMusic.", "", "Панель управления музыкой в игре появится в меню настроек.");
File.WriteAllText(Path.Combine(Root, "README.txt"), contents);
}
catch
{
}
}
}
public static class UiProbe
{
private const string Tag = "[CustomMusic]";
private static readonly StringBuilder Sb = new StringBuilder();
public static bool Dump(string why)
{
//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
Sb.Clear();
Line($"=== UI dump ({why}) {DateTime.Now:yyyy-MM-dd HH:mm:ss} ===");
try
{
MenuUIManager val = FindMenu();
if ((Object)(object)val == (Object)null)
{
Line("MenuUIManager: not found in loaded scene");
Flush();
return false;
}
Line($"MenuUIManager on '{((Object)((Component)val).gameObject).name}' id={((Object)val).GetInstanceID()}");
Scene scene = ((Component)val).gameObject.scene;
Line("scene='" + ((Scene)(ref scene)).name + "'");
Line("canvasRectTransform=" + (((Object)(object)val.canvasRectTransform != (Object)null) ? ((Object)val.canvasRectTransform).name : "null"));
Line("");
Line("--- settingsTabs ---");
Il2CppReferenceArray<GameObject> settingsTabs = val.settingsTabs;
Line("count=" + ((settingsTabs == null) ? "null" : ((Il2CppArrayBase<GameObject>)(object)settingsTabs).Length.ToString()));
if (settingsTabs != null)
{
for (int i = 0; i < ((Il2CppArrayBase<GameObject>)(object)settingsTabs).Length; i++)
{
GameObject val2 = ((Il2CppArrayBase<GameObject>)(object)settingsTabs)[i];
if ((Object)(object)val2 == (Object)null)
{
Line($"[{i}] null");
continue;
}
Line($"[{i}] '{((Object)val2).name}' active={val2.activeSelf} parent='{ParentName(val2)}' sibling={val2.transform.GetSiblingIndex()}");
}
}
Line("");
Line("--- settingsTabButtons ---");
Il2CppReferenceArray<Button> settingsTabButtons = val.settingsTabButtons;
Line("count=" + ((settingsTabButtons == null) ? "null" : ((Il2CppArrayBase<Button>)(object)settingsTabButtons).Length.ToString()));
if (settingsTabButtons != null)
{
for (int j = 0; j < ((Il2CppArrayBase<Button>)(object)settingsTabButtons).Length; j++)
{
Button val3 = ((Il2CppArrayBase<Button>)(object)settingsTabButtons)[j];
if ((Object)(object)val3 == (Object)null)
{
Line($"[{j}] null");
continue;
}
Line($"[{j}] '{((Object)((Component)val3).gameObject).name}' active={((Component)val3).gameObject.activeSelf} interactable={((Selectable)val3).interactable} parent='{ParentName(((Component)val3).gameObject)}' sibling={((Component)val3).transform.GetSiblingIndex()}");
Line(" comps=" + CompNames(((Component)val3).gameObject));
DumpPersistentCalls(val3);
}
}
Line("");
Line("--- mainOptionsTab / settingsTab / cheatsTab ---");
DumpShort("mainOptionsTab", val.mainOptionsTab);
DumpShort("settingsTab", val.settingsTab);
DumpShort("cheatsTab", val.cheatsTab);
if (settingsTabButtons != null && ((Il2CppArrayBase<Button>)(object)settingsTabButtons).Length > 0 && (Object)(object)((Il2CppArrayBase<Button>)(object)settingsTabButtons)[0] != (Object)null)
{
Line("");
Line("--- button row parent hierarchy (depth 3) ---");
Transform parent = ((Component)((Il2CppArrayBase<Button>)(object)settingsTabButtons)[0]).transform.parent;
if ((Object)(object)parent != (Object)null)
{
DumpGo(((Component)parent).gameObject, 0, 3);
}
}
if (settingsTabs != null && ((Il2CppArrayBase<GameObject>)(object)settingsTabs).Length > 0 && (Object)(object)((Il2CppArrayBase<GameObject>)(object)settingsTabs)[0] != (Object)null)
{
Line("");
Line("--- settingsTabs[0] hierarchy (depth 5) ---");
DumpGo(((Il2CppArrayBase<GameObject>)(object)settingsTabs)[0], 0, 5);
}
Line("");
Line("--- MenuUIManager object hierarchy (depth 3) ---");
DumpGo(((Component)val).gameObject, 0, 3);
}
catch (Exception value)
{
Line($"DUMP FAILED: {value}");
}
Flush();
return true;
}
internal static MenuUIManager FindMenu()
{
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
MenuUIManager val = Object.FindObjectOfType<MenuUIManager>();
if ((Object)(object)val != (Object)null)
{
return val;
}
Il2CppArrayBase<MenuUIManager> val2 = Resources.FindObjectsOfTypeAll<MenuUIManager>();
if (val2 == null)
{
return null;
}
foreach (MenuUIManager item in val2)
{
if (!((Object)(object)item == (Object)null))
{
Scene scene = ((Component)item).gameObject.scene;
if (((Scene)(ref scene)).IsValid() && !string.IsNullOrEmpty(((Scene)(ref scene)).name))
{
return item;
}
}
}
return null;
}
private static void DumpShort(string label, GameObject go)
{
if ((Object)(object)go == (Object)null)
{
Line(label + ": null");
return;
}
Line($"{label}: '{((Object)go).name}' active={go.activeSelf} parent='{ParentName(go)}' sibling={go.transform.GetSiblingIndex()}");
Line(" comps=" + CompNames(go));
}
private static void DumpPersistentCalls(Button b)
{
try
{
int persistentEventCount = ((UnityEventBase)b.onClick).GetPersistentEventCount();
Line($" onClick persistent calls: {persistentEventCount}");
for (int i = 0; i < persistentEventCount; i++)
{
Object persistentTarget = ((UnityEventBase)b.onClick).GetPersistentTarget(i);
string persistentMethodName = ((UnityEventBase)b.onClick).GetPersistentMethodName(i);
string value = ((persistentTarget == (Object)null) ? "null" : (persistentTarget.name + " (" + ((object)persistentTarget).GetType().Name + ")"));
Line($" [{i}] {value} -> {persistentMethodName}");
}
}
catch (Exception ex)
{
Line(" onClick dump failed: " + ex.Message);
}
}
private static void DumpGo(GameObject go, int depth, int maxDepth)
{
//IL_0044: 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)
if ((Object)(object)go == (Object)null)
{
return;
}
string value = new string(' ', depth * 2);
RectTransform component = go.GetComponent<RectTransform>();
string value2 = "";
if ((Object)(object)component != (Object)null)
{
value2 = $" size={component.sizeDelta} pos={component.anchoredPosition}";
}
Line($"{value}{((Object)go).name}{(go.activeSelf ? "" : " [inactive]")}{value2} comps={CompNames(go)}");
if (depth < maxDepth)
{
Transform transform = go.transform;
for (int i = 0; i < transform.childCount; i++)
{
DumpGo(((Component)transform.GetChild(i)).gameObject, depth + 1, maxDepth);
}
}
}
private static string CompNames(GameObject go)
{
try
{
Il2CppArrayBase<Component> components = go.GetComponents<Component>();
List<string> list = new List<string>();
foreach (Component item in components)
{
if ((Object)(object)item == (Object)null)
{
list.Add("<null>");
continue;
}
try
{
list.Add(((MemberInfo)((Object)item).GetIl2CppType()).Name);
}
catch
{
list.Add(((object)item).GetType().Name);
}
}
return string.Join(",", list);
}
catch (Exception ex)
{
return "<comps failed: " + ex.Message + ">";
}
}
private static string ParentName(GameObject go)
{
Transform parent = go.transform.parent;
if (!((Object)(object)parent == (Object)null))
{
return ((Object)((Component)parent).gameObject).name;
}
return "<root>";
}
private static void Line(string s)
{
Sb.AppendLine(s);
}
private static void Flush()
{
try
{
MelonLogger.Msg($"{"[CustomMusic]"} UI dump:{Environment.NewLine}{Sb}");
}
catch (Exception ex)
{
MelonLogger.Error("[CustomMusic] UI dump failed: " + ex.Message);
}
}
}
internal static class WavDecoder
{
public static DecodedAudio Decode(string path)
{
byte[] array = File.ReadAllBytes(path);
if (array.Length < 12 || FourCC(array, 0) != "RIFF" || FourCC(array, 8) != "WAVE")
{
throw new InvalidDataException("not a RIFF/WAVE file");
}
int num = -1;
int num2 = 0;
int num3 = -1;
int num4 = 0;
int num5 = 12;
while (num5 + 8 <= array.Length)
{
string text = FourCC(array, num5);
int num6 = I32(array, num5 + 4);
num5 += 8;
if (num6 < 0)
{
throw new InvalidDataException("negative chunk size");
}
if ((long)num5 + (long)num6 > array.Length)
{
if (!(text == "data"))
{
break;
}
num6 = array.Length - num5;
}
if (text == "fmt ")
{
num = num5;
num2 = num6;
}
else if (text == "data")
{
num3 = num5;
num4 = num6;
break;
}
num5 += num6;
if ((num6 & 1) != 0)
{
num5++;
}
}
if (num < 0 || num2 < 16)
{
throw new InvalidDataException("missing fmt chunk");
}
if (num3 < 0 || num4 <= 0)
{
throw new InvalidDataException("missing data chunk");
}
ushort num7 = U16(array, num);
ushort num8 = U16(array, num + 2);
int num9 = I32(array, num + 4);
int num10 = U16(array, num + 12);
int num11 = U16(array, num + 14);
if (num7 == 65534 && num2 >= 40)
{
num7 = U16(array, num + 24);
}
if (num8 == 0 || num9 <= 0)
{
throw new InvalidDataException("bad fmt chunk");
}
if (num11 == 0)
{
throw new InvalidDataException("zero bits per sample");
}
int num12 = (num11 + 7) / 8;
if (num10 <= 0)
{
num10 = num8 * num12;
}
if (num10 < num8 * num12)
{
throw new InvalidDataException("bad block align");
}
int num13 = num4 / num10;
if (num13 <= 0)
{
throw new InvalidDataException("no audio frames");
}
float[] array2 = new float[num13 * num8];
switch (num7)
{
case 1:
switch (num11)
{
case 8:
Decode8(array, num3, num4, num13, num8, num10, array2);
break;
case 16:
Decode16(array, num3, num13, num8, num10, array2);
break;
case 24:
Decode24(array, num3, num13, num8, num10, array2);
break;
case 32:
Decode32(array, num3, num13, num8, num10, array2);
break;
default:
throw new InvalidDataException("unsupported PCM bit depth " + num11);
}
break;
case 3:
switch (num11)
{
case 32:
DecodeFloat32(array, num3, num13, num8, num10, array2);
break;
case 64:
DecodeFloat64(array, num3, num13, num8, num10, array2);
break;
default:
throw new InvalidDataException("unsupported float bit depth " + num11);
}
break;
default:
throw new InvalidDataException("unsupported WAV format tag " + num7);
}
return new DecodedAudio
{
Name = Path.GetFileNameWithoutExtension(path),
Channels = num8,
SampleRate = num9,
Samples = array2
};
}
public static bool IsMciFriendly(string path)
{
try
{
using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
using BinaryReader binaryReader = new BinaryReader(fileStream);
if (fileStream.Length < 44)
{
return false;
}
if (ReadFourCC(binaryReader) != "RIFF")
{
return false;
}
binaryReader.ReadInt32();
if (ReadFourCC(binaryReader) != "WAVE")
{
return false;
}
while (fileStream.Position + 8 <= fileStream.Length)
{
string text = ReadFourCC(binaryReader);
int num = binaryReader.ReadInt32();
if (num < 0)
{
return false;
}
long val = fileStream.Position + num + (num & 1);
if (text == "fmt ")
{
if (num < 16)
{
return false;
}
short num2 = binaryReader.ReadInt16();
short num3 = binaryReader.ReadInt16();
int num4 = binaryReader.ReadInt32();
binaryReader.ReadInt32();
binaryReader.ReadInt16();
short num5 = binaryReader.ReadInt16();
return num2 == 1 && (num5 == 8 || num5 == 16) && num3 >= 1 && num3 <= 2 && num4 >= 8000 && num4 <= 48000;
}
if (text == "data")
{
return false;
}
fileStream.Position = Math.Min(val, fileStream.Length);
}
}
catch
{
}
return false;
}
private static string ReadFourCC(BinaryReader br)
{
return new string(new char[4]
{
(char)br.ReadByte(),
(char)br.ReadByte(),
(char)br.ReadByte(),
(char)br.ReadByte()
});
}
private static void Decode8(byte[] b, int offset, int size, int frames, int channels, int blockAlign, float[] dst)
{
if (blockAlign == channels)
{
for (int i = 0; i < dst.Length; i++)
{
dst[i] = (float)(b[offset + i] - 128) / 128f;
}
return;
}
int num = offset;
for (int j = 0; j < frames; j++)
{
for (int k = 0; k < channels; k++)
{
dst[j * channels + k] = (float)(b[num + k] - 128) / 128f;
}
num += blockAlign;
}
}
private static void Decode16(byte[] b, int offset, int frames, int channels, int blockAlign, float[] dst)
{
if (blockAlign == channels * 2)
{
short[] array = new short[dst.Length];
Buffer.BlockCopy(b, offset, array, 0, dst.Length * 2);
for (int i = 0; i < array.Length; i++)
{
dst[i] = (float)array[i] / 32768f;
}
return;
}
int num = offset;
for (int j = 0; j < frames; j++)
{
for (int k = 0; k < channels; k++)
{
dst[j * channels + k] = (float)(short)(b[num + k * 2] | (b[num + k * 2 + 1] << 8)) / 32768f;
}
num += blockAlign;
}
}
private static void Decode24(byte[] b, int offset, int frames, int channels, int blockAlign, float[] dst)
{
int num = offset;
for (int i = 0; i < frames; i++)
{
for (int j = 0; j < channels; j++)
{
int num2 = b[num] | (b[num + 1] << 8) | (b[num + 2] << 16);
if ((num2 & 0x800000) != 0)
{
num2 |= -16777216;
}
dst[i * channels + j] = (float)num2 / 8388608f;
num += 3;
}
num += blockAlign - channels * 3;
}
}
private static void Decode32(byte[] b, int offset, int frames, int channels, int blockAlign, float[] dst)
{
int num = offset;
for (int i = 0; i < frames; i++)
{
for (int j = 0; j < channels; j++)
{
int num2 = I32(b, num);
dst[i * channels + j] = (float)num2 / 2.1474836E+09f;
num += 4;
}
num += blockAlign - channels * 4;
}
}
private static void DecodeFloat32(byte[] b, int offset, int frames, int channels, int blockAlign, float[] dst)
{
int num = offset;
for (int i = 0; i < frames; i++)
{
for (int j = 0; j < channels; j++)
{
dst[i * channels + j] = BitConverter.ToSingle(b, num);
num += 4;
}
num += blockAlign - channels * 4;
}
}
private static void DecodeFloat64(byte[] b, int offset, int frames, int channels, int blockAlign, float[] dst)
{
int num = offset;
for (int i = 0; i < frames; i++)
{
for (int j = 0; j < channels; j++)
{
dst[i * channels + j] = (float)BitConverter.ToDouble(b, num);
num += 8;
}
num += blockAlign - channels * 8;
}
}
private static string FourCC(byte[] b, int o)
{
return new string(new char[4]
{
(char)b[o],
(char)b[o + 1],
(char)b[o + 2],
(char)b[o + 3]
});
}
private static ushort U16(byte[] b, int o)
{
return (ushort)(b[o] | (b[o + 1] << 8));
}
private static int I32(byte[] b, int o)
{
return b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) | (b[o + 3] << 24);
}
}
}