Decompiled source of BetterMediaControls v1.0.0

BepInEx/plugins/BetterMediaControls/BetterMediaControls.dll

Decompiled 12 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.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BetterMediaControls.audio;
using BetterMediaControls.patches;
using BetterMediaControls.util;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BetterMediaControls")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+58040d45ef491b17ba9ac6c1d43a18c6d74d3ec0")]
[assembly: AssemblyProduct("Better Media Controls")]
[assembly: AssemblyTitle("BetterMediaControls")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BetterMediaControls
{
	[BepInPlugin("com.patty.bettermediacontrols", "Better Media Controls", "1.0.0")]
	[BepInProcess("OnTogether.exe")]
	public class Plugin : BaseUnityPlugin
	{
		private const string PluginGUID = "com.patty.bettermediacontrols";

		private const string PluginName = "Better Media Controls";

		private const string PluginVersion = "1.0.0";

		private readonly Harmony _harmony = new Harmony("com.patty.bettermediacontrols");

		private ConfigEntry<string> _configMusicDir;

		private ConfigEntry<bool> _configVanillaMusicEnabled;

		private ConfigEntry<bool> _configShuffleEnabled;

		public static Plugin Instance { get; private set; }

		public bool IsVanillaMusicEnabled => _configVanillaMusicEnabled.Value;

		public bool IsShuffleEnabled => _configShuffleEnabled.Value;

		public static ManualLogSource Log { get; private set; }

		public Sprite ShuffleOnSprite { get; private set; }

		public Sprite ShuffleOffSprite { get; private set; }

		private void Awake()
		{
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			_configMusicDir = ((BaseUnityPlugin)this).Config.Bind<string>("General", "MusicDirectory", "music", "NOTE: .wav files only. Directory inside the plugin folder where custom music is stored.");
			_configVanillaMusicEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableVanillaMusic", true, "If true, the game's original music will still show up alongside custom tracks.");
			_configShuffleEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "EnableShuffle", false, "self explanatory. uses spotify shuffle (Fisher–Yates) algorithm.");
			string path = Path.Combine(Paths.PluginPath, "BetterMediaControls", "icons");
			ShuffleOnSprite = SpriteLoader.LoadSprite(Path.Combine(path, "Shuffle.png"));
			ShuffleOffSprite = SpriteLoader.LoadSprite(Path.Combine(path, "ShuffleUnchecked.png"));
			if ((Object)(object)ShuffleOnSprite == (Object)null || (Object)(object)ShuffleOffSprite == (Object)null)
			{
				Log.LogWarning((object)"Shuffle icons missing or failed to load");
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin com.patty.bettermediacontrols is loaded!");
			_harmony.PatchAll();
		}

		public void ToggleShuffle()
		{
			_configShuffleEnabled.Value = !_configShuffleEnabled.Value;
			if (_configShuffleEnabled.Value)
			{
				ShufflePatch.ResetShuffle();
			}
			Log.LogInfo((object)("Shuffle " + (IsShuffleEnabled ? "enabled" : "disabled")));
		}

		public string GetMusicDirectory()
		{
			string value = _configMusicDir.Value;
			value = Environment.ExpandEnvironmentVariables(value);
			if (Path.IsPathRooted(value))
			{
				return value;
			}
			return Path.Combine(Paths.PluginPath, "BetterMediaControls", value);
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "BetterMediaControls";

		public const string PLUGIN_NAME = "Better Media Controls";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace BetterMediaControls.util
{
	public static class ReflectionUtils
	{
		public static object GetField(object instance, string fieldName)
		{
			return (instance == null) ? null : AccessTools.Field(instance.GetType(), fieldName)?.GetValue(instance);
		}

		public static T GetField<T>(object instance, string fieldName) where T : class
		{
			return GetField(instance, fieldName) as T;
		}
	}
	public static class SpriteLoader
	{
		public static Sprite LoadSprite(string path)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			if (!File.Exists(path))
			{
				Plugin.Log.LogWarning((object)("Sprite not found: " + path));
				return null;
			}
			byte[] array = File.ReadAllBytes(path);
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
			if (!ImageConversion.LoadImage(val, array))
			{
				Plugin.Log.LogError((object)("Failed to load image: " + path));
				return null;
			}
			((Texture)val).filterMode = (FilterMode)1;
			((Texture)val).wrapMode = (TextureWrapMode)1;
			return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f);
		}
	}
}
namespace BetterMediaControls.patches
{
	[HarmonyPatch(typeof(SoundManager))]
	public static class MediaPatch
	{
		[HarmonyPatch("Start")]
		public static void Postfix(SoundManager __instance)
		{
			((MonoBehaviour)__instance).StartCoroutine(InjectSong(__instance));
		}

		private static IEnumerator InjectSong(SoundManager soundManager)
		{
			ManualLogSource log = Plugin.Log;
			if (!Object.op_Implicit((Object)(object)Plugin.Instance))
			{
				log.LogError((object)"Plugin instance is null");
				yield break;
			}
			string musicDir = Plugin.Instance.GetMusicDirectory();
			string playlistPath = Path.Combine(musicDir, "playlist.json");
			PlaylistFile playlistFile = null;
			bool hasPlaylistJson = File.Exists(playlistPath);
			if (!hasPlaylistJson)
			{
				log.LogWarning((object)("No playlist.json found at " + playlistPath + ", falling back to directory scan"));
			}
			else
			{
				try
				{
					string json = File.ReadAllText(playlistPath);
					playlistFile = JsonConvert.DeserializeObject<PlaylistFile>(json);
					if (playlistFile?.Playlist == null)
					{
						log.LogError((object)"playlist.json parsed, but playlist is null");
						yield break;
					}
					log.LogDebug((object)$"Parsed playlist.json with {playlistFile.Playlist.Count} entries");
				}
				catch (Exception ex)
				{
					Exception e = ex;
					log.LogError((object)$"Failed to parse playlist.json: {e}");
					yield break;
				}
			}
			object mixtapesObj = ReflectionUtils.GetField(soundManager, "_mixtapes");
			if (mixtapesObj == null)
			{
				log.LogError((object)"_mixtapes is null");
				yield break;
			}
			IList mixTapes = ReflectionUtils.GetField<IList>(mixtapesObj, "MixTapes");
			if (mixTapes == null || mixTapes.Count == 0)
			{
				log.LogError((object)"MixTapes is null or empty");
				yield break;
			}
			object firstMixTape = mixTapes[0];
			IList songs = ReflectionUtils.GetField<IList>(firstMixTape, "Songs");
			if (songs == null)
			{
				log.LogError((object)"Songs is null");
				yield break;
			}
			if (!Plugin.Instance.IsVanillaMusicEnabled)
			{
				songs.Clear();
			}
			if (hasPlaylistJson)
			{
				for (int j = playlistFile.Playlist.Count - 1; j >= 0; j--)
				{
					PlaylistEntry entry = playlistFile.Playlist[j];
					string audioPath2 = Path.Combine(musicDir, entry.File);
					if (!File.Exists(audioPath2))
					{
						log.LogError((object)("Missing audio file: " + audioPath2));
					}
					else
					{
						log.LogDebug((object)("Loading song: " + audioPath2));
						AudioClip clip2 = AudioLoader.LoadAudio(audioPath2);
						if (!Object.op_Implicit((Object)(object)clip2))
						{
							log.LogError((object)("Failed to load WAV: " + audioPath2));
						}
						else
						{
							Song song2 = new Song
							{
								Title = (string.IsNullOrEmpty(entry.Title) ? Path.GetFileNameWithoutExtension(entry.File) : entry.Title),
								Artist = (string.IsNullOrEmpty(entry.Artist) ? "Unknown" : entry.Artist),
								Music = clip2,
								Volume = ((entry.Volume > 0f) ? entry.Volume : 1f)
							};
							songs.Insert(0, song2);
							log.LogDebug((object)("Added song: " + song2.Title + " by " + song2.Artist));
						}
					}
				}
			}
			else
			{
				string[] files = Directory.GetFiles(musicDir, "*.wav");
				for (int i = files.Length - 1; i >= 0; i--)
				{
					string audioPath = files[i];
					AudioClip clip = AudioLoader.LoadAudio(audioPath);
					if (!Object.op_Implicit((Object)(object)clip))
					{
						log.LogError((object)("Failed to load WAV: " + audioPath));
					}
					else
					{
						Song song = new Song
						{
							Title = Path.GetFileNameWithoutExtension(audioPath),
							Artist = "Unknown",
							Music = clip,
							Volume = 1f
						};
						songs.Insert(0, song);
						log.LogDebug((object)("Added fallback song: " + song.Title));
					}
				}
			}
			log.LogInfo((object)$"Final song count: {songs.Count}");
		}
	}
	[HarmonyPatch(typeof(SoundManager), "ChangeSong")]
	[HarmonyPatch(new Type[] { typeof(bool) })]
	public static class ShufflePatch
	{
		private static readonly List<int> ShuffledOrder = new List<int>();

		private static int _shufflePosition;

		private static int _lastMixtapeIndex = -1;

		private static readonly MethodInfo ChangeSongClipMethod = AccessTools.Method(typeof(SoundManager), "ChangeSongClip", (Type[])null, (Type[])null);

		private static void Shuffle(List<int> list)
		{
			for (int num = list.Count - 1; num > 0; num--)
			{
				int num2 = Random.Range(0, num + 1);
				int index = num;
				int index2 = num2;
				int value = list[num2];
				int value2 = list[num];
				list[index] = value;
				list[index2] = value2;
			}
		}

		[HarmonyPrefix]
		public static bool Prefix(SoundManager __instance, bool isNext)
		{
			//IL_0200: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Expected O, but got Unknown
			if (!Plugin.Instance.IsShuffleEnabled)
			{
				return true;
			}
			object field = ReflectionUtils.GetField(__instance, "_mixtapes");
			IList field2 = ReflectionUtils.GetField<IList>(field, "MixTapes");
			if (field2 == null || field2.Count == 0)
			{
				return true;
			}
			int num = (int)ReflectionUtils.GetField(__instance, "_mixtapeIndex");
			int item = (int)ReflectionUtils.GetField(__instance, "_songIndex");
			if (num != _lastMixtapeIndex)
			{
				ShuffledOrder.Clear();
				_shufflePosition = 0;
				_lastMixtapeIndex = num;
			}
			IList field3 = ReflectionUtils.GetField<IList>(field2[num], "Songs");
			if (field3 == null || field3.Count == 0)
			{
				return true;
			}
			if (ShuffledOrder.Count != field3.Count)
			{
				ShuffledOrder.Clear();
				for (int i = 0; i < field3.Count; i++)
				{
					ShuffledOrder.Add(i);
				}
				Shuffle(ShuffledOrder);
				_shufflePosition = ShuffledOrder.IndexOf(item);
				if (_shufflePosition < 0)
				{
					_shufflePosition = 0;
				}
			}
			if (isNext)
			{
				_shufflePosition++;
				if (_shufflePosition >= ShuffledOrder.Count)
				{
					_shufflePosition = 0;
				}
			}
			else
			{
				_shufflePosition--;
				if (_shufflePosition < 0)
				{
					_shufflePosition = ShuffledOrder.Count - 1;
				}
			}
			int num2 = ShuffledOrder[_shufflePosition];
			AccessTools.Field(((object)__instance).GetType(), "_songIndex").SetValue(__instance, num2);
			ChangeSongClipMethod.Invoke(__instance, null);
			MonoSingleton<UIManager>.I.UpdateSongInfo((Song)field3[num2]);
			return false;
		}

		public static void ResetShuffle()
		{
			ShuffledOrder.Clear();
			_shufflePosition = 0;
		}
	}
	[HarmonyPatch(typeof(UIManager), "Start")]
	public static class ShuffleUIPatch
	{
		[HarmonyPostfix]
		public static void Postfix(UIManager __instance)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Expected O, but got Unknown
			ManualLogSource log = Plugin.Log;
			Image field = ReflectionUtils.GetField<Image>(__instance, "_loopImage");
			if ((Object)(object)field == (Object)null)
			{
				log.LogError((object)"Shuffle UI: _loopImage not found");
				return;
			}
			GameObject gameObject = ((Component)field).gameObject;
			GameObject val = Object.Instantiate<GameObject>(gameObject, gameObject.transform.parent);
			((Object)val).name = "Button_Shuffle";
			Transform transform = val.transform;
			transform.localPosition += new Vector3(60f, 0f, 0f);
			Image shuffleImage = val.GetComponent<Image>();
			Button component = val.GetComponent<Button>();
			if ((Object)(object)component == (Object)null || (Object)(object)shuffleImage == (Object)null)
			{
				log.LogError((object)"Shuffle UI: missing Image or Button component");
				return;
			}
			component.onClick = new ButtonClickedEvent();
			((UnityEvent)component.onClick).AddListener((UnityAction)delegate
			{
				Plugin.Instance.ToggleShuffle();
				UpdateShuffleUI(__instance, shuffleImage);
				MonoSingleton<SFXManager>.I.PlayUIClick();
			});
			UpdateShuffleUI(__instance, shuffleImage);
			log.LogInfo((object)"Shuffle button cloned from loop button");
		}

		private static void UpdateShuffleUI(UIManager uiManager, Image shuffleImage)
		{
			bool isShuffleEnabled = Plugin.Instance.IsShuffleEnabled;
			Sprite val = Plugin.Instance.ShuffleOnSprite ?? ReflectionUtils.GetField<Sprite>(uiManager, "_loopCheckedSprite");
			Sprite val2 = Plugin.Instance.ShuffleOffSprite ?? ReflectionUtils.GetField<Sprite>(uiManager, "_loopUncheckedSprite");
			shuffleImage.sprite = (isShuffleEnabled ? val : val2);
		}
	}
}
namespace BetterMediaControls.audio
{
	public static class AudioLoader
	{
		public static AudioClip LoadAudio(string path)
		{
			byte[] array = File.ReadAllBytes(path);
			int num = BitConverter.ToInt16(array, 22);
			int num2 = BitConverter.ToInt32(array, 24);
			int num3 = 44;
			int num4 = (array.Length - num3) / 2;
			float[] array2 = new float[num4];
			int num5 = 0;
			for (int i = num3; i < array.Length; i += 2)
			{
				short num6 = BitConverter.ToInt16(array, i);
				array2[num5++] = (float)num6 / 32768f;
			}
			AudioClip val = AudioClip.Create(Path.GetFileNameWithoutExtension(path), num4 / num, num, num2, false);
			val.SetData(array2, 0);
			return val;
		}
	}
	public class PlaylistFile
	{
		public List<PlaylistEntry> Playlist;
	}
	public class PlaylistEntry
	{
		public string File;

		public string Title;

		public string Artist;

		public float Volume = 1f;
	}
}