Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of DunderKlumpar v1.2.3
BepInEx/plugins/DunderKlumpar/DunderKlumpar.dll
Decompiled a month agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using Unity.Netcode; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.SceneManagement; 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("DunderKlumpar")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.3.0")] [assembly: AssemblyInformationalVersion("1.2.3")] [assembly: AssemblyProduct("DunderKlumpar")] [assembly: AssemblyTitle("DunderKlumpar")] [assembly: AssemblyVersion("1.2.3.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 DunderKlumpar { [HarmonyPatch] internal static class DeathSounds { private const string Folder = "deaths"; private const float EarshotMetres = 45f; private static readonly List<AudioClip> Clips = new List<AudioClip>(); private static int _lastDeathCount = -1; internal static void LoadAll() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) string path = Path.Combine(Plugin.PluginDir, "deaths"); if (!Directory.Exists(path)) { Plugin.Log.LogInfo((object)"[Deaths] No deaths/ folder - no death sounds."); return; } string[] files = Directory.GetFiles(path); Array.Sort(files, (IComparer<string>?)StringComparer.OrdinalIgnoreCase); string[] array = files; foreach (string text in array) { AudioType? val = SoundReplacement.DetectAudioType(text); if (val.HasValue) { AudioClip val2 = SoundReplacement.LoadFromDisk(text, val.Value); if (!((Object)(object)val2 == (Object)null)) { ((Object)val2).name = "DunderKlumparDeath_" + Path.GetFileNameWithoutExtension(text); Clips.Add(val2); Plugin.Log.LogInfo((object)$"[Deaths] {Path.GetFileName(text)} ({val2.length:F1}s)"); } } } Plugin.Log.LogInfo((object)$"[Deaths] Ready: {Clips.Count} death sound(s)."); } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerControllerB), "KillPlayerClientRpc")] private static void OnAnyPlayerDied(int playerId) { //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) try { if (Clips.Count == 0) { return; } StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || instance.gameStats == null) { return; } int deaths = instance.gameStats.deaths; if (deaths == _lastDeathCount) { return; } _lastDeathCount = deaths; AudioClip val = Pick(deaths); if ((Object)(object)val == (Object)null) { return; } PlayerControllerB val2 = Victim(instance, playerId); PlayerControllerB localPlayerController = instance.localPlayerController; if ((Object)(object)val2 != (Object)null && (Object)(object)localPlayerController != (Object)null && (Object)(object)val2 == (Object)(object)localPlayerController) { HUDManager instance2 = HUDManager.Instance; if ((Object)(object)instance2 != (Object)null && (Object)(object)instance2.UIAudio != (Object)null) { instance2.UIAudio.PlayOneShot(val); } else { PlayAt(val, ((Component)val2).transform.position); } } else if ((Object)(object)val2 != (Object)null) { PlayAt(val, ((Component)val2).transform.position); } Plugin.Log.LogInfo((object)$"[Deaths] Death #{deaths} ← {((Object)val).name}"); } catch (Exception arg) { Plugin.Log.LogError((object)$"[Deaths] Could not play a death sound: {arg}"); } } private static PlayerControllerB Victim(StartOfRound round, int playerId) { if (round.allPlayerObjects == null) { return null; } if (playerId < 0 || playerId >= round.allPlayerObjects.Length) { return null; } GameObject val = round.allPlayerObjects[playerId]; if (!((Object)(object)val == (Object)null)) { return val.GetComponent<PlayerControllerB>(); } return null; } private static AudioClip Pick(int deaths) { if (Clips.Count == 1) { return Clips[0]; } int count = Clips.Count; int num = deaths / count; int[] array = new int[count]; for (int i = 0; i < count; i++) { array[i] = i; } uint num2 = SoundReplacement.StableHash("deaths") ^ (uint)RoundInfo.Seed ^ (uint)(num * -1640531535); if (num2 == 0) { num2 = 2463534242u; } for (int num3 = count - 1; num3 > 0; num3--) { num2 = SoundReplacement.NextRandom(num2); int num4 = (int)(num2 % (uint)(num3 + 1)); int num5 = array[num3]; array[num3] = array[num4]; array[num4] = num5; } return Clips[array[deaths % count]]; } private static void PlayAt(AudioClip clip, Vector3 position) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown GameObject val = new GameObject("DunderKlumparDeathSound"); val.transform.position = position; AudioSource obj = val.AddComponent<AudioSource>(); obj.clip = clip; obj.spatialBlend = 1f; obj.rolloffMode = (AudioRolloffMode)1; obj.minDistance = 6f; obj.maxDistance = 45f; obj.volume = 1f; obj.Play(); Object.Destroy((Object)val, clip.length + 1f); } } [HarmonyPatch] internal static class DropshipMusic { private sealed class Track { internal string Name; internal AudioClip Part1; internal AudioClip Part2; internal AudioClip Whole; } private sealed class IntroFade : MonoBehaviour { internal AudioSource Source; internal AudioLowPassFilter LowPass; internal DistanceColour Ground; internal double FullAt; internal double Seconds; private const float StartVolume = 0.8f; internal const float SweepStart = 500f; private const float SweepStartHz = 500f; private const float SweepEndHz = 22000f; private void Update() { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Source == (Object)null) { return; } double num = FullAt - AudioSettings.dspTime; if (num <= 0.0) { Source.volume = 1f; Source.minDistance = 25f; if ((Object)(object)Ground != (Object)null) { ((Behaviour)Ground).enabled = true; } ((Behaviour)this).enabled = false; return; } float num2 = Mathf.Clamp01((float)(1.0 - num / Seconds)); Source.volume = Mathf.Lerp(0.8f, 1f, num2); float num3 = DistanceColour.FarAmount(((Component)this).transform.position); if ((Object)(object)LowPass != (Object)null) { float num4 = 500f * Mathf.Pow(44f, num2); LowPass.cutoffFrequency = Mathf.Min(num4, DistanceColour.CutoffFor(num3)); } AudioReverbFilter component = ((Component)this).GetComponent<AudioReverbFilter>(); if ((Object)(object)component != (Object)null) { component.dryLevel = Mathf.Min(Mathf.Lerp(-600f, 0f, num2), Mathf.Lerp(0f, -900f, num3)); } } } private sealed class DistanceColour : MonoBehaviour { internal AudioLowPassFilter LowPass; internal AudioReverbFilter Reverb; private const float NearCutoff = 22000f; private const float FarCutoff = 800f; internal static float FarAmount(Vector3 position) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) AudioListener val = Object.FindObjectOfType<AudioListener>(); if ((Object)(object)val == (Object)null) { return 0f; } return Mathf.Clamp01((Vector3.Distance(position, ((Component)val).transform.position) - 25f) / 55f); } internal static float CutoffFor(float far) { return 22000f * Mathf.Pow(2f / 55f, far); } private void Update() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Object.FindObjectOfType<AudioListener>() == (Object)null) && !((Object)(object)LowPass == (Object)null)) { float num = FarAmount(((Component)this).transform.position); LowPass.cutoffFrequency = CutoffFor(num); if ((Object)(object)Reverb != (Object)null) { Reverb.dryLevel = Mathf.Lerp(0f, -900f, num); Reverb.room = Mathf.Lerp(-1000f, 0f, num); } } } } private const string Folder = "dropship"; private const float EarshotMetres = 220f; private const double TouchdownSeconds = 6.267; private const double SchedulingLeadSeconds = 0.1; private const float IntroSpatialBlend = 1f; private const float IntroFullVolumeMetres = 200f; private const float GroundFullVolumeMetres = 25f; private const float GroundMufflingMetres = 55f; private static readonly List<Track> Tracks = new List<Track>(); private static int _landings; private static int _countedSeed = int.MinValue; private static float _lastLanding = -99f; private static GameObject _speaker; internal static void LoadAll() { //IL_00e8: Unknown result type (might be due to invalid IL or missing references) string path = Path.Combine(Plugin.PluginDir, "dropship"); if (!Directory.Exists(path)) { Plugin.Log.LogInfo((object)"[Dropship] No dropship/ folder - the game's own jingle is left alone."); return; } Dictionary<string, Track> dictionary = new Dictionary<string, Track>(StringComparer.OrdinalIgnoreCase); string[] files = Directory.GetFiles(path); Array.Sort(files, (IComparer<string>?)StringComparer.OrdinalIgnoreCase); string[] array = files; foreach (string text in array) { AudioType? val = SoundReplacement.DetectAudioType(text); if (!val.HasValue) { continue; } string text2 = Path.GetFileNameWithoutExtension(text) ?? ""; int num = 0; if (text2.EndsWith("-part1", StringComparison.OrdinalIgnoreCase)) { num = 1; } else { if (!text2.EndsWith("-part2", StringComparison.OrdinalIgnoreCase)) { Plugin.Log.LogWarning((object)("[Dropship] " + Path.GetFileName(text) + " is neither -part1 nor -part2, so I cannot tell where it belongs. Skipped.")); continue; } num = 2; } string text3 = text2.Substring(0, text2.Length - "-partN".Length); AudioClip val2 = SoundReplacement.LoadFromDisk(text, val.Value); if (!((Object)(object)val2 == (Object)null)) { ((Object)val2).name = "DunderKlumparDropship_" + text2; Preload(val2); if (!dictionary.TryGetValue(text3, out var value)) { Track obj = new Track { Name = text3 }; value = obj; dictionary[text3] = obj; } if (num == 1) { value.Part1 = val2; } else { value.Part2 = val2; } Plugin.Log.LogInfo((object)$"[Dropship] {Path.GetFileName(text)} ({val2.length:F1}s)"); } } foreach (Track value2 in dictionary.Values) { if (!((Object)(object)value2.Part1 == (Object)null) || !((Object)(object)value2.Part2 == (Object)null)) { Tracks.Add(value2); } } foreach (Track track in Tracks) { track.Whole = Join(track); } Tracks.Sort((Track a, Track b) => StringComparer.OrdinalIgnoreCase.Compare(a.Name, b.Name)); Plugin.Log.LogInfo((object)$"[Dropship] Ready: {Tracks.Count} track(s)."); } private static AudioClip Join(Track track) { if ((Object)(object)track.Part1 == (Object)null || (Object)(object)track.Part2 == (Object)null) { return null; } AudioClip part = track.Part1; AudioClip part2 = track.Part2; if (part.channels != part2.channels || part.frequency != part2.frequency) { Plugin.Log.LogWarning((object)($"[Dropship] '{track.Name}': part1 is {part.channels}ch/{part.frequency}Hz " + $"and part2 is {part2.channels}ch/{part2.frequency}Hz. They have to match " + "to be joined, so the two are played separately and the seam may be audible. Re-export both the same way.")); return null; } try { float[] array = new float[part.samples * part.channels]; float[] array2 = new float[part2.samples * part2.channels]; if (!part.GetData(array, 0) || !part2.GetData(array2, 0)) { return null; } float[] array3 = new float[array.Length + array2.Length]; array.CopyTo(array3, 0); array2.CopyTo(array3, array.Length); AudioClip val = AudioClip.Create("DunderKlumparDropship_" + track.Name + "_whole", part.samples + part2.samples, part.channels, part.frequency, false); val.SetData(array3, 0); Plugin.Log.LogInfo((object)("[Dropship] '" + track.Name + "': joined into one clip of " + $"{val.length:F1}s - the seam is now a sample, not a schedule.")); return val; } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Dropship] '" + track.Name + "': could not join the parts (" + ex.Message + "). Playing them separately instead.")); return null; } } private static void Preload(AudioClip clip) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Invalid comparison between Unknown and I4 if (!((Object)(object)clip == (Object)null) && (int)clip.loadState != 2) { clip.LoadAudioData(); int num = 0; while ((int)clip.loadState == 1 && num++ < 2000000) { } if ((int)clip.loadState != 2) { Plugin.Log.LogWarning((object)("[Dropship] " + ((Object)clip).name + " did not finish decoding up front - the join to the next part may not be exact.")); } } } internal static bool SilencesVanillaMusic(string clipName) { if (Tracks.Count == 0 || !Tweaks.On("DropshipLandingMusic")) { return false; } if (!(clipName == "IcecreamTruckV2")) { return clipName == "IcecreamTruckFar"; } return true; } [HarmonyPostfix] [HarmonyPatch(typeof(ItemDropship), "LandShipClientRpc")] private static void OnShipComingDown(ItemDropship __instance) { try { if (Tweaks.On("DropshipLandingMusic") && Tracks.Count != 0 && !((Object)(object)__instance == (Object)null) && !(Time.realtimeSinceStartup - _lastLanding < 2f)) { _lastLanding = Time.realtimeSinceStartup; int seed = RoundInfo.Seed; if (seed != _countedSeed) { _landings = 0; _countedSeed = seed; } Track track = Pick(_landings); _landings++; if (track != null) { Stop(); Play(track, ((Component)__instance).transform); } } } catch (Exception arg) { Plugin.Log.LogError((object)$"[Dropship] Could not start the landing music: {arg}"); } } private static void Play(Track track, Transform ship) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) _speaker = new GameObject("DunderKlumparDropshipMusic"); _speaker.transform.SetParent(ship, false); _speaker.transform.localPosition = Vector3.zero; double num = AudioSettings.dspTime + 0.1; double num2 = num + 6.267; if ((Object)(object)track.Whole != (Object)null) { AudioSource val = Speaker(track.Whole, 1f, 200f); float length = track.Part1.length; if ((double)length >= 6.267) { double num3 = ((double)length - 6.267) * (double)track.Whole.frequency; val.timeSamples = Mathf.Clamp((int)num3, 0, track.Whole.samples - 1); val.PlayScheduled(num); } else { val.PlayScheduled(num2 - (double)length); } Colour(val, num2); Object.Destroy((Object)(object)_speaker, (float)(num2 - AudioSettings.dspTime) + track.Part2.length + 2f); Plugin.Log.LogInfo((object)($"[Dropship] Landing #{_landings} ← '{track.Name}': one joined clip, " + $"intro {length:F1}s ending on touchdown, then {track.Part2.length:F1}s.")); return; } if ((Object)(object)track.Part1 == (Object)null) { AudioSource val2 = Speaker(track.Part2, 1f, 25f); val2.PlayScheduled(num2); try { DistanceColour distanceColour = ((Component)val2).gameObject.AddComponent<DistanceColour>(); distanceColour.LowPass = ((Component)val2).gameObject.AddComponent<AudioLowPassFilter>(); distanceColour.Reverb = ((Component)val2).gameObject.AddComponent<AudioReverbFilter>(); if ((Object)(object)distanceColour.Reverb != (Object)null) { distanceColour.Reverb.reverbPreset = (AudioReverbPreset)18; } } catch { } Object.Destroy((Object)(object)_speaker, (float)(num2 - AudioSettings.dspTime) + track.Part2.length + 2f); Plugin.Log.LogInfo((object)($"[Dropship] Landing #{_landings} ← '{track.Name}': " + $"no intro, {track.Part2.length:F1}s from touchdown.")); return; } AudioSource val3 = Speaker(track.Part1, 1f, 200f); float length2 = track.Part1.length; if ((double)length2 >= 6.267) { double num4 = ((double)length2 - 6.267) * (double)track.Part1.frequency; val3.timeSamples = Mathf.Clamp((int)num4, 0, track.Part1.samples - 1); val3.PlayScheduled(num); } else { val3.PlayScheduled(num2 - (double)length2); } val3.SetScheduledEndTime(num2); try { AudioReverbFilter val4 = ((Component)val3).gameObject.AddComponent<AudioReverbFilter>(); if ((Object)(object)val4 != (Object)null) { val4.reverbPreset = (AudioReverbPreset)18; } } catch { } IntroFade introFade = ((Component)val3).gameObject.AddComponent<IntroFade>(); introFade.Source = val3; introFade.FullAt = num2; introFade.Seconds = 6.267; try { AudioLowPassFilter val5 = ((Component)val3).gameObject.AddComponent<AudioLowPassFilter>(); if ((Object)(object)val5 != (Object)null) { val5.cutoffFrequency = 500f; introFade.LowPass = val5; } } catch { } double num5 = num2; AudioSource val6 = null; if ((Object)(object)track.Part2 != (Object)null) { val6 = Speaker(track.Part2, 1f, 25f); val6.PlayScheduled(num2); num5 += (double)track.Part2.length; } try { if ((Object)(object)val6 != (Object)null) { AddDistanceColour(((Component)val6).gameObject); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Dropship] No distance filtering (" + ex.Message + ") - the music still plays, it just will not muffle with distance.")); } Object.Destroy((Object)(object)_speaker, (float)(num5 - AudioSettings.dspTime) + 2f); Plugin.Log.LogInfo((object)($"[Dropship] Landing #{_landings} ← '{track.Name}': part1 {length2:F1}s " + (((double)length2 >= 6.267) ? $"(starting {(double)length2 - 6.267:F1}s in, to end on touchdown)" : $"(held back {6.267 - (double)length2:F1}s, to end on touchdown)") + (((Object)(object)track.Part2 != (Object)null) ? $", part2 {track.Part2.length:F1}s from touchdown" : ", no part2"))); } private static void Colour(AudioSource source, double touchdown) { try { AudioReverbFilter val = ((Component)source).gameObject.AddComponent<AudioReverbFilter>(); if ((Object)(object)val != (Object)null) { val.reverbPreset = (AudioReverbPreset)18; val.dryLevel = -600f; val.room = -200f; } } catch { } IntroFade introFade = ((Component)source).gameObject.AddComponent<IntroFade>(); introFade.Source = source; introFade.FullAt = touchdown; introFade.Seconds = 6.267; try { AudioLowPassFilter val2 = ((Component)source).gameObject.AddComponent<AudioLowPassFilter>(); if ((Object)(object)val2 != (Object)null) { val2.cutoffFrequency = 500f; val2.lowpassResonanceQ = 2f; introFade.LowPass = val2; DistanceColour distanceColour = ((Component)source).gameObject.AddComponent<DistanceColour>(); distanceColour.LowPass = val2; distanceColour.Reverb = ((Component)source).gameObject.GetComponent<AudioReverbFilter>(); ((Behaviour)distanceColour).enabled = false; introFade.Ground = distanceColour; } } catch { } } private static void AddDistanceColour(GameObject on) { AudioLowPassFilter val = on.AddComponent<AudioLowPassFilter>(); if (!((Object)(object)val == (Object)null)) { val.cutoffFrequency = 2600f; DistanceColour distanceColour = on.AddComponent<DistanceColour>(); distanceColour.LowPass = val; AudioReverbFilter val2 = on.AddComponent<AudioReverbFilter>(); if ((Object)(object)val2 != (Object)null) { val2.reverbPreset = (AudioReverbPreset)20; distanceColour.Reverb = val2; } } } private static AudioSource Speaker(AudioClip clip, float spatialBlend, float fullVolumeMetres) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Part"); val.transform.SetParent(_speaker.transform, false); val.transform.localPosition = Vector3.zero; AudioSource obj = val.AddComponent<AudioSource>(); obj.clip = clip; obj.playOnAwake = false; obj.spatialBlend = spatialBlend; obj.rolloffMode = (AudioRolloffMode)1; obj.minDistance = fullVolumeMetres; obj.maxDistance = 220f; obj.volume = 1f; return obj; } [HarmonyPostfix] [HarmonyPatch(typeof(ItemDropship), "ShipLeave")] private static void OnShipLeaving() { Stop(); } private static void Stop() { if (!((Object)(object)_speaker == (Object)null)) { try { Object.Destroy((Object)(object)_speaker); } catch { } _speaker = null; } } private static Track Pick(int landing) { if (Tracks.Count == 1) { return Tracks[0]; } int count = Tracks.Count; int num = landing / count; int[] array = new int[count]; for (int i = 0; i < count; i++) { array[i] = i; } uint num2 = SoundReplacement.StableHash("dropship") ^ (uint)RoundInfo.Seed ^ (uint)(num * -1640531535); if (num2 == 0) { num2 = 2463534242u; } for (int num3 = count - 1; num3 > 0; num3--) { num2 = SoundReplacement.NextRandom(num2); int num4 = (int)(num2 % (uint)(num3 + 1)); int num5 = array[num3]; array[num3] = array[num4]; array[num4] = num5; } return Tracks[array[landing % count]]; } } internal static class ForeignConfig { private static string ConfigDir => Paths.ConfigPath; internal static bool Set(string fileName, string key, string value, string why, string section = null) { string path = Path.Combine(ConfigDir, fileName); if (!File.Exists(path)) { Plugin.Log.LogInfo((object)("[Tweaks] " + fileName + " is not here - skipping '" + key + "'.")); return false; } try { string[] array = File.ReadAllLines(path); bool flag = false; bool flag2 = section == null; for (int i = 0; i < array.Length; i++) { string text = array[i]; string text2 = text.TrimStart(); if (text2.StartsWith("[")) { if (section != null) { flag2 = new string(text2.Where((char c) => c >= ' ' && c <= '~').ToArray()).IndexOf(section, StringComparison.OrdinalIgnoreCase) >= 0; } } else { if (text2.StartsWith("#") || !flag2) { continue; } int num = text.IndexOf('='); if (num > 0 && !(text.Substring(0, num).Trim() != key)) { flag = true; if (text.Substring(num + 1).Trim() == value) { return true; } array[i] = key + " = " + value; File.WriteAllLines(path, array, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); Plugin.Log.LogInfo((object)("[Tweaks] " + fileName + ": " + key + " -> " + value + " (" + why + "). Takes effect the next time the game starts.")); return true; } } } if (!flag) { Plugin.Log.LogWarning((object)("[Tweaks] '" + key + "' is not in " + fileName + " any more - that mod has probably renamed it. Nothing changed.")); } return false; } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Tweaks] Could not write " + fileName + ": " + ex.Message)); return false; } } } [HarmonyPatch] internal static class NetcodeFix { private static readonly MethodInfo SetIsSceneObject = AccessTools.PropertySetter(typeof(NetworkObject), "IsSceneObject"); private static bool _reported; [HarmonyPrefix] [HarmonyPatch(typeof(NetworkObject), "HostCheckForGlobalObjectIdHashOverride")] private static void FillInMissingSceneObjectFlag(NetworkObject __instance) { if (!Tweaks.On("FixJoiningWithOldInteriors") || (Object)(object)__instance == (Object)null || __instance.IsSceneObject.HasValue) { return; } if (SetIsSceneObject == null) { if (!_reported) { _reported = true; Plugin.Log.LogError((object)"[JoinFix] NetworkObject.IsSceneObject has no setter any more - Unity's netcode changed. Joining may fail when an old custom interior is installed."); } return; } SetIsSceneObject.Invoke(__instance, new object[1] { true }); if (!_reported) { _reported = true; Plugin.Log.LogInfo((object)("[JoinFix] Filled in a missing IsSceneObject on '" + ((Object)__instance).name + "' so the scene could be sent to a joining player. Old custom interiors are the usual reason.")); } } } [HarmonyPatch] internal static class PaintingPatches { private const string PaintingItemName = "Painting"; private static int _appliedForSeed = int.MinValue; [HarmonyPostfix] [HarmonyPatch(typeof(GrabbableObject), "SetScrapValue")] private static void OnScrapValueSet(GrabbableObject __instance) { try { if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.itemProperties == (Object)null || __instance.itemProperties.itemName != "Painting" || _appliedForSeed == RoundInfo.Seed) { return; } string[] array = Plugin.AssetFiles("paintings"); if (array.Length == 0) { return; } string path = array[new Random(RoundInfo.Seed + 977).Next(array.Length)]; Texture2D mainTexture = Plugin.LoadTexture(path); Material[] materialVariants = __instance.itemProperties.materialVariants; if (materialVariants == null || materialVariants.Length == 0) { return; } Material[] array2 = materialVariants; foreach (Material val in array2) { if ((Object)(object)val != (Object)null) { val.mainTexture = (Texture)(object)mainTexture; } } _appliedForSeed = RoundInfo.Seed; Plugin.Log.LogInfo((object)("Painting ← " + Path.GetFileName(path))); } catch (Exception arg) { Plugin.Log.LogError((object)$"Could not swap the painting: {arg}"); } } } internal class PlayOnAwakeRelay : MonoBehaviour { internal AudioSource Source; private bool _played; private void OnEnable() { _played = false; TryPlay(); } private void OnDisable() { if ((Object)(object)Source != (Object)null && Source.isPlaying) { Source.Stop(); } _played = false; } private void Update() { TryPlay(); } private void TryPlay() { if ((Object)(object)Source == (Object)null || (Object)(object)Source.clip == (Object)null) { _played = false; } else if (!Source.isPlaying) { if (!((Behaviour)Source).isActiveAndEnabled) { _played = false; } else if (!_played) { Source.Play(); _played = true; } } } } internal static class PlayOnAwakePatcher { private const float RescanSeconds = 5f; internal static void Start(MonoBehaviour host) { SceneManager.sceneLoaded += delegate { Convert("scene load"); }; host.StartCoroutine(Rescan()); Convert("startup"); } private static IEnumerator Rescan() { WaitForSeconds wait = new WaitForSeconds(5f); while (true) { yield return wait; Convert(null); } } private static void Convert(string reason) { int num = 0; AudioSource[] array = Object.FindObjectsOfType<AudioSource>(true); foreach (AudioSource val in array) { if (!((Object)(object)val == (Object)null) && val.playOnAwake) { val.playOnAwake = false; (((Component)val).gameObject.GetComponent<PlayOnAwakeRelay>() ?? ((Component)val).gameObject.AddComponent<PlayOnAwakeRelay>()).Source = val; num++; } } if (num > 0 && reason != null) { Plugin.Log.LogInfo((object)$"[Sound] Took over {num} auto-playing sounds ({reason})."); } else if (num > 0) { Plugin.Log.LogInfo((object)$"[Sound] Took over {num} new auto-playing sounds."); } } } [BepInPlugin("nu.dunderpatrullen.dunderklumpar", "DunderKlumpar", "1.2.3")] public class Plugin : BaseUnityPlugin { internal class Host : MonoBehaviour { } public const string PluginGuid = "nu.dunderpatrullen.dunderklumpar"; public const string PluginName = "DunderKlumpar"; public const string PluginVersion = "1.2.3"; internal static ManualLogSource Log; internal static string PluginDir; private void Awake() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_0058: 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) //IL_0063: Expected O, but got Unknown //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; PluginDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); Settings.Init(((BaseUnityPlugin)this).Config); Tweaks.Init(); SharedModSettings.Apply(); SoundCatalog.Init(); Harmony val = new Harmony("nu.dunderpatrullen.dunderklumpar"); val.PatchAll(Assembly.GetExecutingAssembly()); SoundPatcher.Apply(val); GameObject val2 = new GameObject("DunderKlumparHost"); Object.DontDestroyOnLoad((Object)val2); ((Object)val2).hideFlags = (HideFlags)61; PlayOnAwakePatcher.Start((MonoBehaviour)(object)val2.AddComponent<Host>()); val2.AddComponent<DecorWatcher>(); try { SoundReplacement.LoadAll(); DeathSounds.LoadAll(); DropshipMusic.LoadAll(); } catch (Exception arg) { Log.LogError((object)$"Could not load sounds: {arg}"); } Log.LogInfo((object)("DunderKlumpar v1.2.3 loaded! Asset folder: " + PluginDir)); } internal static Texture2D LoadTexture(string path) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown //IL_0015: Expected O, but got Unknown Texture2D val = new Texture2D(2, 2); ImageConversion.LoadImage(val, File.ReadAllBytes(path)); return val; } internal static string[] AssetFiles(string subfolder) { string path = Path.Combine(PluginDir, subfolder); if (!Directory.Exists(path)) { return new string[0]; } return Directory.GetFiles(path, "*.png"); } } internal static class RoundInfo { internal static int Seed { get { StartOfRound instance = StartOfRound.Instance; if (!((Object)(object)instance == (Object)null)) { return instance.randomMapSeed; } return 0; } } } [HarmonyPatch] internal static class SaveRepair { private const string StoredKey = "UnlockedShipObjects"; private const string BackupSuffix = ".dunderklumpar-backup"; [HarmonyPrefix] [HarmonyPatch(typeof(StartOfRound), "LoadUnlockables")] private static void RepairBeforeLoad(StartOfRound __instance) { try { Repair(__instance); } catch (Exception arg) { Plugin.Log.LogError((object)$"[Save] Could not check the save file: {arg}"); } } private static void Repair(StartOfRound round) { if ((Object)(object)round == (Object)null || (Object)(object)round.unlockablesList == (Object)null || round.unlockablesList.unlockables == null) { return; } GameNetworkManager instance = GameNetworkManager.Instance; if ((Object)(object)instance == (Object)null) { return; } string currentSaveFileName = instance.currentSaveFileName; if (string.IsNullOrEmpty(currentSaveFileName) || !ES3.KeyExists("UnlockedShipObjects", currentSaveFileName)) { return; } int[] array = ES3.Load<int[]>("UnlockedShipObjects", currentSaveFileName); if (array == null || array.Length == 0) { return; } int count = round.unlockablesList.unlockables.Count; int[] array2 = array.Where((int i) => i >= 0 && i < count).ToArray(); if (array2.Length != array.Length) { string[] array3 = (from i in array where i < 0 || i >= count select i.ToString()).ToArray(); Backup(currentSaveFileName); ES3.Save<int[]>("UnlockedShipObjects", array2, currentSaveFileName); Plugin.Log.LogWarning((object)($"[Save] '{currentSaveFileName}' pointed at {array3.Length} ship object(s) that do not exist " + string.Format("(index {0}; the list holds {1}). Left behind by a mod ", string.Join(", ", array3), count) + "that is no longer installed. Removed them - otherwise the game aborts loading and the suit rack stays completely empty.")); } } private static void Backup(string saveName) { try { string text = Path.Combine(Application.persistentDataPath, saveName); string text2 = text + ".dunderklumpar-backup"; if (File.Exists(text) && !File.Exists(text2)) { File.Copy(text, text2); Plugin.Log.LogInfo((object)("[Save] Original copied to " + Path.GetFileName(text2) + " before editing.")); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Save] Could not write a backup: " + ex.Message)); } } } [HarmonyPatch] internal static class SaveSlots { private const int TotalSlots = 7; private const float Spacing = 0.44f; private const float SlotScale = 0.42f; private static string NameForSlot(int fileNum) { return "LCSaveFile" + (fileNum + 1); } [HarmonyPostfix] [HarmonyPatch(typeof(SaveFileUISlot), "Awake")] private static void NameTheExtraSlots(SaveFileUISlot __instance, ref string ___fileString) { if (Tweaks.On("MoreSaveFiles") && __instance.fileNum >= 3) { ___fileString = NameForSlot(__instance.fileNum); } } [HarmonyPostfix] [HarmonyPatch(typeof(GameNetworkManager), "Start")] private static void RestoreTheRememberedSlot(GameNetworkManager __instance) { if (Tweaks.On("MoreSaveFiles") && __instance.saveFileNum >= 3) { __instance.currentSaveFileName = NameForSlot(__instance.saveFileNum); Plugin.Log.LogInfo((object)("[SaveSlots] Continuing on " + __instance.currentSaveFileName + ".")); } } [HarmonyPostfix] [HarmonyPatch(typeof(MenuManager), "Start")] private static void AddTheExtraSlots(MenuManager __instance) { if (!Tweaks.On("MoreSaveFiles")) { return; } try { GrowCompatibleList(__instance); BuildExtraButtons(); } catch (Exception arg) { Plugin.Log.LogError((object)$"[SaveSlots] Could not add the extra save files: {arg}"); } } private static void GrowCompatibleList(MenuManager menu) { bool[] filesCompatible = menu.filesCompatible; if (filesCompatible == null || filesCompatible.Length < 7) { bool[] array = new bool[7]; for (int i = 0; i < array.Length; i++) { array[i] = filesCompatible == null || i >= filesCompatible.Length || filesCompatible[i]; } menu.filesCompatible = array; } } private static void BuildExtraButtons() { //IL_008a: 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_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Expected O, but got Unknown //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) SaveFileUISlot[] array = Object.FindObjectsOfType<SaveFileUISlot>(true); SaveFileUISlot val = null; SaveFileUISlot val2 = null; SaveFileUISlot val3 = null; SaveFileUISlot[] array2 = array; foreach (SaveFileUISlot val4 in array2) { if (val4.fileNum == 0) { val = val4; } if (val4.fileNum == 1) { val2 = val4; } if (val4.fileNum == 2) { val3 = val4; } if (val4.fileNum >= 3) { return; } } if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null) { Plugin.Log.LogWarning((object)"[SaveSlots] Could not find the game's three save buttons - leaving the menu alone."); return; } Vector2 anchoredPosition = ((RectTransform)((Component)val).transform).anchoredPosition; Vector2 val5 = ((RectTransform)((Component)val2).transform).anchoredPosition - anchoredPosition; Transform parent = ((Component)val3).transform.parent; Component[] components = ((Component)parent).GetComponents<Component>(); foreach (Component val6 in components) { Behaviour val7 = (Behaviour)(object)((val6 is Behaviour) ? val6 : null); if (val7 != null && ((object)val6).GetType().Name.Contains("LayoutGroup")) { val7.enabled = false; Plugin.Log.LogInfo((object)("[SaveSlots] Switched off " + ((object)val6).GetType().Name + " so the buttons stay where we put them.")); } } SaveFileUISlot[] array3 = (SaveFileUISlot[])(object)new SaveFileUISlot[7] { val, val2, val3, default(SaveFileUISlot), default(SaveFileUISlot), default(SaveFileUISlot), default(SaveFileUISlot) }; for (int j = 3; j < 7; j++) { GameObject val8 = Object.Instantiate<GameObject>(((Component)val3).gameObject, parent); ((Object)val8).name = "FileSlot" + (j + 1); SaveFileUISlot component = val8.GetComponent<SaveFileUISlot>(); component.fileNum = j; AccessTools.Field(typeof(SaveFileUISlot), "fileString").SetValue(component, NameForSlot(j)); RelabelSlot(val8, j); PointDeleteButtonAtThisSlot(val8, j); if (val8.activeSelf) { val8.SetActive(false); val8.SetActive(true); } array3[j] = component; } for (int k = 0; k < 7; k++) { RectTransform val9 = (RectTransform)((Component)array3[k]).transform; val9.anchoredPosition = anchoredPosition + val5 * 0.44f * (float)k; ((Transform)val9).localScale = Vector3.one * 0.42f; Plugin.Log.LogInfo((object)$"[SaveSlots] Slot {k + 1} ({NameForSlot(k)}) at {val9.anchoredPosition}."); } Plugin.Log.LogInfo((object)$"[SaveSlots] Ready: {7} save files."); } private static void RelabelSlot(GameObject copy, int fileNum) { string text = "File " + (fileNum + 1); TextMeshProUGUI[] componentsInChildren = copy.GetComponentsInChildren<TextMeshProUGUI>(true); foreach (TextMeshProUGUI val in componentsInChildren) { string text2 = ((TMP_Text)val).text ?? ""; if (text2.TrimStart().StartsWith("File ", StringComparison.OrdinalIgnoreCase)) { ((TMP_Text)val).text = text; Plugin.Log.LogInfo((object)$"[SaveSlots] Slot {fileNum + 1} relabelled '{text2.Trim()}' -> '{text}'."); } } } private static void PointDeleteButtonAtThisSlot(GameObject copy, int fileNum) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown DeleteFileButton prompt = Object.FindObjectOfType<DeleteFileButton>(true); if ((Object)(object)prompt == (Object)null) { Plugin.Log.LogWarning((object)"[SaveSlots] No DeleteFileButton in the menu - the extra files cannot be deleted from here."); return; } Button[] componentsInChildren = copy.GetComponentsInChildren<Button>(true); foreach (Button val in componentsInChildren) { bool flag = false; for (int j = 0; j < ((UnityEventBase)val.onClick).GetPersistentEventCount(); j++) { if (((UnityEventBase)val.onClick).GetPersistentTarget(j) is DeleteFileButton) { flag = true; } } if (!flag) { continue; } int slotNumber = fileNum; ((UnityEvent)val.onClick).AddListener((UnityAction)delegate { prompt.fileToDelete = slotNumber; if ((Object)(object)prompt.deleteFileText != (Object)null) { ((TMP_Text)prompt.deleteFileText).text = $"Do you want to delete File {slotNumber + 1}?"; } Plugin.Log.LogInfo((object)("[SaveSlots] Delete asked for " + NameForSlot(slotNumber) + ".")); }); Plugin.Log.LogInfo((object)($"[SaveSlots] Delete button on slot {fileNum + 1} will ask for " + NameForSlot(fileNum) + ".")); } } [HarmonyPrefix] [HarmonyPatch(typeof(DeleteFileButton), "DeleteFile")] private static bool DeleteAnExtraFile(DeleteFileButton __instance) { if (!Tweaks.On("MoreSaveFiles")) { return true; } if (__instance.fileToDelete < 3 || __instance.fileToDelete >= 7) { return true; } int fileToDelete = __instance.fileToDelete; string text = NameForSlot(fileToDelete); try { MenuManager val = Object.FindObjectOfType<MenuManager>(); if (ES3.FileExists(text)) { ES3.DeleteFile(text); if ((Object)(object)val != (Object)null && (Object)(object)val.MenuAudio != (Object)null && (Object)(object)__instance.deleteFileSFX != (Object)null) { val.MenuAudio.PlayOneShot(__instance.deleteFileSFX); } Plugin.Log.LogInfo((object)("[SaveSlots] Deleted " + text + ".")); } if ((Object)(object)val != (Object)null && val.filesCompatible != null && fileToDelete < val.filesCompatible.Length) { val.filesCompatible[fileToDelete] = true; } SaveFileUISlot[] array = Object.FindObjectsOfType<SaveFileUISlot>(true); foreach (SaveFileUISlot val2 in array) { if (val2.fileNum == fileToDelete) { if ((Object)(object)val2.fileNotCompatibleAlert != (Object)null) { ((Behaviour)val2.fileNotCompatibleAlert).enabled = false; } if (((Component)val2).gameObject.activeSelf) { ((Component)val2).gameObject.SetActive(false); ((Component)val2).gameObject.SetActive(true); } } } } catch (Exception arg) { Plugin.Log.LogError((object)$"[SaveSlots] Could not delete {text}: {arg}"); } return false; } } internal static class Settings { private const string MarkerFile = "dev-tools.txt"; internal static ConfigEntry<bool> LogSounds; internal static ConfigEntry<bool> ExportOriginalSounds; internal static ConfigEntry<bool> DumpAllSounds; internal static ConfigEntry<bool> ExportSuitTemplate; internal static ConfigEntry<bool> TestMode; internal static bool On(ConfigEntry<bool> entry) { return entry?.Value ?? false; } internal static void Init(ConfigFile cfg) { if (!File.Exists(Path.Combine(Plugin.PluginDir, "dev-tools.txt"))) { Plugin.Log.LogInfo((object)"[Config] No settings registered - this build has nothing for players to configure."); return; } Plugin.Log.LogInfo((object)"[Config] dev-tools.txt found - authoring tools enabled."); LogSounds = cfg.Bind<bool>("Authoring", "LogSoundNames", true, "Log the name of every sound the game plays, and collect them in 'discovered_sounds.txt'. Use those names as filenames in sounds/ to replace a sound."); ExportOriginalSounds = cfg.Bind<bool>("Authoring", "ExportOriginalSounds", false, "Save the game's original sounds as .wav in 'original_sounds/' the first time each one plays. Prefer tools/extract_game_sounds.py, which gets the whole library at once."); DumpAllSounds = cfg.Bind<bool>("Authoring", "DumpAllSounds", false, "Dump ALL of the game's sounds at once into 'original_sounds/' when a round starts. Misses anything the game streams from disk - tools/extract_game_sounds.py does not."); TestMode = cfg.Bind<bool>("Authoring", "TestMode", false, "Lay one of every item on the floor by the ship door and top up the credits, so anything can be picked up and heard without playing for it. Host only, once per launch. For testing the mod - not something to leave on while actually playing."); ExportSuitTemplate = cfg.Bind<bool>("Authoring", "ExportSuitTemplate", true, "Write the game's own suit texture to suits/_TEMPLATE.png (only if it is missing), so you have the correct layout to paint over. Files starting with '_' are never loaded as suits."); } } internal static class SharedModSettings { private const int ClearWeatherWeight = 700; internal static void Apply() { if (Tweaks.On("UnlockAllMoons")) { ForeignConfig.Set("JacobG5.WesleyMoonScripts.cfg", "LockMoons", "false", "so every moon can be flown to from a new save instead of being unlocked through the campaign"); ForeignConfig.Set("LethalLevelLoader.cfg", "Moons Catalogue Group Split Count", "100", "so the terminal lists every moon at once instead of a page at a time"); } if (Tweaks.On("VanillaWeather")) { ForeignConfig.Set("mrov.WeatherRegistry.cfg", "Weather Selection Algorithm", "Registry", "so the weather weights below are actually used"); ForeignConfig.Set("mrov.WeatherRegistry.cfg", "Default weight", 700.ToString(), "so clear skies are about as common as in the base game instead of one chance in eight", "Vanilla Weather: None"); } } } [HarmonyPatch] internal static class ShipDecorPatches { private const string PlanePath = "HangarShip/Plane.001"; private const int SheetSize = 1024; private static readonly RectInt[] Places = (RectInt[])(object)new RectInt[7] { new RectInt(2, 745, 337, 276), new RectInt(2, 468, 337, 276), new RectInt(347, 744, 281, 277), new RectInt(347, 467, 281, 277), new RectInt(641, 724, 273, 241), new RectInt(634, 33, 368, 670), new RectInt(184, 38, 410, 364) }; private static Random _rng = new Random(0); private static int _appliedSeed = int.MinValue; [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "Start")] private static void OnShipStart() { ApplyDecor("StartOfRound.Start", verbose: true); SoundDump.DumpIfEnabled(); } [HarmonyPostfix] [HarmonyPatch(typeof(RoundManager), "GenerateNewLevelClientRpc")] private static void OnNewLevel() { ApplyDecor("RoundManager.GenerateNewLevelClientRpc", verbose: true); } internal static void EnsureApplied() { if (RoundInfo.Seed != _appliedSeed) { ApplyDecor("seed arrived", verbose: false); } } private static void ApplyDecor(string source, bool verbose) { try { int seed = RoundInfo.Seed; _rng = new Random(seed); GameObject val = GameObject.Find("HangarShip/Plane.001"); if ((Object)(object)val == (Object)null) { if (verbose) { Plugin.Log.LogWarning((object)("[" + source + "] Could not find 'HangarShip/Plane.001' - skipping posters.")); } return; } MeshRenderer component = val.GetComponent<MeshRenderer>(); if ((Object)(object)component == (Object)null) { if (verbose) { Plugin.Log.LogWarning((object)("[" + source + "] 'HangarShip/Plane.001' has no MeshRenderer.")); } } else { Material[] materials = ((Renderer)component).materials; ApplyPosters(materials, 0, seed); SwapMaterialTexture(materials, 1, "tips"); ((Renderer)component).materials = materials; _appliedSeed = seed; } } catch (Exception arg) { Plugin.Log.LogError((object)$"[{source}] Error swapping posters: {arg}"); } } private static void ApplyPosters(Material[] mats, int index, int seed) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) if (index >= mats.Length) { return; } string[] array = Plugin.AssetFiles("posters"); if (array.Length == 0) { Plugin.Log.LogWarning((object)"No images in posters/ - the ship wall will be blank."); return; } Array.Sort(array, (IComparer<string>?)StringComparer.OrdinalIgnoreCase); int[] array2 = ShuffledOrder(array.Length, seed); Texture2D val = new Texture2D(1024, 1024, (TextureFormat)4, false); val.SetPixels32((Color32[])(object)new Color32[1048576]); for (int i = 0; i < Places.Length; i++) { string path = array[array2[i % array2.Length]]; DrawFitted(val, Plugin.LoadTexture(path), Places[i]); Plugin.Log.LogInfo((object)$"Poster place {i + 1} ← {Path.GetFileName(path)}"); } val.Apply(); mats[index].mainTexture = (Texture)(object)val; } private static int[] ShuffledOrder(int count, int seed) { int[] array = new int[count]; for (int i = 0; i < count; i++) { array[i] = i; } uint num = SoundReplacement.StableHash("posters") ^ (uint)seed; if (num == 0) { num = 2463534242u; } for (int num2 = count - 1; num2 > 0; num2--) { num = SoundReplacement.NextRandom(num); int num3 = (int)(num % (uint)(num2 + 1)); int num4 = array[num2]; array[num2] = array[num3]; array[num3] = num4; } return array; } private static void DrawFitted(Texture2D sheet, Texture2D image, RectInt slot) { //IL_00af: 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) float num = Mathf.Min((float)((RectInt)(ref slot)).width / (float)((Texture)image).width, (float)((RectInt)(ref slot)).height / (float)((Texture)image).height); int num2 = Mathf.Max(1, Mathf.RoundToInt((float)((Texture)image).width * num)); int num3 = Mathf.Max(1, Mathf.RoundToInt((float)((Texture)image).height * num)); int num4 = ((RectInt)(ref slot)).x + (((RectInt)(ref slot)).width - num2) / 2; int num5 = ((RectInt)(ref slot)).y + (((RectInt)(ref slot)).height - num3) / 2; Color[] array = (Color[])(object)new Color[num2 * num3]; for (int i = 0; i < num3; i++) { for (int j = 0; j < num2; j++) { array[i * num2 + j] = image.GetPixelBilinear(((float)j + 0.5f) / (float)num2, ((float)i + 0.5f) / (float)num3); } } sheet.SetPixels(num4, num5, num2, num3, array); } private static void SwapMaterialTexture(Material[] mats, int index, string subfolder) { if (index < mats.Length) { string[] array = Plugin.AssetFiles(subfolder); if (array.Length == 0) { Plugin.Log.LogWarning((object)$"No images in '{subfolder}/' - keeping original for material {index}."); return; } string path = array[_rng.Next(array.Length)]; mats[index].mainTexture = (Texture)(object)Plugin.LoadTexture(path); Plugin.Log.LogInfo((object)$"Material {index} ({subfolder}) ← {Path.GetFileName(path)}"); } } } internal class DecorWatcher : MonoBehaviour { private const float IntervalSeconds = 2f; private float _next; private void Update() { if (!(Time.unscaledTime < _next)) { _next = Time.unscaledTime + 2f; ShipDecorPatches.EnsureApplied(); } } } internal static class SoundCatalog { private static readonly HashSet<string> Seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static string _catalogPath; private static string _exportDir; internal static void Init() { _catalogPath = Path.Combine(Plugin.PluginDir, "discovered_sounds.txt"); _exportDir = Path.Combine(Plugin.PluginDir, "original_sounds"); } internal static void Note(AudioClip clip) { if (!((Object)(object)clip == (Object)null) && !string.IsNullOrEmpty(((Object)clip).name) && Seen.Add(((Object)clip).name)) { if (Settings.On(Settings.LogSounds)) { Plugin.Log.LogInfo((object)("[SoundScan] " + ((Object)clip).name)); TryAppendCatalog(clip); } if (Settings.On(Settings.ExportOriginalSounds)) { TryExport(clip); } } } private static void TryAppendCatalog(AudioClip clip) { try { string text = (SoundReplacement.Variants.ContainsKey(((Object)clip).name) ? " [REPLACED]" : ""); File.AppendAllText(_catalogPath, $"{((Object)clip).name}{new string(' ', Math.Max(1, 40 - ((Object)clip).name.Length))}{clip.length:F1}s{text}{Environment.NewLine}", new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[SoundScan] Could not write catalogue: " + ex.Message)); } } private static void TryExport(AudioClip clip) { try { Directory.CreateDirectory(_exportDir); string path = Path.Combine(_exportDir, SafeName(((Object)clip).name) + ".wav"); if (!File.Exists(path)) { float[] array = new float[clip.samples * clip.channels]; if (!clip.GetData(array, 0)) { Plugin.Log.LogWarning((object)("[Export] Could not read audio data for " + ((Object)clip).name + " (streamed clip?).")); return; } File.WriteAllBytes(path, ToWav(array, clip.channels, clip.frequency)); Plugin.Log.LogInfo((object)("[Export] Saved original: original_sounds/" + Path.GetFileName(path))); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Export] Failed for " + ((Object)clip).name + ": " + ex.Message)); } } private static string SafeName(string name) { char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { name = name.Replace(oldChar, '_'); } return name; } private static byte[] ToWav(float[] samples, int channels, int frequency) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); int num = samples.Length * 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(frequency); binaryWriter.Write(frequency * channels * 2); binaryWriter.Write((short)(channels * 2)); binaryWriter.Write((short)16); binaryWriter.Write(Encoding.ASCII.GetBytes("data")); binaryWriter.Write(num); foreach (float num2 in samples) { binaryWriter.Write((short)(Mathf.Clamp(num2, -1f, 1f) * 32767f)); } binaryWriter.Flush(); return memoryStream.ToArray(); } } internal static class SoundDump { private static bool _done; internal static void DumpIfEnabled() { if (_done || !Settings.On(Settings.DumpAllSounds)) { return; } _done = true; try { Dump(); } catch (Exception arg) { Plugin.Log.LogError((object)$"[Dump] Failed: {arg}"); } } private static void Dump() { string text = Path.Combine(Plugin.PluginDir, "original_sounds"); Directory.CreateDirectory(text); HashSet<AudioClip> ours = new HashSet<AudioClip>(from v in SoundReplacement.Variants.Values.SelectMany((List<SoundVariant> v) => v) select v.Clip); List<AudioClip> list = (from g in (from c in Resources.FindObjectsOfTypeAll<AudioClip>() where (Object)(object)c != (Object)null && !string.IsNullOrEmpty(((Object)c).name) && !ours.Contains(c) select c).GroupBy<AudioClip, string>((AudioClip c) => ((Object)c).name, StringComparer.OrdinalIgnoreCase) select g.First()).OrderBy<AudioClip, string>((AudioClip c) => ((Object)c).name, StringComparer.OrdinalIgnoreCase).ToList(); Plugin.Log.LogInfo((object)$"[Dump] Found {list.Count} sounds in the game. Exporting to original_sounds/ ..."); List<(string, float, bool)> list2 = new List<(string, float, bool)>(); int num = 0; int num2 = 0; foreach (AudioClip item in list) { bool flag = TryExportWav(item, text); if (flag) { num++; } else { num2++; } list2.Add((((Object)item).name, item.length, flag)); } WriteHtmlIndex(list2); Plugin.Log.LogInfo((object)($"[Dump] Done: {num} sounds saved, {num2} could not be read. " + "See all_sounds.html for the full list.")); } private static bool TryExportWav(AudioClip clip, string dir) { try { string path = Path.Combine(dir, SafeName(((Object)clip).name) + ".wav"); if (File.Exists(path)) { return true; } float[] array = new float[clip.samples * clip.channels]; if (array.Length == 0 || !clip.GetData(array, 0)) { return false; } File.WriteAllBytes(path, WavEncoder.Encode(array, clip.channels, clip.frequency)); return true; } catch { return false; } } internal static string SafeName(string name) { char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { name = name.Replace(oldChar, '_'); } return name; } private static void WriteHtmlIndex(List<(string Name, float Length, bool Exported)> rows) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("<!doctype html><html lang=\"en\"><meta charset=\"utf-8\">\n<title>DunderKlumpar - all sounds in the game</title>\n<style>\n body{font:15px/1.5 system-ui,sans-serif;margin:2rem;background:#14161a;color:#e6e6e6}\n h1{font-size:1.4rem;margin:0 0 .25rem} p{color:#9aa0a6;margin:.25rem 0 1rem}\n input{width:100%;padding:.7rem .9rem;font-size:1rem;border-radius:8px;border:1px solid #333;\n background:#1c1f24;color:#eee;margin-bottom:1rem}\n table{border-collapse:collapse;width:100%} th,td{text-align:left;padding:.45rem .6rem;border-bottom:1px solid #262a30}\n th{position:sticky;top:0;background:#14161a;color:#9aa0a6;font-weight:600}\n code{background:#1c1f24;padding:.15rem .4rem;border-radius:4px;color:#7fd1b9}\n .name{font-family:ui-monospace,Consolas,monospace;color:#7fd1b9}\n .no{color:#e0794f} .yes{color:#6bbf59} .rep{color:#f0c674;font-weight:600}\n tr:hover{background:#1a1d22}\n</style>\n<h1>All sounds in Lethal Company</h1>\n<p>Name your audio file <b>exactly</b> like the Sound name column (e.g. <code>StartGameLever.ogg</code>)\nand put it in <code>sounds/</code>. Add <code>-ver01</code>, <code>-ver02</code>… to have the game\npick randomly between several versions. Saved originals are the .wav files in <code>original_sounds/</code>.</p>\n<input id=\"q\" placeholder=\"Search sounds... (e.g. scream, door, lever)\" autofocus>\n<table><thead><tr><th>Sound name</th><th>Length</th><th>Saved as .wav</th><th></th></tr></thead><tbody id=\"t\">\n"); foreach (var row in rows) { string text = (SoundReplacement.Variants.ContainsKey(row.Name) ? "<span class=\"rep\">REPLACED</span>" : ""); string text2 = (row.Exported ? "<span class=\"yes\">yes</span>" : "<span class=\"no\">no (streamed)</span>"); stringBuilder.Append("<tr><td class=\"name\">" + Escape(row.Name) + "</td><td>" + row.Length.ToString("F1", CultureInfo.InvariantCulture) + "s</td><td>" + text2 + "</td><td>" + text + "</td></tr>\n"); } stringBuilder.Append("</tbody></table>\n<script>\n const q=document.getElementById('q'),rows=[...document.querySelectorAll('#t tr')];\n q.addEventListener('input',()=>{const v=q.value.toLowerCase();\n rows.forEach(r=>r.style.display=r.textContent.toLowerCase().includes(v)?'':'none')});\n</script></html>"); string text3 = Path.Combine(Plugin.PluginDir, "all_sounds.html"); File.WriteAllText(text3, stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); Plugin.Log.LogInfo((object)("[Dump] Wrote list: " + text3)); } private static string Escape(string s) { return s.Replace("&", "&").Replace("<", "<").Replace(">", ">"); } } internal static class WavEncoder { internal static byte[] Encode(float[] samples, int channels, int frequency) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); int num = samples.Length * 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(frequency); binaryWriter.Write(frequency * channels * 2); binaryWriter.Write((short)(channels * 2)); binaryWriter.Write((short)16); binaryWriter.Write(Encoding.ASCII.GetBytes("data")); binaryWriter.Write(num); foreach (float num2 in samples) { binaryWriter.Write((short)(Mathf.Clamp(num2, -1f, 1f) * 32767f)); } binaryWriter.Flush(); return memoryStream.ToArray(); } } internal class SoundVariant { internal AudioClip Clip; internal string File; } internal static class SoundReplacement { internal static readonly Dictionary<string, List<SoundVariant>> Variants = new Dictionary<string, List<SoundVariant>>(StringComparer.OrdinalIgnoreCase); private static readonly Regex VariantSuffix = new Regex("-ver\\d+$", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Dictionary<string, int> Plays = new Dictionary<string, int>(); private static int _playsSeed = int.MinValue; internal static void LoadAll() { //IL_008d: Unknown result type (might be due to invalid IL or missing references) string text = Path.Combine(Plugin.PluginDir, "sounds"); if (!Directory.Exists(text)) { Plugin.Log.LogWarning((object)("[Sound] No sounds/ folder (" + text + ") - skipping sound loading.")); return; } string[] files = Directory.GetFiles(text); Plugin.Log.LogInfo((object)$"[Sound] Found {files.Length} files in sounds/."); string[] array = files; foreach (string text2 in array) { AudioType? val = DetectAudioType(text2); if (!val.HasValue) { continue; } string text3 = ParseName(Path.GetFileNameWithoutExtension(text2)); AudioClip val2 = LoadFromDisk(text2, val.Value); if (!((Object)(object)val2 == (Object)null)) { ((Object)val2).name = text3; if (!Variants.TryGetValue(text3, out var value)) { value = (Variants[text3] = new List<SoundVariant>()); } value.Add(new SoundVariant { Clip = val2, File = Path.GetFileName(text2) }); Plugin.Log.LogInfo((object)$"[Sound] {text3} ← {Path.GetFileName(text2)} ({val2.length:F1}s)"); } } int num = Variants.Count((KeyValuePair<string, List<SoundVariant>> kv) => kv.Value.Count > 1); Plugin.Log.LogInfo((object)($"[Sound] Ready: {Variants.Count} sounds can be replaced" + ((num > 0) ? $", {num} of which pick randomly between versions." : "."))); } private static string ParseName(string fileName) { Match match = VariantSuffix.Match(fileName); if (!match.Success) { return fileName; } return fileName.Substring(0, match.Index); } internal static AudioClip Pick(string clipName) { if (clipName == null || !Variants.TryGetValue(clipName, out var value) || value.Count == 0) { return null; } if (value.Count == 1) { return value[0].Clip; } int seed = RoundInfo.Seed; if (seed != _playsSeed) { Plays.Clear(); _playsSeed = seed; } Plays.TryGetValue(clipName, out var value2); Plays[clipName] = value2 + 1; int[] array = ShuffledOrder(clipName, seed, value2 / value.Count, value.Count); return value[array[value2 % value.Count]].Clip; } private static int[] ShuffledOrder(string clipName, int seed, int cycle, int count) { if (count == 2) { return Shuffle(clipName, seed, 0, count); } int[] array = Shuffle(clipName, seed, cycle, count); int[] array2 = Shuffle(clipName, seed, cycle + 1, count); if (array[count - 1] == array2[0]) { int num = array[count - 1]; array[count - 1] = array[count - 2]; array[count - 2] = num; } return array; } private static int[] Shuffle(string clipName, int seed, int cycle, int count) { int[] array = new int[count]; for (int i = 0; i < count; i++) { array[i] = i; } uint num = StableHash(clipName) ^ (uint)seed ^ (uint)(cycle * -1640531535); if (num == 0) { num = 2463534242u; } for (int num2 = count - 1; num2 > 0; num2--) { num = NextRandom(num); int num3 = (int)(num % (uint)(num2 + 1)); int num4 = array[num2]; array[num2] = array[num3]; array[num3] = num4; } return array; } internal static uint NextRandom(uint state) { state ^= state << 13; state ^= state >> 17; state ^= state << 5; if (state != 0) { return state; } return 2463534242u; } internal static uint StableHash(string text) { uint num = 2166136261u; foreach (char c in text) { num ^= char.ToLowerInvariant(c); num *= 16777619; } return num; } internal static AudioType? DetectAudioType(string file) { return Path.GetExtension(file).ToLowerInvariant() switch { ".ogg" => (AudioType)14, ".wav" => (AudioType)20, ".mp3" => (AudioType)13, _ => null, }; } internal static AudioClip LoadFromDisk(string path, AudioType type) { //IL_0001: 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_001e: Invalid comparison between Unknown and I4 UnityWebRequest audioClip = UnityWebRequestMultimedia.GetAudioClip(path, type); try { audioClip.SendWebRequest(); try { while (!audioClip.isDone) { } if ((int)audioClip.result != 1) { Plugin.Log.LogError((object)("[Sound] Could not load " + Path.GetFileName(path) + ": " + audioClip.error)); return null; } return DownloadHandlerAudioClip.GetContent(audioClip); } catch (Exception ex) { Plugin.Log.LogError((object)("[Sound] Error loading " + Path.GetFileName(path) + ": " + ex.Message)); return null; } } finally { ((IDisposable)audioClip)?.Dispose(); } } } internal static class SoundPatcher { internal static void Apply(Harmony harmony) { Type typeFromHandle = typeof(AudioSource); Type typeFromHandle2 = typeof(SoundPatcher); Hook(harmony, AccessTools.Method(typeFromHandle, "Play", new Type[0], (Type[])null), typeFromHandle2, "OnSourcePlay"); Hook(harmony, AccessTools.Method(typeFromHandle, "Play", new Type[1] { typeof(ulong) }, (Type[])null), typeFromHandle2, "OnSourcePlay"); Hook(harmony, AccessTools.Method(typeFromHandle, "PlayDelayed", new Type[1] { typeof(float) }, (Type[])null), typeFromHandle2, "OnSourcePlay"); Hook(harmony, AccessTools.Method(typeFromHandle, "PlayScheduled", new Type[1] { typeof(double) }, (Type[])null), typeFromHandle2, "OnSourcePlay"); Hook(harmony, AccessTools.Method(typeFromHandle, "PlayOneShotHelper", new Type[3] { typeof(AudioSource), typeof(AudioClip), typeof(float) }, (Type[])null), typeFromHandle2, "OnPlayOneShot"); Hook(harmony, AccessTools.Method(typeFromHandle, "PlayClipAtPoint", new Type[3] { typeof(AudioClip), typeof(Vector3), typeof(float) }, (Type[])null), typeFromHandle2, "OnClipAtPoint"); } private static void Hook(Harmony harmony, MethodBase target, Type owner, string prefix) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown if (target == null) { Plugin.Log.LogWarning((object)("[Sound] Could not find AudioSource method for " + prefix + " - skipped.")); return; } try { harmony.Patch(target, new HarmonyMethod(AccessTools.Method(owner, prefix, (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Sound] Could not patch " + target.Name + ": " + ex.Message)); } } private static bool OnSourcePlay(AudioSource __instance) { if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.clip == (Object)null) { return true; } SoundCatalog.Note(__instance.clip); if (DropshipMusic.SilencesVanillaMusic(((Object)__instance.clip).name)) { return false; } AudioClip val = SoundReplacement.Pick(((Object)__instance.clip).name); if ((Object)(object)val != (Object)null) { __instance.clip = val; } return true; } private static void OnPlayOneShot(AudioSource source, ref AudioClip clip) { if (!((Object)(object)clip == (Object)null)) { SoundCatalog.Note(clip); AudioClip val = SoundReplacement.Pick(((Object)clip).name); if ((Object)(object)val != (Object)null) { clip = val; } } } private static void OnClipAtPoint(ref AudioClip clip) { if (!((Object)(object)clip == (Object)null)) { SoundCatalog.Note(clip); AudioClip val = SoundReplacement.Pick(((Object)clip).name); if ((Object)(object)val != (Object)null) { clip = val; } } } } [HarmonyPatch(typeof(StartOfRound))] internal static class SuitLoader { private static bool _added; [HarmonyPatch("Start")] [HarmonyPrefix] private static void AddSuits(ref StartOfRound __instance) { if (_added) { return; } try { Add(__instance); } catch (Exception arg) { Plugin.Log.LogError((object)$"[Suits] Failed: {arg}"); } } private static void Add(StartOfRound round) { //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) string text = Path.Combine(Plugin.PluginDir, "suits"); if (!Directory.Exists(text)) { Plugin.Log.LogInfo((object)"[Suits] No suits/ folder - skipping."); _added = true; return; } List<string> list = (from p in Directory.GetFiles(text, "*.png") where !Path.GetFileName(p).StartsWith("_") select p).OrderBy<string, string>((string p) => Path.GetFileName(p), StringComparer.OrdinalIgnoreCase).ToList(); if (list.Count == 0) { _added = true; return; } UnlockableItem val = ((IEnumerable<UnlockableItem>)round.unlockablesList.unlockables).FirstOrDefault((Func<UnlockableItem, bool>)((UnlockableItem u) => (Object)(object)u.suitMaterial != (Object)null && u.alreadyUnlocked)); if (val == null) { Plugin.Log.LogWarning((object)"[Suits] Could not find the game's default suit - skipping."); return; } if (Settings.On(Settings.ExportSuitTemplate)) { ExportTemplate(val.suitMaterial, text); } int num = 0; foreach (string item in list) { try { string text2 = DisplayName(Path.GetFileNameWithoutExtension(item)); UnlockableItem val2 = JsonUtility.FromJson<UnlockableItem>(JsonUtility.ToJson((object)val)); Material val3 = Object.Instantiate<Material>(val2.suitMaterial); val3.mainTexture = (Texture)(object)LoadTexture(item); val3.SetFloat("_NormalScale", 0f); ApplyAdvanced(val3, text, text2); val2.suitMaterial = val3; val2.unlockableName = text2; val2.alreadyUnlocked = true; val2.hasBeenMoved = false; val2.placedPosition = Vector3.zero; val2.placedRotation = Vector3.zero; round.unlockablesList.unlockables.Add(val2); num++; Plugin.Log.LogInfo((object)("[Suits] Added suit: " + text2)); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Suits] Could not add " + Path.GetFileName(item) + ": " + ex.Message)); } } _added = true; Plugin.Log.LogInfo((object)$"[Suits] Ready: {num} suits added."); } private static string DisplayName(string fileName) { int num = fileName.IndexOf('-'); if (num <= 0 || num == fileName.Length - 1) { return fileName; } return fileName.Substring(num + 1).Trim(); } private static void ExportTemplate(Material source, string suitsDir) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) string path = Path.Combine(suitsDir, "_TEMPLATE.png"); if (File.Exists(path)) { return; } Texture mainTexture = source.mainTexture; if ((Object)(object)mainTexture == (Object)null) { return; } RenderTexture temporary = RenderTexture.GetTemporary(mainTexture.width, mainTexture.height, 0, (RenderTextureFormat)7, (RenderTextureReadWrite)2); RenderTexture active = RenderTexture.active; try { Graphics.Blit(mainTexture, temporary); RenderTexture.active = temporary; Texture2D val = new Texture2D(mainTexture.width, mainTexture.height, (TextureFormat)4, false); val.ReadPixels(new Rect(0f, 0f, (float)((Texture)temporary).width, (float)((Texture)temporary).height), 0, 0); val.Apply(); File.WriteAllBytes(path, ImageConversion.EncodeToPNG(val)); Plugin.Log.LogInfo((object)$"[Suits] Wrote template: suits/_TEMPLATE.png ({mainTexture.width}x{mainTexture.height})"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Suits] Could not write template: " + ex.Message)); } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); } } private static Texture2D LoadTexture(string path) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown Texture2D val = new Texture2D(2, 2); ImageConversion.LoadImage(val, File.ReadAllBytes(path)); val.Apply(true, true); return val; } private static void ApplyAdvanced(Material material, string suitsDir, string suitName) { //IL_017d: Unknown result type (might be due to invalid IL or missing references) string path = Path.Combine(suitsDir, "advanced", suitName + ".json"); if (!File.Exists(path)) { return; } string[] array = File.ReadAllLines(path); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Trim().Split(':'); if (array2.Length != 2) { continue; } string text = array2[0].Trim('"', ' ', ','); string text2 = array2[1].Trim('"', ' ', ','); if (text.Length == 0 || text2.Length == 0) { continue; } try { float result; Vector4 v; if (text2.EndsWith(".png", StringComparison.OrdinalIgnoreCase)) { string path2 = Path.Combine(suitsDir, "advanced", text2); if (File.Exists(path2)) { material.SetTexture(text, (Texture)(object)LoadTexture(path2)); } else { Plugin.Log.LogWarning((object)("[Suits] " + suitName + ": missing texture " + text2)); } } else if (text2 == "KEYWORD") { material.EnableKeyword(text); } else if (text2 == "DISABLEKEYWORD") { material.DisableKeyword(text); } else if (text == "SHADER") { Shader val = Shader.Find(text2); if ((Object)(object)val != (Object)null) { material.shader = val; } } else if (float.TryParse(text2, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { material.SetFloat(text, result); } else if (TryParseVector(text2, out v)) { material.SetVector(text, v); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Suits] " + suitName + ": could not apply '" + text + "': " + ex.Message)); } } } private static bool TryParseVector(string s, out Vector4 v) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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_006c: Unknown result type (might be due to invalid IL or missing references) v = Vector4.zero; string[] array = s.Split(','); if (array.Length < 2 || array.Length > 4) { return false; } float[] array2 = new float[4]; for (int i = 0; i < array.Length; i++) { if (!float.TryParse(array[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out array2[i])) { return false; } } v = new Vector4(array2[0], array2[1], array2[2], array2[3]); return true; } [HarmonyPatch("PositionSuitsOnRack")] [HarmonyPrefix] private static bool PositionSuitsOnRack(ref StartOfRound __instance) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: 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_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: 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) try { List<UnlockableSuit> list = (from s in Object.FindObjectsOfType<UnlockableSuit>() orderby s.syncedSuitID.Value select s).ToList(); float num = 0.18f; if (list.Count > 13) { num /= (float)Math.Min(list.Count, 20) / 12f; } for (int num2 = 0; num2 < list.Count; num2++) { AutoParentToShip component = ((Component)list[num2]).gameObject.GetComponent<AutoParentToShip>(); if (!((Object)(object)component == (Object)null)) { component.overrideOffset = true; component.positionOffset = new Vector3(-2.45f, 2.75f, -8.41f) + __instance.rightmostSuitPosition.forward * num * (float)num2; component.rotationOffset = new Vector3(0f, 90f, 0f); } } return false; } catch (Exception arg) { Plugin.Log.LogError((object)$"[Suits] Rack layout failed: {arg}"); return true; } } } [HarmonyPatch] internal static class TestMode { private const int Credits = 100000; private static bool _done; [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "Start")] private static void OnShipStart(StartOfRound __instance) { if (!_done && Settings.On(Settings.TestMode)) { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsServer) { Plugin.Log.LogInfo((object)"[TestMode] Only the host lays out the items - skipping here."); return; } _done = true; ((MonoBehaviour)__instance).StartCoroutine(LayEverythingOut(__instance)); } } private static IEnumerator LayEverythingOut(StartOfRound round) { yield return (object)new WaitForSeconds(3f); try { GiveCredits(); } catch (Exception ex) { Plugin.Log.LogError((object)("[TestMode] Could not set credits: " + ex.Message)); } try { SpawnEveryItem(round); } catch (Exception arg) { Plugin.Log.LogError((object)$"[TestMode] Could not spawn items: {arg}"); } } private static void GiveCredits() { Terminal val = Object.FindObjectOfType<Terminal>(); if (!((Object)(object)val == (Object)null)) { val.groupCredits = 100000; Plugin.Log.LogInfo((object)$"[TestMode] Credits set to {100000}."); } } private static void SpawnEveryItem(StartOfRound round) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00df: 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) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)round.allItemsList == (Object)null || round.allItemsList.itemsList == null) { return; } Transform val = (((Object)(object)round.shipDoorNode != (Object)null) ? round.shipDoorNode : round.elevatorTransform); if ((Object)(object)val == (Object)null) { return; } Vector3 val2 = val.position + val.forward * 1.5f + Vector3.up * 0.5f; List<Item> itemsList = round.allItemsList.itemsList; int num = 0; int num2 = 0; HashSet<GameObject> hashSet = new HashSet<GameObject>(); List<string> list = new List<string>(); for (int i = 0; i < itemsList.Count; i++) { Item val3 = itemsList[i]; if ((Object)(object)val3 == (Object)null || (Object)(object)val3.spawnPrefab == (Object)null) { continue; } if (!hashSet.Add(val3.spawnPrefab)) { num2++; continue; } try { Vector3 val4 = val2 + val.right * ((float)(num % 16) * 0.4f - 3.2f) + val.forward * ((float)(num / 16) * 0.4f); GameObject val5 = Object.Instantiate<GameObject>(val3.spawnPrefab, val4, Quaternion.identity, round.elevatorTransform); GrabbableObject component = val5.GetComponent<GrabbableObject>(); if ((Object)(object)component != (Object)null) { component.fallTime = 0f; if (val3.isScrap) { component.SetScrapValue(50); } } NetworkObject component2 = val5.GetComponent<NetworkObject>(); if ((Object)(object)component2 == (Object)null) { Object.Destroy((Object)(object)val5); continue; } component2.Spawn(false); num++; list.Add(val3.itemName); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[TestMode] Skipped " + val3.itemName + ": " + ex.Message)); } } Plugin.Log.LogInfo((object)($"[TestMode] Laid out {num} item(s) by the ship door" + ((num2 > 0) ? $", {num2} duplicate(s) not repeated." : "."))); Plugin.Log.LogInfo((object)("[TestMode] " + string.Join(", ", list.ToArray()))); } } internal static class Tweaks { private const string FileName = "tweaks.cfg"; internal const string JoinFixWithOldInteriors = "FixJoiningWithOldInteriors"; internal const string MoreSaveFiles = "MoreSaveFiles"; internal const string UnlockAllMoons = "UnlockAllMoons"; internal const string VanillaWeather = "VanillaWeather"; internal const string DropshipLandingMusic = "DropshipLandingMusic"; private static readonly Dictionary<string, bool> Defaults = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase) { { "FixJoiningWithOldInteriors", true }, { "MoreSaveFiles", true }, { "UnlockAllMoons", true }, { "VanillaWeather", true }, { "DropshipLandingMusic", true } }; private static Dictionary<string, bool> _values; internal static bool On(string key) { if (_values != null && _values.TryGetValue(key, out var value)) { return value; } bool value2; return Defaults.TryGetValue(key, out value2) && value2; } internal static void Init() { _values = new Dictionary<string, bool>(Defaults, StringComparer.OrdinalIgnoreCase); string path = Path.Combine(Plugin.PluginDir, "tweaks.cfg"); if (File.Exists(path)) { try { Read(path); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[Tweaks] Could not read tweaks.cfg (" + ex.Message + ") - using defaults for everything.")); } } else { Plugin.Log.LogInfo((object)"[Tweaks] No tweaks.cfg next to the DLL - using defaults."); } foreach (KeyValuePair<string, bool> value in _values) { Plugin.Log.LogInfo((object)("[Tweaks] " + value.Key + " = " + (value.Value ? "on" : "off"))); } } private static void Read(string path) { string[] array = File.ReadAllLines(path); foreach (string text in array) { string text2 = text; int num = text2.IndexOf('#'); if (num >= 0) { text2 = text2.Substring(0, num); } text2 = text2.Trim(); if (text2.Length == 0) { continue; } int num2 = text2.IndexOf('='); if (num2 <= 0) { Plugin.Log.LogWarning((object)("[Tweaks] Skipping line I cannot read: " + text.Trim())); continue; } string text3 = text2.Substring(0, num2).Trim(); string text4 = text2.Substring(num2 + 1).Trim(); if (!Defaults.ContainsKey(text3)) { Plugin.Log.LogWarning((object)("[Tweaks] Unknown setting '" + text3 + "' - ignoring it.")); continue; } if (bool.TryParse(text4, out var result)) { _values[text3] = result; continue; } Plugin.Log.LogWarning((object)("[Tweaks] '" + text3 + "' should be true or false, not '" + text4 + "' - keeping the default.")); } } } }