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;
}
}