using 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 HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Networking; using UnityEngine.SceneManagement; [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.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1")] [assembly: AssemblyProduct("DunderKlumpar")] [assembly: AssemblyTitle("DunderKlumpar")] [assembly: AssemblyVersion("1.0.1.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 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(true); foreach (AudioSource val in array) { if (!((Object)(object)val == (Object)null) && val.playOnAwake) { val.playOnAwake = false; (((Component)val).gameObject.GetComponent() ?? ((Component)val).gameObject.AddComponent()).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.0.1")] 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.0.1"; internal static ManualLogSource Log; internal static string PluginDir; private void Awake() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; PluginDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); Settings.Init(((BaseUnityPlugin)this).Config); 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()); val2.AddComponent(); try { SoundReplacement.LoadAll(); } catch (Exception arg) { Log.LogError((object)$"Could not load sounds: {arg}"); } Log.LogInfo((object)("DunderKlumpar v1.0.1 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("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("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)); } } } internal static class Settings { internal static ConfigEntry LogSounds; internal static ConfigEntry ExportOriginalSounds; internal static ConfigEntry DumpAllSounds; internal static ConfigEntry ExportSuitTemplate; internal static void Init(ConfigFile cfg) { LogSounds = cfg.Bind("Sounds", "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("Sounds", "ExportOriginalSounds", false, "Save the game's original sounds as .wav in 'original_sounds/' the first time each one plays. Useful for hearing what you are replacing and how long it is. Turn off when done - it uses disk space."); DumpAllSounds = cfg.Bind("Sounds", "DumpAllSounds", false, "Dump ALL of the game's sounds at once into 'original_sounds/' when a round starts, plus a searchable list in 'all_sounds.html'. Do this ONCE to get the whole library to work from, then turn it off again. Takes a few seconds and some disk space."); ExportSuitTemplate = cfg.Bind("Suits", "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 in Photoshop. Files starting with '_' are never loaded as suits."); } } [HarmonyPatch] internal static class ShipDecorPatches { private const string PlanePath = "HangarShip/Plane.001"; 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(); 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; SwapMaterialTexture(materials, 0, "posters"); 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 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 Seen = new HashSet(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.LogSounds != null && Settings.LogSounds.Value) { Plugin.Log.LogInfo((object)("[SoundScan] " + ((Object)clip).name)); TryAppendCatalog(clip); } if (Settings.ExportOriginalSounds != null && Settings.ExportOriginalSounds.Value) { 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.DumpAllSounds == null || !Settings.DumpAllSounds.Value) { 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 ours = new HashSet(from v in SoundReplacement.Variants.Values.SelectMany((List v) => v) select v.Clip); List list = (from g in (from c in Resources.FindObjectsOfTypeAll() where (Object)(object)c != (Object)null && !string.IsNullOrEmpty(((Object)c).name) && !ours.Contains(c) select c).GroupBy((AudioClip c) => ((Object)c).name, StringComparer.OrdinalIgnoreCase) select g.First()).OrderBy((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("\nDunderKlumpar - all sounds in the game\n\n

All sounds in Lethal Company

\n

Name your audio file exactly like the Sound name column (e.g. StartGameLever.ogg)\nand put it in sounds/. Add -ver01, -ver02… to have the game\npick randomly between several versions. Saved originals are the .wav files in original_sounds/.

\n\n\n"); foreach (var row in rows) { string text = (SoundReplacement.Variants.ContainsKey(row.Name) ? "REPLACED" : ""); string text2 = (row.Exported ? "yes" : "no (streamed)"); stringBuilder.Append("\n"); } stringBuilder.Append("
Sound nameLengthSaved as .wav
" + Escape(row.Name) + "" + row.Length.ToString("F1", CultureInfo.InvariantCulture) + "s" + text2 + "" + text + "
\n"); 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> Variants = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly Regex VariantSuffix = new Regex("-ver\\d+$", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Dictionary Plays = new Dictionary(); 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()); } 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> 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; } private static uint NextRandom(uint state) { state ^= state << 13; state ^= state >> 17; state ^= state << 5; if (state != 0) { return state; } return 2463534242u; } private static uint StableHash(string text) { uint num = 2166136261u; foreach (char c in text) { num ^= char.ToLowerInvariant(c); num *= 16777619; } return num; } private static AudioType? DetectAudioType(string file) { return Path.GetExtension(file).ToLowerInvariant() switch { ".ogg" => (AudioType)14, ".wav" => (AudioType)20, ".mp3" => (AudioType)13, _ => null, }; } private 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 void OnSourcePlay(AudioSource __instance) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.clip == (Object)null)) { SoundCatalog.Note(__instance.clip); AudioClip val = SoundReplacement.Pick(((Object)__instance.clip).name); if ((Object)(object)val != (Object)null) { __instance.clip = val; } } } 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_018b: 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_0197: Unknown result type (might be due to invalid IL or missing references) //IL_019c: 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 list = (from p in Directory.GetFiles(text, "*.png") where !Path.GetFileName(p).StartsWith("_") select p).OrderBy((string p) => Path.GetFileName(p), StringComparer.OrdinalIgnoreCase).ToList(); if (list.Count == 0) { _added = true; return; } UnlockableItem val = ((IEnumerable)round.unlockablesList.unlockables).FirstOrDefault((Func)((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.ExportSuitTemplate != null && Settings.ExportSuitTemplate.Value) { ExportTemplate(val.suitMaterial, text); } int num = 0; foreach (string item in list) { try { string text2 = DisplayName(Path.GetFileNameWithoutExtension(item)); UnlockableItem val2 = JsonUtility.FromJson(JsonUtility.ToJson((object)val)); Material val3 = Object.Instantiate(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 list = (from s in Object.FindObjectsOfType() 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(); 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; } } } }