using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using SleepeyDev.OnTogetherLocalPlaylist.Net; using SleepeyDev.OnTogetherLocalPlaylist.Patches; using SleepeyDev.OnTogetherLocalPlaylist.Platform; using SleepeyDev.OnTogetherLocalPlaylist.Playlist; using SleepeyDev.OnTogetherLocalPlaylist.Share; using SleepeyDev.OnTogetherLocalPlaylist.UI; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SleepeyDev")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.6.0.0")] [assembly: AssemblyInformationalVersion("0.6.0+63ead13a0b394a0e0ee425c279a9e5b8a274470f")] [assembly: AssemblyProduct("OnTogetherLocalPlaylist")] [assembly: AssemblyTitle("OnTogetherLocalPlaylist")] [assembly: AssemblyVersion("0.6.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SleepeyDev.OnTogetherLocalPlaylist { public static class HarmonyBootstrap { private static readonly Type[] PatchClasses = new Type[4] { typeof(SoundManagerPatches), typeof(UIManagerPatches), typeof(LifecyclePatches), typeof(UiBuildPatches) }; public static void ApplyAll() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown Harmony val = new Harmony("com.sleepeydev.ontogether.localplaylist"); int num = 0; Type[] patchClasses = PatchClasses; foreach (Type type in patchClasses) { try { val.CreateClassProcessor(type).Patch(); num++; } catch (Exception ex) { Plugin.Log.LogError((object)("Patch class " + type.Name + " failed to apply (that feature is off): " + ex.Message)); } } Plugin.Log.LogInfo((object)("Applied " + num + "/" + PatchClasses.Length + " patch classes.")); } } public static class MainThread { private static readonly ConcurrentQueue Queue = new ConcurrentQueue(); public static void Post(Action action) { if (action != null) { Queue.Enqueue(action); } } public static void Drain(int maxPerFrame = 64) { for (int i = 0; i < maxPerFrame; i++) { if (!Queue.TryDequeue(out var result)) { break; } try { result(); } catch (Exception ex) { Plugin.Log.LogError((object)("Main-thread action threw: " + ex)); } } } } public static class ModConfig { private const int CurrentLayoutVersion = 7; private static readonly string Sep = Path.DirectorySeparatorChar.ToString(); public static ConfigEntry LayoutVersion; public static ConfigEntry PlaylistEnabled; public static ConfigEntry MusicFolder; public static ConfigEntry MixtapeName; public static ConfigEntry IncludeSubfolders; public static ConfigEntry AllowTrackerFormats; public static ConfigEntry MaxTracks; public static ConfigEntry SeekBarEnabled; public static ConfigEntry SeekBarOnVanillaMixtapes; public static ConfigEntry PanelGrow; public static ConfigEntry SeekBarX; public static ConfigEntry SeekBarY; public static ConfigEntry SeekBarWidth; public static ConfigEntry SeekTimeX; public static ConfigEntry SeekTimeFontScale; public static ConfigEntry ModPanelEnabled; public static ConfigEntry ModPanelHeight; public static ConfigEntry ModRowTextRight; public static ConfigEntry ModRowButtonRight; public static ConfigEntry ModRowButtonWidth; public static ConfigEntry ModRowButtonGap; public static ConfigEntry ModRowTextWidth; public static ConfigEntry PickFolderByFile; public static ConfigEntry ModRowHeight; public static ConfigEntry ModRowFontScale; public static ConfigEntry PickFolderLabel; public static ConfigEntry ReloadLabel; public static ConfigEntry WatchFolder; public static ConfigEntry WatchDebounceMs; public static ConfigEntry SharingEnabled; public static ConfigEntry LiveTapeName; public static ConfigEntry MaxListeners; public static ConfigEntry ListenLatencyMs; public static ConfigEntry VerboseLogging; public static ConfigEntry DumpUi; public static ConfigEntry DumpUiKey; public static ConfigEntry ProbeSeek; public static void Bind(ConfigFile cfg) { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Expected O, but got Unknown //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Expected O, but got Unknown //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Expected O, but got Unknown //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Expected O, but got Unknown //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Expected O, but got Unknown //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Expected O, but got Unknown //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Expected O, but got Unknown //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Expected O, but got Unknown //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Expected O, but got Unknown //IL_0363: Unknown result type (might be due to invalid IL or missing references) //IL_036d: Expected O, but got Unknown //IL_039b: Unknown result type (might be due to invalid IL or missing references) //IL_03a5: Expected O, but got Unknown //IL_03d3: Unknown result type (might be due to invalid IL or missing references) //IL_03dd: Expected O, but got Unknown //IL_0426: Unknown result type (might be due to invalid IL or missing references) //IL_0430: Expected O, but got Unknown //IL_045e: Unknown result type (might be due to invalid IL or missing references) //IL_0468: Expected O, but got Unknown //IL_04ef: Unknown result type (might be due to invalid IL or missing references) //IL_04f9: Expected O, but got Unknown //IL_0556: Unknown result type (might be due to invalid IL or missing references) //IL_0560: Expected O, but got Unknown //IL_058a: Unknown result type (might be due to invalid IL or missing references) //IL_0594: Expected O, but got Unknown PlaylistEnabled = cfg.Bind("Playlist", "Enabled", true, "Master switch for the local music mixtape."); MusicFolder = cfg.Bind("Playlist", "MusicFolder", "", "Folder scanned for music. Leave empty to disable.\nExample: D:" + Sep + "Music" + Sep + "Focus\nSupported: .mp3 .ogg .wav .aif .aiff\nNetwork paths are not supported by Unity's audio loader - map a drive letter instead.\nThe Folder... button on the mini music player sets this without editing the file."); MixtapeName = cfg.Bind("Playlist", "MixtapeName", "Local", "Name shown on the mixtape in the in-game music player."); IncludeSubfolders = cfg.Bind("Playlist", "IncludeSubfolders", true, "Scan subfolders of MusicFolder as well."); AllowTrackerFormats = cfg.Bind("Playlist", "AllowTrackerFormats", false, "Also accept tracker modules (.it .mod .s3m .xm). Off by default: they are rare and\nthe decoder behaviour on Windows standalone is not verified."); MaxTracks = cfg.Bind("Playlist", "MaxTracks", 300, new ConfigDescription("Safety cap on how many files are added to the mixtape.\nThe game builds one clickable button per track when the list is shown, so very\nlarge numbers cost a visible hitch the first time the music panel opens.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 5000), Array.Empty())); SeekBarEnabled = cfg.Bind("UI", "SeekBarEnabled", true, "Show a scrub bar on the mini music player, so you can jump to any point in a track."); SeekBarOnVanillaMixtapes = cfg.Bind("UI", "SeekBarOnVanillaMixtapes", true, "Also show the scrub bar while one of the game's own mixtapes is selected."); PanelGrow = cfg.Bind("UI", "PanelGrow", 44f, new ConfigDescription("Extra height added to the mini music player so the scrub row has its own space instead\nof being crammed against the bottom edge. The top edge stays put; the panel grows\ndownwards, and the hover box that keeps the panel open is moved to match.\n0 leaves the panel exactly as the game draws it - then move the bar with SeekBarY.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 160f), Array.Empty())); SeekBarX = cfg.Bind("UI", "SeekBarX", -14f, new ConfigDescription("Centre of the scrub bar, horizontally. The default lines its left edge up with the\nvolume slider above it.", (AcceptableValueBase)(object)new AcceptableValueRange(-500f, 500f), Array.Empty())); SeekBarY = cfg.Bind("UI", "SeekBarY", -112f, new ConfigDescription("Height of the scrub bar. The panel's visible bottom is at -118 unmodified, or\n-118 minus PanelGrow with the default growth.", (AcceptableValueBase)(object)new AcceptableValueRange(-280f, 140f), Array.Empty())); SeekBarWidth = cfg.Bind("UI", "SeekBarWidth", 628f, new ConfigDescription("Width of the scrub bar in pixels.", (AcceptableValueBase)(object)new AcceptableValueRange(100f, 900f), Array.Empty())); SeekTimeX = cfg.Bind("UI", "SeekTimeX", 440f, new ConfigDescription("RIGHT edge of the time readout - the label is right-aligned, like the artist name it\nis cloned from. The panel's visible right edge is at 448.", (AcceptableValueBase)(object)new AcceptableValueRange(-500f, 500f), Array.Empty())); SeekTimeFontScale = cfg.Bind("UI", "SeekTimeFontScale", 0.72f, new ConfigDescription("Size of the time readout relative to the artist name.", (AcceptableValueBase)(object)new AcceptableValueRange(0.3f, 1.5f), Array.Empty())); ModPanelEnabled = cfg.Bind("UI", "ModPanelEnabled", true, "Add a row under the scrub bar with the current music folder, a folder picker and a\nReload button. Turning it off also removes the extra panel height it needs."); ModPanelHeight = cfg.Bind("UI", "ModPanelHeight", 46f, new ConfigDescription("How far below the scrub bar that row sits, and how much extra panel height it takes.", (AcceptableValueBase)(object)new AcceptableValueRange(24f, 120f), Array.Empty())); ModRowTextRight = cfg.Bind("UI", "ModRowTextRight", -106f, new ConfigDescription("RIGHT edge of the status label, which is left-aligned inside ModRowTextWidth.\nIt has to clear the leftmost button; with four buttons that is around -106.", (AcceptableValueBase)(object)new AcceptableValueRange(-500f, 500f), Array.Empty())); ModRowTextWidth = cfg.Bind("UI", "ModRowTextWidth", 320f, new ConfigDescription("Width of the status label's box.", (AcceptableValueBase)(object)new AcceptableValueRange(100f, 700f), Array.Empty())); ModRowButtonRight = cfg.Bind("UI", "ModRowButtonRight", 430f, new ConfigDescription("RIGHT edge of the Reload button; the folder button sits just left of it.", (AcceptableValueBase)(object)new AcceptableValueRange(-500f, 500f), Array.Empty())); ModRowButtonWidth = cfg.Bind("UI", "ModRowButtonWidth", 124f, new ConfigDescription("Width of each button on the row. Four of them plus the status label have to fit\ninside the panel's 896 px, so there is not much room to grow.", (AcceptableValueBase)(object)new AcceptableValueRange(60f, 320f), Array.Empty())); ModRowButtonGap = cfg.Bind("UI", "ModRowButtonGap", 10f, new ConfigDescription("Space between the buttons.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 60f), Array.Empty())); PickFolderByFile = cfg.Bind("UI", "PickFolderByFile", false, "Choose the folder by picking any track inside it, instead of selecting the folder itself.\nWindows' folder mode hides files, so a music folder with no subfolders looks empty - if\nthat bothers you more than picking a file does, set this to true."); ModRowHeight = cfg.Bind("UI", "ModRowHeight", 38f, new ConfigDescription("Height of the folder and reload buttons.", (AcceptableValueBase)(object)new AcceptableValueRange(20f, 90f), Array.Empty())); ModRowFontScale = cfg.Bind("UI", "ModRowFontScale", 0.55f, new ConfigDescription("Button caption size relative to the artist name.", (AcceptableValueBase)(object)new AcceptableValueRange(0.3f, 1.5f), Array.Empty())); PickFolderLabel = cfg.Bind("UI", "PickFolderLabel", "Folder...", "Caption of the button that opens the Windows folder dialog."); ReloadLabel = cfg.Bind("UI", "ReloadLabel", "Reload", "Caption of the rescan button."); WatchFolder = cfg.Bind("Playlist", "WatchFolder", true, "Notice files added to or removed from the music folder while the game is running."); WatchDebounceMs = cfg.Bind("Playlist", "WatchDebounceMs", 1500, new ConfigDescription("How long the folder must be quiet before a change triggers a rescan. This is also what\nkeeps a half-copied file from being read: Windows reports a file as created before its\nbytes have finished arriving. Raise it if you copy over a slow drive.", (AcceptableValueBase)(object)new AcceptableValueRange(250, 10000), Array.Empty())); SharingEnabled = cfg.Bind("Sharing", "Enabled", true, "Allow listening to, and broadcasting to, other players who have this mod.\nNothing is sent to anyone until you press Share, and nothing is received until you\npress Listen. Players without the mod are never contacted at all."); LiveTapeName = cfg.Bind("Sharing", "LiveTapeName", "Shared", "Name of the extra mixtape used for listening to someone else."); MaxListeners = cfg.Bind("Sharing", "MaxListeners", 6, new ConfigDescription("How many people may listen to you at once. Each one costs roughly your own\nbitrate in upload while their next track transfers.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 16), Array.Empty())); ListenLatencyMs = cfg.Bind("Sharing", "ListenLatencyMs", 250, new ConfigDescription("How far behind the broadcaster to sit. Some slack absorbs network jitter; without\nit the listener would keep trying to play audio the broadcaster has not sent yet.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 2000), Array.Empty())); VerboseLogging = cfg.Bind("Diagnostics", "VerboseLogging", false, "Log every scanned file, clip load, eviction and stall repair. Noisy; for bug reports."); DumpUi = cfg.Bind("Diagnostics", "DumpUi", false, "Dump the music-player UI tree to the log when it is built. Attach the log to a bug\nreport if a widget lands in the wrong place."); DumpUiKey = cfg.Bind("Diagnostics", "DumpUiKey", (KeyCode)284, "Key that dumps that tree on demand. F4-F6 and F10-F11 are taken by Day and Night,\nF7-F9 by FrameCare and BlueSage, so F1-F3 are the free ones."); ProbeSeek = cfg.Bind("Diagnostics", "ProbeSeek", true, "Log the cost and read-back accuracy of the first seek. Cheap, and it is what turns\n'seeking does not work' into a report someone can act on."); MigrateLayout(cfg); } private static void MigrateLayout(ConfigFile cfg) { LayoutVersion = cfg.Bind("UI", "LayoutVersion", 0, "Internal. Bumped when the mod ships new default widget positions, which resets the\nposition values above to those defaults. Set it to 0 by hand to force that reset."); if (LayoutVersion.Value < 7) { ResetToDefault(PanelGrow); ResetToDefault(SeekBarX); ResetToDefault(SeekBarY); ResetToDefault(SeekBarWidth); ResetToDefault(SeekTimeX); ResetToDefault(SeekTimeFontScale); ResetToDefault(ModPanelHeight); ResetToDefault(ModRowTextRight); ResetToDefault(ModRowButtonRight); ResetToDefault(ModRowButtonWidth); ResetToDefault(ModRowHeight); ResetToDefault(ModRowFontScale); ResetToDefault(ModRowButtonGap); ResetToDefault(ModRowTextWidth); Plugin.Log.LogInfo((object)("Applied the 0.3.1 music player layout (previous version: " + LayoutVersion.Value + "). Your own tweaks to those positions were reset.")); LayoutVersion.Value = 7; } } private static void ResetToDefault(ConfigEntry entry) { if (entry != null) { entry.Value = (T)((ConfigEntryBase)entry).DefaultValue; } } } public sealed class ModRoot : MonoBehaviour { public static ModRoot Instance { get; private set; } public static void Create() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown if (!((Object)(object)Instance != (Object)null)) { GameObject val = new GameObject("OTLP_ModRoot") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)val); Instance = val.AddComponent(); } } public static Coroutine Run(IEnumerator routine) { if ((Object)(object)Instance == (Object)null || routine == null) { return null; } return ((MonoBehaviour)Instance).StartCoroutine(routine); } public static void Stop(Coroutine routine) { if ((Object)(object)Instance != (Object)null && routine != null) { ((MonoBehaviour)Instance).StopCoroutine(routine); } } private void Update() { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) MainThread.Drain(); try { MixtapeBridge.Tick(); } catch (Exception ex) { Plugin.Log.LogError((object)("MixtapeBridge.Tick threw, disabling: " + ex)); MixtapeBridge.Disable(); } try { ShareController.Tick(); } catch (Exception ex2) { Plugin.Log.LogError((object)("ShareController.Tick threw, sharing stopped: " + ex2)); ShareController.Shutdown(); } try { FolderWatcher.Tick(); } catch (Exception ex3) { Plugin.Log.LogError((object)("FolderWatcher.Tick threw, watching stopped: " + ex3)); FolderWatcher.Disarm(); } if (ModConfig.DumpUiKey != null && Input.GetKeyDown(ModConfig.DumpUiKey.Value)) { UiBuilder.DumpNow(); } } } [BepInPlugin("com.sleepeydev.ontogether.localplaylist", "On-Together Local Playlist", "0.6.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "com.sleepeydev.ontogether.localplaylist"; public static ManualLogSource Log { get; private set; } private void Awake() { Log = ((BaseUnityPlugin)this).Logger; try { ModConfig.Bind(((BaseUnityPlugin)this).Config); } catch (Exception ex) { Log.LogError((object)("Config binding failed - some features will switch themselves off: " + ex)); } ModRoot.Create(); HarmonyBootstrap.ApplyAll(); ShareController.Init(); if (ModConfig.MusicFolder != null) { ModConfig.MusicFolder.SettingChanged += delegate { MainThread.Post(delegate { FolderWatcher.Sync(); ReloadCoordinator.Request(ReloadReason.FolderChanged); }); }; } Log.LogInfo((object)("Local Playlist " + ((BaseUnityPlugin)this).Info.Metadata.Version?.ToString() + " ready.")); Refl.LogHealth(); } } public static class Refl { private static readonly List Missing = new List(); public static readonly FieldRef MixtapeMusic = Field("_mixtapeMusic"); public static readonly FieldRef SongIndex = Field("_songIndex"); public static readonly FieldRef MixtapeIndex = Field("_mixtapeIndex"); public static readonly FieldRef IsPlayingFlag = Field("_isPlaying"); public static readonly MethodInfo PlayMixtape = Method(typeof(SoundManager), "PlayMixtape"); public static readonly FieldRef MixTapeVolumeSlider = Field("_mixTapeVolumeSlider"); public static readonly FieldRef ArtistNameLabel = Field("_artistName"); public static readonly FieldRef SongButtonPrefab = Field("_songButtonPrefab"); public static readonly FieldRef MusicTimer = Field("_musicTimer"); public static readonly FieldRef MusicCorner1 = Field("_musicCorner1"); public static bool PlaylistReady { get { if (MixtapeMusic != null && SongIndex != null && MixtapeIndex != null && IsPlayingFlag != null) { return PlayMixtape != null; } return false; } } public static bool SeekUiReady { get { if (PlaylistReady && MixTapeVolumeSlider != null) { return ArtistNameLabel != null; } return false; } } public static bool ModPanelReady { get { if (PlaylistReady && MixTapeVolumeSlider != null && ArtistNameLabel != null) { return SongButtonPrefab != null; } return false; } } private static FieldRef Field(string name) { try { FieldInfo fieldInfo = AccessTools.Field(typeof(TOwner), name); if (fieldInfo == null || !typeof(TField).IsAssignableFrom(fieldInfo.FieldType)) { Missing.Add(typeof(TOwner).Name + "." + name); return null; } return AccessTools.FieldRefAccess(name); } catch (Exception) { Missing.Add(typeof(TOwner).Name + "." + name); return null; } } private static MethodInfo Method(Type owner, string name) { MethodInfo methodInfo = AccessTools.Method(owner, name, (Type[])null, (Type[])null); if (methodInfo == null) { Missing.Add(owner.Name + "." + name + "()"); } return methodInfo; } public static void LogHealth() { if (Missing.Count == 0) { Plugin.Log.LogInfo((object)"Game handles: all resolved."); return; } Plugin.Log.LogWarning((object)("Game handles missing (the game was probably updated): " + string.Join(", ", Missing.ToArray()))); Plugin.Log.LogWarning((object)("Feature availability - local playlist: " + (PlaylistReady ? "on" : "OFF") + ", seek bar: " + (SeekUiReady ? "on" : "OFF") + ", folder row: " + (ModPanelReady ? "on" : "OFF"))); } } } namespace SleepeyDev.OnTogetherLocalPlaylist.UI { public sealed class DragWatcher : MonoBehaviour, IPointerDownHandler, IEventSystemHandler, IPointerUpHandler { public bool IsDragging { get; private set; } public event Action Down; public event Action Up; public static DragWatcher Attach(GameObject go) { if ((Object)(object)go == (Object)null) { return null; } DragWatcher component = go.GetComponent(); if (!((Object)(object)component != (Object)null)) { return go.AddComponent(); } return component; } public void OnPointerDown(PointerEventData eventData) { if (!IsDragging) { IsDragging = true; Fire(this.Down); } } public void OnPointerUp(PointerEventData eventData) { Release(); } private void Update() { if (IsDragging && !Input.GetMouseButton(0)) { Release(); } } private void OnDisable() { if (IsDragging) { Release(); } } private void Release() { if (IsDragging) { IsDragging = false; Fire(this.Up); } } private void Fire(Action a) { if (a == null) { return; } try { a(); } catch (Exception ex) { Plugin.Log.LogError((object)("DragWatcher handler threw: " + ex)); } } } public static class GameUi { public const string Prefix = "OTLP_"; public static Transform Existing(Transform parent, string name) { if (!((Object)(object)parent == (Object)null)) { return parent.Find(name); } return null; } public static Slider CloneSlider(Slider donor, Transform parent, string name) { if ((Object)(object)donor == (Object)null || (Object)(object)parent == (Object)null) { return null; } GameObject val = Object.Instantiate(((Component)donor).gameObject, parent, false); ((Object)val).name = name; Slider component = val.GetComponent(); if ((Object)(object)component == (Object)null) { Object.Destroy((Object)(object)val); return null; } ClearListeners((UnityEvent)(object)component.onValueChanged); StripLocalizers(val); val.SetActive(true); return component; } public static GameObject CloneObject(GameObject donor, Transform parent, string name) { if ((Object)(object)donor == (Object)null || (Object)(object)parent == (Object)null) { return null; } GameObject obj = Object.Instantiate(donor, parent, false); ((Object)obj).name = name; StripLocalizers(obj); obj.SetActive(true); return obj; } public static TMP_Text CloneLabel(TMP_Text donor, Transform parent, string name, string text) { if ((Object)(object)donor == (Object)null || (Object)(object)parent == (Object)null) { return null; } GameObject val = Object.Instantiate(((Component)donor).gameObject, parent, false); ((Object)val).name = name; TMP_Text component = val.GetComponent(); if ((Object)(object)component == (Object)null) { Object.Destroy((Object)(object)val); return null; } StripLocalizers(val); StaticText.Apply(component, text); val.SetActive(true); return component; } public static void ClearListeners(UnityEvent evt) { if (evt != null) { for (int num = ((UnityEventBase)evt).GetPersistentEventCount() - 1; num >= 0; num--) { ((UnityEventBase)evt).SetPersistentListenerState(num, (UnityEventCallState)0); } ((UnityEventBase)evt).RemoveAllListeners(); } } public static void ClearListeners(UnityEvent evt) { if (evt != null) { for (int num = ((UnityEventBase)evt).GetPersistentEventCount() - 1; num >= 0; num--) { ((UnityEventBase)evt).SetPersistentListenerState(num, (UnityEventCallState)0); } ((UnityEventBase)evt).RemoveAllListeners(); } } public static int StripScripts(GameObject root) { if ((Object)(object)root == (Object)null) { return 0; } Assembly assembly = typeof(SoundManager).Assembly; int num = 0; Component[] componentsInChildren = root.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { Type type = ((object)val).GetType(); bool flag = type.Name.IndexOf("Localize", StringComparison.Ordinal) >= 0; bool flag2 = type.Assembly == assembly; if (flag || flag2) { Object.Destroy((Object)(object)val); num++; } } } return num; } public static int StripLocalizers(GameObject root) { return StripScripts(root); } public static void Place(RectTransform rt, Vector2 anchoredPos, Vector2 sizeDelta) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)rt == (Object)null) { return; } LayoutGroup val = (((Object)(object)((Transform)rt).parent == (Object)null) ? null : ((Component)((Transform)rt).parent).GetComponent()); if ((Object)(object)val == (Object)null) { rt.anchoredPosition = anchoredPos; rt.sizeDelta = sizeDelta; return; } HorizontalOrVerticalLayoutGroup val2 = (HorizontalOrVerticalLayoutGroup)(object)((val is HorizontalOrVerticalLayoutGroup) ? val : null); if ((Object)(object)val2 != (Object)null && val2.childControlHeight) { LayoutElement val3 = ((Component)rt).GetComponent(); if ((Object)(object)val3 == (Object)null) { val3 = ((Component)rt).gameObject.AddComponent(); } val3.preferredHeight = sizeDelta.y; } else { rt.sizeDelta = new Vector2(rt.sizeDelta.x, sizeDelta.y); } } public static LayoutGroup DrivingLayout(Transform t) { if (!((Object)(object)t == (Object)null) && !((Object)(object)t.parent == (Object)null)) { return ((Component)t.parent).GetComponent(); } return null; } public static void DropFocus() { if ((Object)(object)EventSystem.current != (Object)null) { EventSystem.current.SetSelectedGameObject((GameObject)null); } } public static void DumpHierarchy(Transform root, string title, int maxDepth = 6) { if ((Object)(object)root == (Object)null) { Plugin.Log.LogInfo((object)("[dump] " + title + ": ")); return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[dump] ").Append(title).Append('\n'); Describe(root, stringBuilder, 0, maxDepth); Plugin.Log.LogInfo((object)stringBuilder.ToString()); } private static void Describe(Transform t, StringBuilder sb, int depth, int maxDepth) { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)t == (Object)null || depth > maxDepth) { return; } sb.Append(' ', depth * 2).Append(((Object)t).name); sb.Append(" [").Append(((Component)t).gameObject.activeSelf ? "on" : "off").Append(", sib ") .Append(t.GetSiblingIndex()) .Append(']'); Component[] components = ((Component)t).GetComponents(); sb.Append(" {"); for (int i = 0; i < components.Length; i++) { if (i > 0) { sb.Append(", "); } sb.Append(((Object)(object)components[i] == (Object)null) ? "" : ((object)components[i]).GetType().Name); } sb.Append('}'); RectTransform val = (RectTransform)(object)((t is RectTransform) ? t : null); if (val != null) { sb.Append(" aMin").Append(V(val.anchorMin)).Append(" aMax") .Append(V(val.anchorMax)) .Append(" piv") .Append(V(val.pivot)) .Append(" pos") .Append(V(val.anchoredPosition)) .Append(" size") .Append(V(val.sizeDelta)) .Append(" scale") .Append(V(((Transform)val).localScale)); } LayoutGroup val2 = (((Object)(object)t.parent == (Object)null) ? null : ((Component)t.parent).GetComponent()); if ((Object)(object)val2 != (Object)null) { sb.Append(" [LAYOUT-DRIVEN by ").Append(((object)val2).GetType().Name).Append(']'); } LayoutGroup component = ((Component)t).GetComponent(); if ((Object)(object)component != (Object)null) { sb.Append(" ").Append(DescribeLayout(component)); } Selectable component2 = ((Component)t).GetComponent(); Slider val3 = (Slider)(object)((component2 is Slider) ? component2 : null); if (val3 != null) { sb.Append(" slider(persistent=").Append(((UnityEventBase)val3.onValueChanged).GetPersistentEventCount()).Append(')'); } else { Button val4 = (Button)(object)((component2 is Button) ? component2 : null); if (val4 != null) { sb.Append(" button(persistent=").Append(((UnityEventBase)val4.onClick).GetPersistentEventCount()).Append(')'); } } TMP_Text component3 = ((Component)t).GetComponent(); if ((Object)(object)component3 != (Object)null) { string text = component3.text ?? ""; if (text.Length > 40) { text = text.Substring(0, 40) + "..."; } sb.Append(" text=\"").Append(text).Append('"'); } sb.Append('\n'); for (int j = 0; j < t.childCount; j++) { Describe(t.GetChild(j), sb, depth + 1, maxDepth); } } public static string DescribeLayout(LayoutGroup g) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)g == (Object)null) { return ""; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(((object)g).GetType().Name).Append("(pad ").Append(g.padding.left) .Append(',') .Append(g.padding.right) .Append(',') .Append(g.padding.top) .Append(',') .Append(g.padding.bottom) .Append(" align=") .Append(g.childAlignment); HorizontalOrVerticalLayoutGroup val = (HorizontalOrVerticalLayoutGroup)(object)((g is HorizontalOrVerticalLayoutGroup) ? g : null); if (val != null) { stringBuilder.Append(" spacing=").Append(val.spacing).Append(" ctrlW=") .Append(val.childControlWidth) .Append(" ctrlH=") .Append(val.childControlHeight) .Append(" expW=") .Append(val.childForceExpandWidth) .Append(" expH=") .Append(val.childForceExpandHeight) .Append(" reverse=") .Append(val.reverseArrangement); } return stringBuilder.Append(')').ToString(); } private static string V(Vector2 v) { return "(" + v.x.ToString("0.##") + "," + v.y.ToString("0.##") + ")"; } private static string V(Vector3 v) { return "(" + v.x.ToString("0.##") + "," + v.y.ToString("0.##") + ")"; } } public static class MiniPlayer { public static float TotalGrow => Mathf.Max(0f, ModConfig.PanelGrow.Value) + (ModConfig.ModPanelEnabled.Value ? Mathf.Max(0f, ModConfig.ModPanelHeight.Value) : 0f); public static Transform Resolve(UIManager ui) { if ((Object)(object)ui == (Object)null || Refl.MixTapeVolumeSlider == null) { return null; } Slider val = Refl.MixTapeVolumeSlider.Invoke(ui); if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).transform.parent == (Object)null) { return null; } return ((Component)val).transform.parent.parent; } public static void Grow(UIManager ui, Transform panel, float grow) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)panel == (Object)null || grow <= 0f) { return; } int num = 0; string[] array = new string[2] { "Image_Bg", "Image_Outline" }; foreach (string text in array) { Transform obj = panel.Find(text); RectTransform val = (RectTransform)(object)((obj is RectTransform) ? obj : null); if (val != null) { val.sizeDelta = new Vector2(val.sizeDelta.x, val.sizeDelta.y + grow); val.anchoredPosition = new Vector2(val.anchoredPosition.x, val.anchoredPosition.y - grow * 0.5f); num++; } } if (num == 0) { Plugin.Log.LogWarning((object)"Could not find the mini player's background to enlarge; set UI/PanelGrow = 0 if the layout looks wrong."); return; } if (Refl.MusicCorner1 != null) { Transform obj2 = Refl.MusicCorner1.Invoke(ui); RectTransform val2 = (RectTransform)(object)((obj2 is RectTransform) ? obj2 : null); if (val2 != null) { val2.anchoredPosition = new Vector2(val2.anchoredPosition.x, val2.anchoredPosition.y - grow); } else { Plugin.Log.LogWarning((object)"The music hover-box corner is not a RectTransform; the panel may close when you reach for the added controls."); } } Plugin.Log.LogInfo((object)("Mini player grown by " + grow + " px (" + num + " layer(s)).")); } public static void KeepOpen(UIManager ui) { if ((Object)(object)ui != (Object)null && Refl.MusicTimer != null) { Refl.MusicTimer.Invoke(ui) = 0f; } } } public sealed class ModPanel : MonoBehaviour { private const string PathName = "OTLP_FolderPath"; private const string PickName = "OTLP_PickFolder"; private const string ReloadName = "OTLP_Reload"; private const string ShareName = "OTLP_Share"; private const string ListenName = "OTLP_Listen"; private static bool _failed; private UIManager _ui; private TMP_Text _pathLabel; private string _shown; private Button _shareButton; private Button _listenButton; private TMP_Text _shareCaption; private TMP_Text _listenCaption; private string _shownShareCaption; private string _shownListenCaption; public static void Build(UIManager ui, Transform panel) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: 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) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) if (_failed || !ModConfig.ModPanelEnabled.Value) { return; } if (!Refl.ModPanelReady) { Plugin.Log.LogWarning((object)"Folder row: game handles missing, skipping. The folder is still settable in the config file."); _failed = true; } else { if ((Object)(object)panel == (Object)null || (Object)(object)GameUi.Existing(panel, "OTLP_FolderPath") != (Object)null) { return; } float num = ModConfig.SeekBarY.Value - ModConfig.ModPanelHeight.Value; TMP_Text val = Refl.ArtistNameLabel.Invoke(ui); TMP_Text val2 = GameUi.CloneLabel(val, panel, "OTLP_FolderPath", ""); if ((Object)(object)val2 == (Object)null) { Plugin.Log.LogWarning((object)"Folder row: could not clone a label."); return; } RectTransform val3 = (RectTransform)val2.transform; val3.sizeDelta = new Vector2(ModConfig.ModRowTextWidth.Value, 34f); val3.anchoredPosition = new Vector2(ModConfig.ModRowTextRight.Value, num); val2.alignment = (TextAlignmentOptions)513; val2.fontSize = Mathf.Max(10f, val.fontSize * ModConfig.SeekTimeFontScale.Value); ((Graphic)val2).raycastTarget = false; ModPanel modPanel = ((Component)val2).gameObject.AddComponent(); modPanel._ui = ui; modPanel._pathLabel = val2; float value = ModConfig.ModRowButtonWidth.Value; float value2 = ModConfig.ModRowButtonGap.Value; float value3 = ModConfig.ModRowButtonRight.Value; Button val4 = MakeButton(ui, panel, "OTLP_Reload", ModConfig.ReloadLabel.Value, value3, num, OnReload); value3 -= value + value2; Button val5 = MakeButton(ui, panel, "OTLP_PickFolder", ModConfig.PickFolderLabel.Value, value3, num, OnPickFolder); value3 -= value + value2; if (ModConfig.SharingEnabled.Value) { modPanel._listenButton = MakeButton(ui, panel, "OTLP_Listen", "Listen", value3, num, ShareController.ToggleListen); value3 -= value + value2; modPanel._shareButton = MakeButton(ui, panel, "OTLP_Share", "Share", value3, num, ShareController.ToggleBroadcast); modPanel._listenCaption = CaptionOf(modPanel._listenButton); modPanel._shareCaption = CaptionOf(modPanel._shareButton); } Plugin.Log.LogInfo((object)("Mod row added at y " + num + " (" + (((Object)(object)val5 != (Object)null) ? "folder " : "") + (((Object)(object)val4 != (Object)null) ? "reload " : "") + (((Object)(object)modPanel._shareButton != (Object)null) ? "share listen" : "") + ").")); } } private static TMP_Text CaptionOf(Button b) { if (!((Object)(object)b == (Object)null)) { return ((Component)b).GetComponentInChildren(true); } return null; } private static Button MakeButton(UIManager ui, Transform panel, string name, string caption, float right, float y, Action onClick) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Expected O, but got Unknown Button val = FindButtonDonor(ui); if ((Object)(object)val == (Object)null) { return null; } GameObject val2 = GameUi.CloneObject(((Component)val).gameObject, panel, name); if ((Object)(object)val2 == (Object)null) { return null; } Button component = val2.GetComponent