using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using UnityEngine; using UnityEngine.Video; [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("ConfigurableMoonPreview")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ConfigurableMoonPreview")] [assembly: AssemblyTitle("ConfigurableMoonPreview")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ConfigurableMoonPreview { internal sealed class Manifest { internal const int SupportedFormatVersion = 1; [JsonProperty("formatVersion")] public int FormatVersion = 1; [JsonProperty("packName")] public string PackName; [JsonProperty("author")] public string Author; [JsonProperty("entries")] public List Entries; } internal sealed class ManifestEntry { [JsonProperty("scene")] public string Scene; [JsonProperty("planet")] public string Planet; [JsonProperty("description")] public string Description; [JsonProperty("video")] public string Video; [JsonProperty("image")] public string Image; internal bool HasSelector => !string.IsNullOrEmpty(Scene) || !string.IsNullOrEmpty(Planet); internal bool HasReel => !string.IsNullOrEmpty(Video); internal bool HasPayload => Description != null || HasReel; internal string DescribeSelector => (!string.IsNullOrEmpty(Scene)) ? ("scene '" + Scene + "'") : ((!string.IsNullOrEmpty(Planet)) ? ("planet '" + Planet + "'") : ""); } internal static class ManifestLoader { private const string Suffix = "*.moonpreview.json"; private const string BundledManifest = "vanilla.moonpreview.json"; private static int bundledEntries; internal static ReelStore LoadAll(string pluginDirectory) { bundledEntries = 0; ReelStore reelStore = new ReelStore(); string skip = LoadBundled(reelStore, pluginDirectory); (int, int) tuple = LoadFrom(reelStore, Paths.PluginPath, "mod", skip); (int, int) tuple2 = LoadFrom(reelStore, Paths.ConfigPath, "user override", null); Plugin.Log.LogInfo((object)(string.Format("{0} moon entr{1} loaded: ", reelStore.Count, (reelStore.Count == 1) ? "y" : "ies") + $"{bundledEntries} bundled, {tuple.Item2} from {tuple.Item1} mod manifest(s), " + $"{tuple2.Item2} from {tuple2.Item1} user override(s).")); return reelStore; } private static string LoadBundled(ReelStore store, string pluginDirectory) { if (string.IsNullOrEmpty(pluginDirectory)) { return null; } string text = Path.Combine(pluginDirectory, "vanilla.moonpreview.json"); if (!File.Exists(text)) { Plugin.Log.LogWarning((object)("'vanilla.moonpreview.json' is missing from " + pluginDirectory + ". The vanilla moons keep the base game's descriptions. Reinstall if that was not deliberate.")); return text; } bundledEntries = Math.Max(Load(store, text, pluginDirectory), 0); return text; } private static (int manifests, int entries) LoadFrom(ReelStore store, string root, string label, string skip) { if (string.IsNullOrEmpty(root) || !Directory.Exists(root)) { return (manifests: 0, entries: 0); } string[] files; try { files = Directory.GetFiles(root, "*.moonpreview.json", SearchOption.AllDirectories); } catch (Exception arg) { Plugin.Log.LogError((object)$"Scanning '{root}' threw. {arg}"); return (manifests: 0, entries: 0); } if (files.Length == 0) { return (manifests: 0, entries: 0); } Array.Sort(files, (IComparer?)StringComparer.OrdinalIgnoreCase); int num = 0; int num2 = 0; string[] array = files; foreach (string text in array) { if (skip == null || !string.Equals(text, skip, StringComparison.OrdinalIgnoreCase)) { int num3 = Load(store, text, root); if (num3 >= 0) { num++; num2 += num3; } } } return (manifests: num, entries: num2); } private static int Load(ReelStore store, string path, string root) { string text = Shorten(path, root); Manifest manifest; try { manifest = JsonConvert.DeserializeObject(File.ReadAllText(path)); } catch (Exception ex) { Plugin.Log.LogError((object)("'" + text + "' is not valid JSON and was skipped. " + ex.Message)); return -1; } if (manifest == null) { Plugin.Log.LogWarning((object)("'" + text + "' is empty and was skipped.")); return -1; } if (manifest.FormatVersion > 1) { Plugin.Log.LogWarning((object)($"'{text}' declares formatVersion {manifest.FormatVersion}; this build understands " + $"up to {1}. Update Configurable Moon Preview. Skipped rather " + "than half-applied.")); return -1; } if (manifest.Entries == null || manifest.Entries.Count == 0) { Plugin.Log.LogWarning((object)("'" + text + "' has no entries.")); return -1; } string dir = Path.GetDirectoryName(path) ?? string.Empty; string text2 = ((!string.IsNullOrEmpty(manifest.PackName)) ? manifest.PackName : text); if (!string.IsNullOrEmpty(manifest.Author)) { text2 = text2 + " by " + manifest.Author; } int num = 0; for (int i = 0; i < manifest.Entries.Count; i++) { if (AddEntry(store, manifest.Entries[i], i, dir, text2, text)) { num++; } } return num; } private static bool AddEntry(ReelStore store, ManifestEntry entry, int index, string dir, string source, string name) { string text = $"'{name}' entry {index}"; if (entry == null) { Plugin.Log.LogWarning((object)(text + " is null; skipped.")); return false; } if (!entry.HasSelector) { Plugin.Log.LogWarning((object)(text + " names no moon. Give it a \"scene\" or a \"planet\". Skipped — an entry matching everything would repaint every moon in the game.")); return false; } if (!string.IsNullOrEmpty(entry.Image)) { Plugin.Log.LogWarning((object)(text + " sets \"image\", which is not supported — reels are video files. Convert it to an mp4 and use \"video\" instead.")); } if (!entry.HasPayload) { Plugin.Log.LogWarning((object)(text + " (" + entry.DescribeSelector + ") asks for nothing: no \"description\" and no \"video\". Skipped.")); return false; } MoonReel moonReel = new MoonReel { Description = entry.Description }; if (entry.Description != null) { moonReel.DescriptionSource = source; } if (!string.IsNullOrEmpty(entry.Video)) { moonReel.VideoPath = Resolve(dir, entry.Video); moonReel.ReelSource = source; if (!File.Exists(moonReel.VideoPath)) { Plugin.Log.LogWarning((object)(text + " points at '" + entry.Video + "', which does not exist (" + moonReel.VideoPath + "). The moon keeps its own reel.")); moonReel.VideoPath = null; moonReel.ReelSource = null; if (moonReel.Description == null) { return false; } } } store.Add(entry.Scene, entry.Planet, moonReel); return true; } private static string Resolve(string dir, string relative) { return Path.IsPathRooted(relative) ? relative : Path.Combine(dir, relative); } private static string Shorten(string path, string root) { if (!string.IsNullOrEmpty(root) && path.StartsWith(root, StringComparison.OrdinalIgnoreCase)) { return path.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); } return Path.GetFileName(path); } } internal static class Monitor { private static VideoAudioOutputMode? originalAudioMode; internal static void ApplyDescriptions() { SelectableLevel[] array = StartOfRound.Instance?.levels; if (array == null) { return; } int num = 0; SelectableLevel[] array2 = array; foreach (SelectableLevel level in array2) { if (ApplyDescription(level)) { num++; } } Plugin.Log.LogInfo((object)$"Applied {num} description(s) across {array.Length} moon(s)."); } internal static bool ApplyDescription(SelectableLevel level) { if ((Object)(object)level == (Object)null) { return false; } MoonReel moonReel = Plugin.Store.Lookup(level.sceneName, level.PlanetName); if (moonReel?.Description == null) { return false; } level.LevelDescription = moonReel.Description; return true; } internal static void Refresh(StartOfRound start) { if ((Object)(object)start == (Object)null) { return; } VideoPlayer screenLevelVideoReel = start.screenLevelVideoReel; SelectableLevel currentLevel = start.currentLevel; if ((Object)(object)screenLevelVideoReel == (Object)null || (Object)(object)currentLevel == (Object)null) { return; } try { MoonReel moonReel = Plugin.Store.Lookup(currentLevel.sceneName, currentLevel.PlanetName); if (start.isChallengeFile || moonReel?.VideoPath == null) { RestoreClipMode(start, screenLevelVideoReel, currentLevel); } else { Play(screenLevelVideoReel, moonReel.VideoPath); } } catch (Exception arg) { Plugin.Log.LogError((object)$"Updating the monitor for {currentLevel.PlanetName} threw. {arg}"); } } private static void Play(VideoPlayer player, string path) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_003d: Unknown result type (might be due to invalid IL or missing references) if ((int)player.source != 1 || !string.Equals(player.url, path, StringComparison.Ordinal)) { player.Stop(); if (!originalAudioMode.HasValue) { originalAudioMode = player.audioOutputMode; } player.audioOutputMode = (VideoAudioOutputMode)0; player.source = (VideoSource)1; player.clip = null; player.url = path; player.isLooping = true; } ((Behaviour)player).enabled = true; ((Component)player).gameObject.SetActive(true); player.Play(); } private static void RestoreClipMode(StartOfRound start, VideoPlayer player, SelectableLevel level) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0052: Unknown result type (might be due to invalid IL or missing references) if ((int)player.source == 1) { player.Stop(); player.source = (VideoSource)0; player.url = string.Empty; player.clip = level.videoReel; if (originalAudioMode.HasValue) { player.audioOutputMode = originalAudioMode.Value; } bool flag = (((Behaviour)player).enabled = (Object)(object)level.videoReel != (Object)null && !start.isChallengeFile); ((Component)player).gameObject.SetActive(flag); if (flag) { player.Play(); } } } } [HarmonyPatch(typeof(StartOfRound))] internal static class Patches { [HarmonyPostfix] [HarmonyPatch("Start")] [HarmonyPriority(200)] private static void Start_Postfix() { try { Monitor.ApplyDescriptions(); } catch (Exception arg) { Plugin.Log.LogError((object)$"Applying descriptions at StartOfRound.Start threw. {arg}"); } } [HarmonyPrefix] [HarmonyPatch("SetMapScreenInfoToCurrentLevel")] private static void SetMapScreenInfoToCurrentLevel_Prefix(StartOfRound __instance) { try { Monitor.ApplyDescription(__instance.currentLevel); } catch (Exception arg) { Plugin.Log.LogError((object)$"Applying a description threw; the screen keeps vanilla text. {arg}"); } } [HarmonyPostfix] [HarmonyPatch("SetMapScreenInfoToCurrentLevel")] private static void SetMapScreenInfoToCurrentLevel_Postfix(StartOfRound __instance) { Monitor.Refresh(__instance); } [HarmonyPostfix] [HarmonyPatch("SwitchMapMonitorPurpose")] private static void SwitchMapMonitorPurpose_Postfix(StartOfRound __instance, bool displayInfo) { if (displayInfo) { Monitor.Refresh(__instance); } } } [BepInPlugin("com.luth.configurablemoonpreview", "ConfigurableMoonPreview", "1.0.0")] [BepInProcess("Lethal Company.exe")] public class Plugin : BaseUnityPlugin { public const string Guid = "com.luth.configurablemoonpreview"; public const string Name = "ConfigurableMoonPreview"; public const string Version = "1.0.0"; internal static ManualLogSource Log { get; private set; } internal static ReelStore Store { get; private set; } = new ReelStore(); private void Awake() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; string pluginDirectory = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? string.Empty; Store = ManifestLoader.LoadAll(pluginDirectory); try { new Harmony("com.luth.configurablemoonpreview").PatchAll(typeof(Plugin).Assembly); } catch (Exception arg) { Log.LogError((object)$"Harmony patching failed; Configurable Moon Preview is inert this session. {arg}"); return; } Log.LogInfo((object)"ConfigurableMoonPreview 1.0.0 loaded."); } } internal sealed class MoonReel { internal string Description; internal string VideoPath; internal string DescriptionSource; internal string ReelSource; internal bool HasReel => VideoPath != null; internal MoonReel Clone() { return new MoonReel { Description = Description, VideoPath = VideoPath, DescriptionSource = DescriptionSource, ReelSource = ReelSource }; } internal void Overlay(MoonReel other) { if (other != null) { if (other.Description != null) { Description = other.Description; DescriptionSource = other.DescriptionSource; } if (other.HasReel) { VideoPath = other.VideoPath; ReelSource = other.ReelSource; } } } } internal sealed class ReelStore { private readonly Dictionary byScene = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary byPlanet = new Dictionary(StringComparer.OrdinalIgnoreCase); internal int Count => byScene.Count + byPlanet.Count; internal void Add(string scene, string planet, MoonReel reel) { if (reel != null) { if (!string.IsNullOrEmpty(scene)) { Merge(byScene, scene, reel); } if (!string.IsNullOrEmpty(planet)) { Merge(byPlanet, Numberless(planet), reel); } } } private static void Merge(Dictionary target, string key, MoonReel reel) { if (target.TryGetValue(key, out var value)) { value.Overlay(reel); } else { target[key] = reel.Clone(); } } internal MoonReel Lookup(string sceneName, string planetName) { MoonReel moonReel = null; if (!string.IsNullOrEmpty(planetName) && byPlanet.TryGetValue(Numberless(planetName), out var value)) { moonReel = value.Clone(); } if (!string.IsNullOrEmpty(sceneName) && byScene.TryGetValue(sceneName, out var value2)) { if (moonReel == null) { moonReel = value2.Clone(); } else { moonReel.Overlay(value2); } } return moonReel; } internal static string Numberless(string planetName) { if (string.IsNullOrEmpty(planetName)) { return string.Empty; } int i; for (i = 0; i < planetName.Length && char.IsDigit(planetName[i]); i++) { } for (; i < planetName.Length && planetName[i] == ' '; i++) { } return (i >= planetName.Length) ? planetName.Trim() : planetName.Substring(i).Trim(); } } }