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 NobleMod v1.0.2
NobleMod.dll
Decompiled 4 hours 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 HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("Raisery")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("NobleMod")] [assembly: AssemblyTitle("NobleMod")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace NobleMod { internal static class ClipFileLoader { public static AudioClip LoadFromPath(string path) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Invalid comparison between Unknown and I4 if (Path.GetExtension(path)?.ToLowerInvariant() != ".ogg") { return null; } UnityWebRequest audioClip = UnityWebRequestMultimedia.GetAudioClip(new Uri(path), (AudioType)14); try { UnityWebRequestAsyncOperation val = audioClip.SendWebRequest(); while (!((AsyncOperation)val).isDone) { } if ((int)audioClip.result != 1) { return null; } return DownloadHandlerAudioClip.GetContent(audioClip); } finally { ((IDisposable)audioClip)?.Dispose(); } } } internal static class LevelEnemyOverrideBank { private static readonly Dictionary<int, string> MobByLevel = new Dictionary<int, string>(); private static ManualLogSource _logger; private static string _spawnConfigRootPath; public static void Initialize(ManualLogSource logger) { _logger = logger; Reload(); } public static bool TryGetMobKey(int levelNumber, out string mobKey) { if (MobByLevel.TryGetValue(levelNumber, out mobKey) && !string.IsNullOrWhiteSpace(mobKey)) { return true; } mobKey = null; return false; } private static void Reload() { MobByLevel.Clear(); _spawnConfigRootPath = Path.Combine(Plugin.AssemblyDirectory, "SpawnConfig"); Directory.CreateDirectory(_spawnConfigRootPath); string text = ModConfig.SpawnOverridesFileName.Value; if (string.IsNullOrWhiteSpace(text)) { text = "level_enemy_overrides.json"; } string text2 = Path.Combine(_spawnConfigRootPath, text); if (!File.Exists(text2)) { WriteDefaultTemplate(text2); _logger.LogWarning((object)("Missing spawn override file: " + text2 + ". Template created.")); return; } try { foreach (Match item in Regex.Matches(File.ReadAllText(text2, Encoding.UTF8), "(?:\"(?<key1>[^\"]+)\"|(?<key2>\\d+))\\s*:\\s*\"(?<value>[^\"]+)\"", RegexOptions.IgnoreCase)) { string text3 = (item.Groups["key1"].Success ? item.Groups["key1"].Value : item.Groups["key2"].Value); string value = item.Groups["value"].Value.Trim(); if (!string.IsNullOrWhiteSpace(text3) && !string.IsNullOrWhiteSpace(value) && TryParseLevelKey(text3, out var levelNumber) && levelNumber > 0) { MobByLevel[levelNumber] = NormalizeMobKey(value); } } if (ModConfig.LogSpawnOverrides.Value) { _logger.LogInfo((object)$"Loaded {MobByLevel.Count} level mob override(s) from '{text2}'."); } } catch (Exception ex) { _logger.LogError((object)("Failed to parse spawn override file '" + text2 + "': " + ex.Message)); } } private static bool TryParseLevelKey(string rawKey, out int levelNumber) { levelNumber = 0; if (string.IsNullOrWhiteSpace(rawKey)) { return false; } Match match = Regex.Match(rawKey, "\\d+"); if (!match.Success) { return false; } return int.TryParse(match.Value, out levelNumber); } private static string NormalizeMobKey(string value) { return value.Trim().ToLowerInvariant(); } private static void WriteDefaultTemplate(string outputPath) { File.WriteAllText(outputPath, "{\n \"1\": \"huntsman\",\n \"2\": \"headman\"\n}\n", Encoding.UTF8); } } internal static class ModConfig { public static ConfigEntry<bool> EnableCustomSounds; public static ConfigEntry<string> AudioSourceHierarchyFilter; public static ConfigEntry<bool> LogReplacements; public static ConfigEntry<bool> LogUnknownVanillaClipNamesOnce; public static ConfigEntry<bool> WriteDiscoveredClipsHierarchyJson; public static ConfigEntry<string> DiscoveredClipsHierarchyFileName; public static ConfigEntry<string> DiscoveredClipsOutputPath; public static ConfigEntry<bool> EnableSpawnOverrides; public static ConfigEntry<string> SpawnOverridesFileName; public static ConfigEntry<bool> LogSpawnOverrides; public static ConfigEntry<bool> DebugLogHookEntrypoints; public static ConfigEntry<int> DebugLogHookEntrypointsPerMethod; public static ConfigEntry<bool> DebugSuppressMenuSpam; public static ConfigEntry<bool> LogWeightedSoundPicks; public static void Bind(ConfigFile config) { EnableCustomSounds = config.Bind<bool>("General", "EnableCustomSounds", true, "Active les remplacements de sons personnalises."); AudioSourceHierarchyFilter = config.Bind<string>("General", "AudioSourceHierarchyFilter", "", "Limite remplacements / decouverte aux AudioSource dont la hierarchie (noms des transforms) contient cette sous-chaine. Vide = tout le jeu (generique)."); LogReplacements = config.Bind<bool>("General", "LogReplacements", false, "Active les logs de remplacement audio."); LogUnknownVanillaClipNamesOnce = config.Bind<bool>("General", "LogUnknownVanillaClipNamesOnce", false, "Log chaque nom de clip vanilla non mappe une seule fois (utile pour completer replacements.json)."); WriteDiscoveredClipsHierarchyJson = config.Bind<bool>("General", "WriteDiscoveredClipsHierarchyJson", true, "Ecrit les nouveaux noms de clips rencontres dans un fichier JSON hierarchique (sans doublons)."); DiscoveredClipsHierarchyFileName = config.Bind<string>("General", "DiscoveredClipsHierarchyFileName", "discovered_clips_hierarchy.json", "Nom du fichier JSON hierarchique qui stocke les clips decouverts."); DiscoveredClipsOutputPath = config.Bind<string>("General", "DiscoveredClipsOutputPath", "", "Chemin absolu du dossier ou ecrire le JSON des clips decouverts (ex: .../votre-clone/CustomSounds). Vide = CustomSounds du plugin."); EnableSpawnOverrides = config.Bind<bool>("Spawning", "EnableSpawnOverrides", false, "Active le remplacement du mob principal via un mapping niveau -> mob (fichier JSON dans SpawnConfig)."); SpawnOverridesFileName = config.Bind<string>("Spawning", "SpawnOverridesFileName", "level_enemy_overrides.json", "Nom du fichier JSON contenant le mapping niveau -> identifiant de mob."); LogSpawnOverrides = config.Bind<bool>("Spawning", "LogSpawnOverrides", true, "Log les decisions de remplacement des mobs principaux (selection, fallback, erreurs)."); DebugLogHookEntrypoints = config.Bind<bool>("Debug", "DebugLogHookEntrypoints", false, "Log les entrees des hooks audio (meme si source/clip est null)."); DebugLogHookEntrypointsPerMethod = config.Bind<int>("Debug", "DebugLogHookEntrypointsPerMethod", 500, "Nombre max de logs d'entree par hook/methode pour eviter le spam (<= 0 = illimite)."); DebugSuppressMenuSpam = config.Bind<bool>("Debug", "DebugSuppressMenuSpam", true, "Masque le bruit des logs audio menu/UI (ex: menu hover, PlayHelper) pour faciliter le debug en partie."); LogWeightedSoundPicks = config.Bind<bool>("Debug", "LogWeightedSoundPicks", false, "Log chaque tirage pondere (Random.value, creneaux cumules, resultat). Utile pour comprendre vanilla % vs customs. Les reutilisations meme frame (cache) ou boucle (sticky) sont en LogDebug."); } } internal static class NobleModMenuIntegration { private static Assembly _menuLib; private static Type _menuApiType; private static MethodInfo _miCreateButton; private static MethodInfo _miCreateToggle; private static MethodInfo _miCreatePopup; private static Type _presetSideType; private static object _popup; private static object _toggleSounds; private static object _toggleLogWeightedPicks; private static object _toggleSpawn; private static bool _methodsResolved; private static void ClearNoblePopupCache() { _popup = null; _toggleSounds = null; _toggleLogWeightedPicks = null; _toggleSpawn = null; } private static bool IsUnityObjAlive(object u) { Object val = (Object)((u is Object) ? u : null); if (val != null) { return val != (Object)null; } return false; } private static object GetRepoPopupMenuPage(object repoPopupPage) { return repoPopupPage?.GetType().GetField("menuPage", BindingFlags.Instance | BindingFlags.Public)?.GetValue(repoPopupPage); } internal static void TryRegister(ManualLogSource log) { try { _menuLib = Array.Find(AppDomain.CurrentDomain.GetAssemblies(), (Assembly a) => string.Equals(a.GetName().Name, "MenuLib", StringComparison.Ordinal)); if (_menuLib == null) { log.LogWarning((object)"[NobleMod] MenuLib introuvable. Installez la dependance 'MenuLib' (Thunderstore, auteur nickklmao). Le bouton NobleMod dans Parametres ne sera pas ajoute."); return; } _menuApiType = _menuLib.GetType("MenuLib.MenuAPI", throwOnError: false); if (_menuApiType == null) { log.LogError((object)"[NobleMod] Type MenuLib.MenuAPI introuvable."); return; } MethodInfo method = _menuApiType.GetMethod("AddElementToSettingsMenu", BindingFlags.Static | BindingFlags.Public); if (method == null) { log.LogError((object)"[NobleMod] MenuAPI.AddElementToSettingsMenu introuvable."); return; } Type parameterType = method.GetParameters()[0].ParameterType; MethodInfo method2 = typeof(NobleModMenuIntegration).GetMethod("OnSettingsMenuBuilt", BindingFlags.Static | BindingFlags.NonPublic); Delegate @delegate = Delegate.CreateDelegate(parameterType, method2); method.Invoke(null, new object[1] { @delegate }); log.LogInfo((object)"[NobleMod] MenuLib: bouton ajoute au menu Parametres du jeu."); } catch (Exception arg) { log.LogError((object)$"[NobleMod] Integration MenuLib: {arg}"); } } private static void OnSettingsMenuBuilt(Transform parent) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) try { ResolveMenuApiMethods(); Transform val = FindSettingsLeftNavControlsRow(parent); if ((Object)(object)val != (Object)null && (Object)(object)val.parent != (Object)null) { Transform parent2 = val.parent; object? obj = _miCreateButton.Invoke(null, new object[4] { "NOBLEMOD", new Action(OpenNobleModPopup), parent2, Vector2.zero }); PlaceNobleButtonUnderControlsNav((Component)((obj is Component) ? obj : null), val); return; } ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[NobleMod] Entree CONTROLS (liste gauche) introuvable; placement repli sur le scroll."); } Transform val2 = FindMenuScrollScroller(parent) ?? parent; Vector2 val3 = ComputeAppendLocalPosition(val2); object? obj2 = _miCreateButton.Invoke(null, new object[4] { "NobleMod — reglages du mod", new Action(OpenNobleModPopup), val2, val3 }); object? obj3 = ((obj2 is Component) ? obj2 : null); if (obj3 != null) { ((Component)obj3).transform.SetAsLastSibling(); } } catch (Exception arg) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)$"[NobleMod] OnSettingsMenuBuilt: {arg}"); } } } private static Transform FindSettingsLeftNavControlsRow(Transform pageRoot) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) Transform result = null; float num = float.MaxValue; Transform[] componentsInChildren = ((Component)pageRoot).GetComponentsInChildren<Transform>(true); foreach (Transform val in componentsInChildren) { Component val2 = TryGetTmpTextComponent(val); if ((Object)(object)val2 == (Object)null) { continue; } string tmpText = GetTmpText(val2); if (string.IsNullOrEmpty(tmpText)) { continue; } string text = tmpText.Trim(); if (!text.Equals("CONTROLS", StringComparison.OrdinalIgnoreCase) && !text.Equals("CONTRÔLES", StringComparison.OrdinalIgnoreCase)) { continue; } Transform parent = val.parent; if (!((Object)(object)parent == (Object)null)) { float x = parent.position.x; if (x < num) { num = x; result = parent; } } } return result; } private static Component TryGetTmpTextComponent(Transform tr) { Component component = ((Component)tr).GetComponent("TMPro.TextMeshProUGUI"); if ((Object)(object)component != (Object)null) { return component; } return ((Component)tr).GetComponent("TextMeshProUGUI"); } private static string GetTmpText(Component tmp) { return ((object)tmp).GetType().GetProperty("text")?.GetValue(tmp) as string; } private static void PlaceNobleButtonUnderControlsNav(Component btnComponent, Transform controlsRow) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_018d: 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) Transform obj = ((btnComponent != null) ? btnComponent.transform : null); RectTransform val = (RectTransform)(object)((obj is RectTransform) ? obj : null); RectTransform val2 = (RectTransform)(object)((controlsRow is RectTransform) ? controlsRow : null); Transform val3 = ((controlsRow != null) ? controlsRow.parent : null); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null) { return; } int siblingIndex = controlsRow.GetSiblingIndex(); RectTransform val4 = null; if (siblingIndex + 1 < val3.childCount) { Transform child = val3.GetChild(siblingIndex + 1); val4 = (RectTransform)(object)((child is RectTransform) ? child : null); } ((Transform)val).SetSiblingIndex(siblingIndex + 1); val.anchorMin = val2.anchorMin; val.anchorMax = val2.anchorMax; val.pivot = val2.pivot; val.sizeDelta = val2.sizeDelta; float num; if (siblingIndex > 0) { Transform child2 = val3.GetChild(siblingIndex - 1); RectTransform val5 = (RectTransform)(object)((child2 is RectTransform) ? child2 : null); if (val5 != null) { num = ((Transform)val2).localPosition.y - ((Transform)val5).localPosition.y; goto IL_012d; } } num = ((!((Object)(object)val4 != (Object)null)) ? (0f - (((val2.sizeDelta.y > 8f) ? val2.sizeDelta.y : 40f) + 6f)) : ((((Transform)val4).localPosition.y - ((Transform)val2).localPosition.y) * 0.5f)); goto IL_012d; IL_012d: ((Transform)val).localPosition = ((Transform)val2).localPosition + new Vector3(0f, num, 0f); if ((Object)(object)val4 != (Object)null && Mathf.Abs(num) > 0.01f) { float num2 = ((Transform)val).localPosition.y + num; RectTransform val6 = val4; ((Transform)val6).localPosition = new Vector3(((Transform)val6).localPosition.x, num2, ((Transform)val6).localPosition.z); } } private static Vector2 ComputeAppendLocalPosition(Transform content) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)content == (Object)null) { return new Vector2(250f, -40f); } float num = float.MaxValue; bool flag = false; for (int i = 0; i < content.childCount; i++) { Transform child = content.GetChild(i); RectTransform val = (RectTransform)(object)((child is RectTransform) ? child : null); if (val != null && !IsChromeScrollChild(child)) { float localBottomY = GetLocalBottomY(val, content); if (localBottomY < num) { num = localBottomY; flag = true; } } } float num2 = (flag ? (num - 22f) : (-40f)); return new Vector2(250f, num2); } private static bool IsChromeScrollChild(Transform child) { string text = ((Object)child).name ?? string.Empty; if (text.IndexOf("scroll", StringComparison.OrdinalIgnoreCase) >= 0 && text.IndexOf("bar", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } return false; } private static float GetLocalBottomY(RectTransform rt, Transform contentParent) { //IL_001b: 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_0025: 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) //IL_002f: Unknown result type (might be due to invalid IL or missing references) Vector3[] array = (Vector3[])(object)new Vector3[4]; rt.GetWorldCorners(array); float num = float.MaxValue; for (int i = 0; i < 4; i++) { Vector3 val = contentParent.InverseTransformPoint(array[i]); if (val.y < num) { num = val.y; } } return num; } private static void ResolveMenuApiMethods() { if (_methodsResolved) { return; } MethodInfo[] methods = _menuApiType.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "CreateREPOButton") { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 4 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType == typeof(Action) && parameters[2].ParameterType == typeof(Transform) && parameters[3].ParameterType == typeof(Vector2)) { _miCreateButton = methodInfo; } } else if (methodInfo.Name == "CreateREPOToggle") { ParameterInfo[] parameters2 = methodInfo.GetParameters(); if (parameters2.Length == 7 && parameters2[0].ParameterType == typeof(string) && parameters2[1].ParameterType == typeof(Action<bool>) && parameters2[2].ParameterType == typeof(Transform) && parameters2[3].ParameterType == typeof(Vector2) && parameters2[4].ParameterType == typeof(string) && parameters2[5].ParameterType == typeof(string) && parameters2[6].ParameterType == typeof(bool)) { _miCreateToggle = methodInfo; } } else if (methodInfo.Name == "CreateREPOPopupPage") { ParameterInfo[] parameters3 = methodInfo.GetParameters(); if (parameters3.Length >= 5 && parameters3[0].ParameterType == typeof(string) && parameters3[1].ParameterType.IsEnum && parameters3[2].ParameterType == typeof(bool) && parameters3[3].ParameterType == typeof(bool) && parameters3[4].ParameterType == typeof(float)) { _miCreatePopup = methodInfo; _presetSideType = parameters3[1].ParameterType; } } } if (_miCreateButton == null || _miCreateToggle == null || _miCreatePopup == null || _presetSideType == null) { throw new InvalidOperationException("Signatures MenuAPI inattendues (mettre a jour NobleModMenuIntegration)."); } _methodsResolved = true; } private static Transform FindMenuScrollScroller(Transform pageRoot) { MonoBehaviour[] componentsInChildren = ((Component)pageRoot).GetComponentsInChildren<MonoBehaviour>(true); foreach (MonoBehaviour val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !(((object)val).GetType().Name != "MenuScrollBox")) { object? obj = ((object)val).GetType().GetField("scroller", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(val); Transform val2 = (Transform)((obj is Transform) ? obj : null); if (val2 != null) { return val2; } } } return null; } private static void OpenNobleModPopup() { try { EnsureNoblePopup(); if (!IsUnityObjAlive(_popup)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)"[NobleMod] Popup NobleMod introuvable apres EnsureNoblePopup."); } return; } MethodInfo methodInfo = (_toggleSounds?.GetType())?.GetMethod("SetState", new Type[2] { typeof(bool), typeof(bool) }); if (IsUnityObjAlive(_toggleSounds)) { methodInfo?.Invoke(_toggleSounds, new object[2] { ModConfig.EnableCustomSounds.Value, false }); } if (IsUnityObjAlive(_toggleLogWeightedPicks)) { methodInfo?.Invoke(_toggleLogWeightedPicks, new object[2] { ModConfig.LogWeightedSoundPicks.Value, false }); } if (IsUnityObjAlive(_toggleSpawn)) { methodInfo?.Invoke(_toggleSpawn, new object[2] { ModConfig.EnableSpawnOverrides.Value, false }); } _popup.GetType().GetMethod("OpenPage", new Type[1] { typeof(bool) })?.Invoke(_popup, new object[1] { false }); } catch (Exception arg) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)$"[NobleMod] OpenNobleModPopup: {arg}"); } } } private static void EnsureNoblePopup() { //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_0342: Unknown result type (might be due to invalid IL or missing references) if (!IsUnityObjAlive(_popup) || !IsUnityObjAlive(GetRepoPopupMenuPage(_popup))) { ClearNoblePopupCache(); ResolveMenuApiMethods(); object obj = Enum.Parse(_presetSideType, "Left"); _popup = _miCreatePopup.Invoke(null, new object[5] { "NobleMod", obj, true, true, 0f }); Type type = _popup.GetType(); object obj2 = type.GetField("menuScrollBox", BindingFlags.Instance | BindingFlags.Public)?.GetValue(_popup); if (obj2 == null) { throw new InvalidOperationException("REPOPopupPage.menuScrollBox null (Awake pas encore execute ?)."); } object? obj3 = obj2.GetType().GetField("scroller", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(obj2); Transform val = (Transform)((obj3 is Transform) ? obj3 : null); if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("MenuScrollBox.scroller null."); } MethodInfo method = type.GetMethod("AddElementToScrollView", BindingFlags.Instance | BindingFlags.Public, null, new Type[4] { typeof(RectTransform), typeof(Vector2), typeof(float), typeof(float) }, null); if (method == null) { throw new InvalidOperationException("REPOPopupPage.AddElementToScrollView(RectTransform, ...) introuvable."); } RegisterPopupScrollElement(ctrl: (Component)(_toggleSounds = (object)/*isinst with value type is only supported in some contexts*/), popupPage: _popup, addElementToScrollView: method, topPad: 14f, bottomPad: 0f); RegisterPopupScrollElement(ctrl: (Component)(_toggleLogWeightedPicks = (object)/*isinst with value type is only supported in some contexts*/), popupPage: _popup, addElementToScrollView: method, topPad: 0f, bottomPad: 0f); RegisterPopupScrollElement(ctrl: (Component)(_toggleSpawn = (object)/*isinst with value type is only supported in some contexts*/), popupPage: _popup, addElementToScrollView: method, topPad: 0f, bottomPad: 0f); object? obj4 = _miCreateButton.Invoke(null, new object[4] { "Fermer", (Action)delegate { _popup.GetType().GetMethod("ClosePage", new Type[1] { typeof(bool) })?.Invoke(_popup, new object[1] { false }); }, val, Vector2.zero }); Component ctrl4 = (Component)((obj4 is Component) ? obj4 : null); RegisterPopupScrollElement(_popup, method, ctrl4, 0f, 20f); object obj5 = type.GetField("scrollView", BindingFlags.Instance | BindingFlags.Public)?.GetValue(_popup); (obj5?.GetType().GetMethod("UpdateElements", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null))?.Invoke(obj5, null); (obj5?.GetType().GetMethod("SetScrollPosition", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(float) }, null))?.Invoke(obj5, new object[1] { 0f }); } } private static void RegisterPopupScrollElement(object popupPage, MethodInfo addElementToScrollView, Component ctrl, float topPad, float bottomPad) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)ctrl == (Object)null) && !(addElementToScrollView == null)) { Transform transform = ctrl.transform; RectTransform val = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (!((Object)(object)val == (Object)null)) { addElementToScrollView.Invoke(popupPage, new object[4] { val, Vector2.zero, topPad, bottomPad }); } } } private static void SaveCfg() { try { Plugin instance = Plugin.Instance; if (instance != null) { ((BaseUnityPlugin)instance).Config.Save(); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[NobleMod] Sauvegarde config: " + ex.Message)); } } } } [BepInPlugin("raisery.noblemod", "NobleMod", "1.0.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { internal static Plugin Instance; internal static Harmony Harmony; internal static ManualLogSource Log; internal static string AssemblyDirectory { get; private set; } = ""; private void Awake() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown AssemblyDirectory = Path.GetDirectoryName(typeof(Plugin).Assembly.Location) ?? ""; Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); SoundBank.Initialize(((BaseUnityPlugin)this).Logger); LevelEnemyOverrideBank.Initialize(((BaseUnityPlugin)this).Logger); Harmony = new Harmony("raisery.noblemod"); Harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"NobleMod v1.0.2 loaded."); NobleModMenuIntegration.TryRegister(((BaseUnityPlugin)this).Logger); } private void OnDestroy() { } } internal static class PluginInfo { public const string Guid = "raisery.noblemod"; public const string Name = "NobleMod"; public const string Version = "1.0.2"; } internal static class ReplacementsJsonParser { internal sealed class Entry { public readonly string MappingKey; public readonly List<Variant> Variants = new List<Variant>(); public Entry(string mappingKey) { MappingKey = mappingKey; } } internal struct Variant { public string File; public float WeightPercent; public bool IsVanilla; } private sealed class Parser { private readonly string _s; private int _i; private bool Eof => _i >= _s.Length; public Parser(string s) { _s = s; _i = 0; } public List<Entry> ParseRootObject() { SkipWs(); Expect('{'); List<Entry> list = new List<Entry>(); SkipWs(); while (!Eof && Peek() != '}') { string text = ReadString(); SkipWs(); Expect(':'); SkipWs(); Entry entry = new Entry(text); if (Peek() == '[') { ReadVariantArray(entry.Variants); } else { if (Peek() != '"') { throw new FormatException("Valeur attendue pour la cle '" + text + "' : chaine ou tableau."); } entry.Variants.Add(new Variant { File = ReadString(), WeightPercent = 100f, IsVanilla = false }); } list.Add(entry); SkipWs(); if (Peek() == ',') { _i++; SkipWs(); } else if (Peek() != '}') { throw new FormatException("',' ou '}' attendu apres une entree."); } } Expect('}'); SkipWs(); if (!Eof) { throw new FormatException("Caracteres inattendus apres la racine JSON."); } return list; } private void ReadVariantArray(List<Variant> target) { Expect('['); SkipWs(); while (!Eof && Peek() != ']') { Expect('{'); SkipWs(); string text = null; float? num = null; bool? flag = null; while (!Eof && Peek() != '}') { string a = ReadString(); SkipWs(); Expect(':'); SkipWs(); if (string.Equals(a, "file", StringComparison.OrdinalIgnoreCase)) { if (Peek() != '"') { throw new FormatException("\"file\" doit etre une chaine."); } text = ReadString(); } else if (string.Equals(a, "weight", StringComparison.OrdinalIgnoreCase)) { num = ReadNumber(); } else if (string.Equals(a, "vanilla", StringComparison.OrdinalIgnoreCase)) { flag = ReadJsonBool(); } else { SkipValue(); } SkipWs(); if (Peek() == ',') { _i++; SkipWs(); } else if (Peek() != '}') { throw new FormatException("Propriete d'objet : ',' ou '}' attendu."); } } Expect('}'); bool flag2 = flag == true; if (!flag2 && string.IsNullOrWhiteSpace(text)) { throw new FormatException("Variante sans \"file\" (ou utilisez \"vanilla\": true)."); } if (!num.HasValue) { throw new FormatException(flag2 ? "Variante vanilla sans \"weight\"." : ("Variante sans \"weight\" pour '" + text + "'.")); } target.Add(new Variant { File = (flag2 ? null : text.Trim()), WeightPercent = num.Value, IsVanilla = flag2 }); SkipWs(); if (Peek() == ',') { _i++; SkipWs(); } else if (Peek() != ']') { throw new FormatException("',' ou ']' attendu dans le tableau de variantes."); } } Expect(']'); } private void SkipValue() { if (Peek() == '"') { ReadString(); return; } if (Peek() == '[') { int num = 0; while (!Eof) { switch (_s[_i++]) { case '[': num++; break; case ']': num--; if (num == 0) { return; } break; } } return; } if (Peek() == '{') { int num2 = 0; while (!Eof) { switch (_s[_i++]) { case '{': num2++; break; case '}': num2--; if (num2 == 0) { return; } break; } } return; } SkipWs(); if (!Eof && _i + 4 <= _s.Length && string.CompareOrdinal(_s, _i, "true", 0, 4) == 0) { _i += 4; return; } if (!Eof && _i + 5 <= _s.Length && string.CompareOrdinal(_s, _i, "false", 0, 5) == 0) { _i += 5; return; } if (!Eof && _i + 4 <= _s.Length && string.CompareOrdinal(_s, _i, "null", 0, 4) == 0) { _i += 4; return; } while (!Eof && !char.IsWhiteSpace(_s[_i]) && _s[_i] != ',' && _s[_i] != '}' && _s[_i] != ']') { _i++; } } private bool ReadJsonBool() { SkipWs(); if (_i + 4 <= _s.Length && string.CompareOrdinal(_s, _i, "true", 0, 4) == 0) { _i += 4; return true; } if (_i + 5 <= _s.Length && string.CompareOrdinal(_s, _i, "false", 0, 5) == 0) { _i += 5; return false; } throw new FormatException("Litteral JSON true ou false attendu."); } private float ReadNumber() { SkipWs(); int i = _i; if (_i < _s.Length && (_s[_i] == '-' || _s[_i] == '+')) { _i++; } while (_i < _s.Length && char.IsDigit(_s[_i])) { _i++; } if (_i < _s.Length && _s[_i] == '.') { _i++; while (_i < _s.Length && char.IsDigit(_s[_i])) { _i++; } } string text = _s.Substring(i, _i - i); if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { throw new FormatException("Nombre invalide : '" + text + "'"); } return result; } private string ReadString() { Expect('"'); StringBuilder stringBuilder = new StringBuilder(); while (!Eof && _s[_i] != '"') { if (_s[_i] == '\\') { _i++; if (Eof) { throw new FormatException("Chaine non terminee (echappement)."); } char c = _s[_i++]; switch (c) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; default: stringBuilder.Append(c); break; } } else { stringBuilder.Append(_s[_i++]); } } Expect('"'); return stringBuilder.ToString(); } private void SkipWs() { while (!Eof && char.IsWhiteSpace(_s[_i])) { _i++; } } private char Peek() { if (!Eof) { return _s[_i]; } return '\0'; } private void Expect(char c) { if (Eof || _s[_i] != c) { throw new FormatException($"Caractere '{c}' attendu, position {_i}."); } _i++; } } internal static List<Entry> Parse(string json) { if (string.IsNullOrWhiteSpace(json)) { return new List<Entry>(); } return new Parser(json.Trim()).ParseRootObject(); } } internal enum SoundReplacementResolve { NoMatch, KeepVanilla, Replace } internal static class SoundBank { private sealed class ClipTreeNode { public readonly Dictionary<string, ClipTreeNode> Children = new Dictionary<string, ClipTreeNode>(); public readonly List<string> Clips = new List<string>(); } private readonly struct PickerSlot { public readonly bool IsVanilla; public readonly AudioClip Clip; public PickerSlot(bool isVanilla, AudioClip clip) { IsVanilla = isVanilla; Clip = clip; } } private sealed class WeightedClipPicker { private readonly PickerSlot[] _slots; private readonly float[] _cumulative; public WeightedClipPicker(PickerSlot[] slots, float[] weightsNormalized) { _slots = slots; _cumulative = new float[slots.Length]; float num = 0f; for (int i = 0; i < weightsNormalized.Length; i++) { num += weightsNormalized[i]; _cumulative[i] = num; } } public ReplacementPick Pick(string vanillaClipNameHeard, string ruleLabel) { if (!TryPickCore(out var pick, out var r, out var slotIndex)) { return default(ReplacementPick); } if (ModConfig.LogWeightedSoundPicks.Value && _logger != null) { LogPick(vanillaClipNameHeard, ruleLabel, pick, r, slotIndex); } return pick; } private bool TryPickCore(out ReplacementPick pick, out float r, out int slotIndex) { pick = default(ReplacementPick); r = 0f; slotIndex = 0; if (_slots == null || _slots.Length == 0) { return false; } if (_slots.Length == 1) { slotIndex = 0; r = float.NaN; PickerSlot pickerSlot = _slots[0]; pick = (pickerSlot.IsVanilla ? new ReplacementPick(keepVanilla: true, null) : new ReplacementPick(keepVanilla: false, pickerSlot.Clip)); return true; } r = Random.value; for (int i = 0; i < _cumulative.Length; i++) { if (r < _cumulative[i]) { slotIndex = i; PickerSlot pickerSlot2 = _slots[i]; pick = (pickerSlot2.IsVanilla ? new ReplacementPick(keepVanilla: true, null) : new ReplacementPick(keepVanilla: false, pickerSlot2.Clip)); return true; } } slotIndex = _slots.Length - 1; PickerSlot pickerSlot3 = _slots[_slots.Length - 1]; pick = (pickerSlot3.IsVanilla ? new ReplacementPick(keepVanilla: true, null) : new ReplacementPick(keepVanilla: false, pickerSlot3.Clip)); return true; } private void LogPick(string vanillaClipNameHeard, string ruleLabel, ReplacementPick pick, float r, int slotIndex) { string text = (pick.KeepVanilla ? "vanilla" : (((Object)(object)pick.Clip != (Object)null) ? ((Object)pick.Clip).name : "(null)")); if (_slots.Length == 1) { _logger.LogInfo((object)("[Tirage] vanilla lu='" + vanillaClipNameHeard + "' regle=" + ruleLabel + " | 1 seul creneau → " + text)); return; } StringBuilder stringBuilder = new StringBuilder(256); stringBuilder.Append("[Tirage] vanilla lu='").Append(vanillaClipNameHeard).Append("' regle=") .Append(ruleLabel); stringBuilder.Append(" | r=").Append(r.ToString("F4")).Append(" | creneaux cumules: "); float num = 0f; for (int i = 0; i < _cumulative.Length; i++) { float num2 = _cumulative[i]; PickerSlot pickerSlot = _slots[i]; string value = (pickerSlot.IsVanilla ? "vanilla" : (((Object)(object)pickerSlot.Clip != (Object)null) ? ((Object)pickerSlot.Clip).name : "?")); if (i > 0) { stringBuilder.Append("; "); } stringBuilder.Append('[').Append(num.ToString("F3")).Append(',') .Append(num2.ToString("F3")) .Append(")=") .Append(value); num = num2; } stringBuilder.Append(" | choix=slot#").Append(slotIndex).Append(" → ") .Append(text); _logger.LogInfo((object)stringBuilder.ToString()); } } private readonly struct ReplacementPick { public readonly bool KeepVanilla; public readonly AudioClip Clip; public ReplacementPick(bool keepVanilla, AudioClip clip) { KeepVanilla = keepVanilla; Clip = clip; } } private struct CachedResolveEntry { public SoundReplacementResolve Kind; public AudioClip ReplacementClip; } private sealed class ManagedRerollSticky { public AudioClip ChosenClip; } private static readonly Dictionary<string, WeightedClipPicker> ClipsByVanillaName = new Dictionary<string, WeightedClipPicker>(); private static readonly List<KeyValuePair<string, WeightedClipPicker>> ContainsRules = new List<KeyValuePair<string, WeightedClipPicker>>(); private static readonly HashSet<AudioClip> ManagedClips = new HashSet<AudioClip>(); private static readonly HashSet<string> UnknownLoggedOnce = new HashSet<string>(); private static readonly HashSet<string> DiscoveredClipNames = new HashSet<string>(); private static ManualLogSource _logger; private static string _customSoundsRootPath; private static string _discoveredOutputRootPath; private static int _resolveCacheFrame = -1; private static readonly Dictionary<int, CachedResolveEntry> _resolveCacheByClipId = new Dictionary<int, CachedResolveEntry>(); private static readonly HashSet<int> _resolveCacheDebugOncePerClipThisFrame = new HashSet<int>(); private static ConditionalWeakTable<AudioSource, string> _audioSourceToVanillaClipName = new ConditionalWeakTable<AudioSource, string>(); private static readonly Dictionary<string, AudioClip> VanillaClipRefByExactName = new Dictionary<string, AudioClip>(StringComparer.Ordinal); private static ConditionalWeakTable<AudioSource, ManagedRerollSticky> _managedRerollStickyBySource = new ConditionalWeakTable<AudioSource, ManagedRerollSticky>(); public static bool IsAudioSourceInFilterScope(AudioSource source) { if ((Object)(object)source == (Object)null) { return false; } string value = ModConfig.AudioSourceHierarchyFilter.Value; if (string.IsNullOrWhiteSpace(value)) { return true; } Transform val = ((Component)source).transform; while ((Object)(object)val != (Object)null) { if (((Object)val).name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } val = val.parent; } return false; } public static void RememberVanillaPlaybackContext(AudioSource source, AudioClip vanillaClip) { if (!((Object)(object)source == (Object)null) && !((Object)(object)vanillaClip == (Object)null) && !IsManagedClip(vanillaClip)) { string name = ((Object)vanillaClip).name; if (!string.IsNullOrEmpty(name)) { _audioSourceToVanillaClipName.Remove(source); _audioSourceToVanillaClipName.Add(source, name); VanillaClipRefByExactName[name] = vanillaClip; _managedRerollStickyBySource.Remove(source); } } } public static bool TryRerollManagedClipOnSource(AudioSource source, ref AudioClip clip) { if ((Object)(object)source == (Object)null || (Object)(object)clip == (Object)null || !IsManagedClip(clip)) { return false; } if (!IsAudioSourceInFilterScope(source)) { return false; } if (!_audioSourceToVanillaClipName.TryGetValue(source, out var value) || string.IsNullOrEmpty(value)) { return false; } if (!VanillaClipRefByExactName.TryGetValue(value, out var value2) || (Object)(object)value2 == (Object)null) { return false; } if (_managedRerollStickyBySource.TryGetValue(source, out var value3) && (Object)(object)value3.ChosenClip != (Object)null) { if (ModConfig.LogWeightedSoundPicks.Value && _logger != null) { _logger.LogDebug((object)$"[Tirage] sticky AudioSource id={((Object)source).GetInstanceID()} ref vanilla='{value}' → garde '{((Object)value3.ChosenClip).name}' (pas de nouveau Random jusqu'a un clip vanilla sur cette source)"); } clip = value3.ChosenClip; return true; } AudioClip replacementClip; SoundReplacementResolve soundReplacementResolve = TryResolveReplacement(value, clip, out replacementClip); AudioClip val; if (soundReplacementResolve == SoundReplacementResolve.KeepVanilla || soundReplacementResolve == SoundReplacementResolve.NoMatch) { val = value2; } else { if (soundReplacementResolve != SoundReplacementResolve.Replace || !((Object)(object)replacementClip != (Object)null)) { return false; } val = replacementClip; } clip = val; _managedRerollStickyBySource.Remove(source); _managedRerollStickyBySource.Add(source, new ManagedRerollSticky { ChosenClip = val }); return true; } public static void Initialize(ManualLogSource logger) { _logger = logger; Reload(); } public static bool IsManagedClip(AudioClip clip) { if ((Object)(object)clip != (Object)null) { return ManagedClips.Contains(clip); } return false; } public static SoundReplacementResolve TryResolveReplacement(string vanillaClipName, AudioClip vanillaClipAsset, out AudioClip replacementClip) { replacementClip = null; if (string.IsNullOrWhiteSpace(vanillaClipName)) { return SoundReplacementResolve.NoMatch; } if ((Object)(object)vanillaClipAsset != (Object)null) { int frameCount = Time.frameCount; if (_resolveCacheFrame != frameCount) { _resolveCacheByClipId.Clear(); _resolveCacheDebugOncePerClipThisFrame.Clear(); _resolveCacheFrame = frameCount; } int instanceID = ((Object)vanillaClipAsset).GetInstanceID(); if (_resolveCacheByClipId.TryGetValue(instanceID, out var value)) { if (ModConfig.LogWeightedSoundPicks.Value && _logger != null && _resolveCacheDebugOncePerClipThisFrame.Add(instanceID)) { string arg = value.Kind switch { SoundReplacementResolve.KeepVanilla => "KeepVanilla", SoundReplacementResolve.Replace => "Replace → " + (((Object)(object)value.ReplacementClip != (Object)null) ? ((Object)value.ReplacementClip).name : "?"), _ => "NoMatch", }; _logger.LogDebug((object)$"[Tirage] cache meme frame (instance clip vanilla id={instanceID}) → reutilise {arg} (plusieurs hooks ont appele TryResolveReplacement sans nouveau tirage)"); } replacementClip = value.ReplacementClip; return value.Kind; } SoundReplacementResolve soundReplacementResolve = TryResolveReplacementCore(vanillaClipName, out replacementClip); _resolveCacheByClipId[instanceID] = new CachedResolveEntry { Kind = soundReplacementResolve, ReplacementClip = replacementClip }; return soundReplacementResolve; } return TryResolveReplacementCore(vanillaClipName, out replacementClip); } private static SoundReplacementResolve TryResolveReplacementCore(string vanillaClipName, out AudioClip replacementClip) { replacementClip = null; if (ClipsByVanillaName.TryGetValue(vanillaClipName, out var value)) { return ResolvePick(value.Pick(vanillaClipName, "exact:" + vanillaClipName), out replacementClip); } for (int i = 0; i < ContainsRules.Count; i++) { KeyValuePair<string, WeightedClipPicker> keyValuePair = ContainsRules[i]; if (vanillaClipName.IndexOf(keyValuePair.Key, StringComparison.OrdinalIgnoreCase) >= 0) { return ResolvePick(keyValuePair.Value.Pick(vanillaClipName, "contains:" + keyValuePair.Key), out replacementClip); } } return SoundReplacementResolve.NoMatch; } private static SoundReplacementResolve ResolvePick(ReplacementPick pick, out AudioClip replacementClip) { replacementClip = null; if (pick.KeepVanilla) { return SoundReplacementResolve.KeepVanilla; } replacementClip = pick.Clip; if (!((Object)(object)replacementClip != (Object)null)) { return SoundReplacementResolve.NoMatch; } return SoundReplacementResolve.Replace; } public static bool ShouldLogUnknownClip(string vanillaClipName) { if (string.IsNullOrWhiteSpace(vanillaClipName)) { return false; } return UnknownLoggedOnce.Add(vanillaClipName); } private static void Reload() { ClipsByVanillaName.Clear(); ContainsRules.Clear(); ManagedClips.Clear(); UnknownLoggedOnce.Clear(); DiscoveredClipNames.Clear(); _resolveCacheFrame = -1; _resolveCacheByClipId.Clear(); _resolveCacheDebugOncePerClipThisFrame.Clear(); VanillaClipRefByExactName.Clear(); _audioSourceToVanillaClipName = new ConditionalWeakTable<AudioSource, string>(); _managedRerollStickyBySource = new ConditionalWeakTable<AudioSource, ManagedRerollSticky>(); string text = (_customSoundsRootPath = Path.Combine(Plugin.AssemblyDirectory, "CustomSounds")); string text2 = ModConfig.DiscoveredClipsOutputPath.Value?.Trim(); _discoveredOutputRootPath = (string.IsNullOrWhiteSpace(text2) ? text : text2); Directory.CreateDirectory(text); Directory.CreateDirectory(_discoveredOutputRootPath); LoadExistingDiscoveredClips(); string text3 = Path.Combine(text, "replacements.json"); if (!File.Exists(text3)) { WriteDefaultMappingTemplate(text3); _logger.LogWarning((object)("Missing mapping file: " + text3 + ". Template created.")); return; } List<ReplacementsJsonParser.Entry> list; try { list = ReplacementsJsonParser.Parse(File.ReadAllText(text3, Encoding.UTF8)); } catch (Exception ex) { _logger.LogError((object)("replacements.json invalide : " + ex.Message)); return; } if (list == null || list.Count == 0) { _logger.LogWarning((object)("No valid mapping entry found in " + text3)); return; } Dictionary<string, ReplacementsJsonParser.Entry> dictionary = new Dictionary<string, ReplacementsJsonParser.Entry>(StringComparer.Ordinal); foreach (ReplacementsJsonParser.Entry item in list) { dictionary[item.MappingKey] = item; } foreach (KeyValuePair<string, ReplacementsJsonParser.Entry> item2 in dictionary) { Register(text, item2.Value); } } public static void TrackDiscoveredClip(string vanillaClipName) { if (string.IsNullOrWhiteSpace(vanillaClipName) || !DiscoveredClipNames.Add(vanillaClipName) || !ModConfig.WriteDiscoveredClipsHierarchyJson.Value) { return; } try { if (!string.IsNullOrWhiteSpace(_discoveredOutputRootPath)) { Directory.CreateDirectory(_discoveredOutputRootPath); string text = ModConfig.DiscoveredClipsHierarchyFileName.Value; if (string.IsNullOrWhiteSpace(text)) { text = "discovered_clips_hierarchy.json"; } SaveDiscoveredHierarchyJson(Path.Combine(_discoveredOutputRootPath, text)); } } catch (Exception ex) { _logger.LogWarning((object)("Failed to write discovered clip '" + vanillaClipName + "': " + ex.Message)); } } private static void Register(string root, ReplacementsJsonParser.Entry entry) { string mappingKey = entry.MappingKey; string text = "exact"; string text2 = mappingKey; if (mappingKey.StartsWith("contains:", StringComparison.OrdinalIgnoreCase)) { text = "contains"; text2 = mappingKey.Substring("contains:".Length).Trim(); } else if (mappingKey.StartsWith("exact:", StringComparison.OrdinalIgnoreCase)) { text = "exact"; text2 = mappingKey.Substring("exact:".Length).Trim(); } if (string.IsNullOrWhiteSpace(text2)) { _logger.LogWarning((object)("Ignoring empty mapping key: '" + mappingKey + "'")); return; } if (entry.Variants == null || entry.Variants.Count == 0) { _logger.LogWarning((object)("No variants for mapping key: '" + mappingKey + "'")); return; } List<float> list = new List<float>(); List<PickerSlot> list2 = new List<PickerSlot>(); List<string> list3 = new List<string>(); foreach (ReplacementsJsonParser.Variant variant in entry.Variants) { if (variant.WeightPercent < 0f) { _logger.LogWarning((object)("Negative weight ignored under '" + mappingKey + "'")); } else if (variant.IsVanilla) { list.Add(variant.WeightPercent); list2.Add(new PickerSlot(isVanilla: true, null)); list3.Add($"vanilla ({variant.WeightPercent}%)"); } else { if (string.IsNullOrWhiteSpace(variant.File)) { continue; } string text3 = Path.Combine(root, variant.File.Trim()); if (!File.Exists(text3)) { _logger.LogWarning((object)("Missing sound file: " + text3 + " (cle '" + mappingKey + "')")); continue; } AudioClip val = ClipFileLoader.LoadFromPath(text3); if ((Object)(object)val == (Object)null) { _logger.LogWarning((object)("Failed to load clip: " + text3)); continue; } ((Object)val).name = "noble_" + variant.File; list.Add(variant.WeightPercent); list2.Add(new PickerSlot(isVanilla: false, val)); ManagedClips.Add(val); list3.Add($"{variant.File} ({variant.WeightPercent}%)"); } } if (list2.Count == 0) { _logger.LogWarning((object)("No loadable variants for '" + mappingKey + "'")); return; } float num = list.Sum(); float[] array = new float[list2.Count]; if (num <= 0f) { _logger.LogWarning((object)("Sum of weights is 0 for '" + mappingKey + "', using equal shares.")); float num2 = 1f / (float)list2.Count; for (int i = 0; i < list2.Count; i++) { array[i] = num2; } } else { if (Math.Abs(num - 100f) > 0.01f) { _logger.LogInfo((object)$"Weights for '{mappingKey}' sum to {num:0.##}% (not 100); normalizing."); } float num3 = 1f / num; for (int j = 0; j < list2.Count; j++) { array[j] = list[j] * num3; } } WeightedClipPicker value = new WeightedClipPicker(list2.ToArray(), array); if (text == "contains") { ContainsRules.Add(new KeyValuePair<string, WeightedClipPicker>(text2, value)); } else { ClipsByVanillaName[text2] = value; } string text4 = string.Join(", ", list3); _logger.LogInfo((object)("Loaded replacement [" + text + "] '" + text2 + "' <- " + text4)); } private static void WriteDefaultMappingTemplate(string mappingFilePath) { File.WriteAllText(mappingFilePath, "{\r\n \"vanilla_clip_name_exact\": \"mon_son.ogg\",\r\n \"contains:footstep\": [\r\n { \"file\": \"pas_a.ogg\", \"weight\": 50 },\r\n { \"vanilla\": true, \"weight\": 25 },\r\n { \"file\": \"pas_b.ogg\", \"weight\": 25 }\r\n ]\r\n}", Encoding.UTF8); } private static void LoadExistingDiscoveredClips() { try { if (ModConfig.WriteDiscoveredClipsHierarchyJson.Value) { string text = ModConfig.DiscoveredClipsHierarchyFileName.Value; if (string.IsNullOrWhiteSpace(text)) { text = "discovered_clips_hierarchy.json"; } string path = Path.Combine(_discoveredOutputRootPath, text); if (File.Exists(path)) { LoadDiscoveredNamesFromHierarchyJson(File.ReadAllText(path, Encoding.UTF8)); } } } catch (Exception ex) { _logger.LogWarning((object)("Failed to load discovered clips file: " + ex.Message)); } } private static void LoadDiscoveredNamesFromHierarchyJson(string json) { foreach (Match item in Regex.Matches(json, "\"name\"\\s*:\\s*\"(?<v>(?:[^\"\\\\]|\\\\.)*)\"", RegexOptions.IgnoreCase)) { string[] array = UnescapeJsonString(item.Groups["v"].Value).Split(new string[1] { " | " }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (!string.IsNullOrWhiteSpace(text)) { DiscoveredClipNames.Add(text); } } } if (json.IndexOf("\"__clips\"", StringComparison.OrdinalIgnoreCase) < 0) { return; } foreach (Match item2 in Regex.Matches(json, "\"__clips\"\\s*:\\s*\\[(?<arr>[^\\]]*)\\]", RegexOptions.IgnoreCase)) { foreach (Match item3 in Regex.Matches(item2.Groups["arr"].Value, "\"(?<v>(?:[^\"\\\\]|\\\\.)*)\"")) { string text2 = UnescapeJsonString(item3.Groups["v"].Value).Trim(); if (!string.IsNullOrWhiteSpace(text2)) { DiscoveredClipNames.Add(text2); } } } } private static string JsonEscapeString(string s) { return s.Replace("\\", "\\\\").Replace("\"", "\\\""); } private static string UnescapeJsonString(string s) { if (string.IsNullOrEmpty(s)) { return s; } StringBuilder stringBuilder = new StringBuilder(s.Length); for (int i = 0; i < s.Length; i++) { if (s[i] != '\\' || i + 1 >= s.Length) { stringBuilder.Append(s[i]); continue; } i++; switch (s[i]) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; default: stringBuilder.Append(s[i]); break; } } return stringBuilder.ToString(); } private static void SaveDiscoveredHierarchyJson(string outputPath) { ClipTreeNode clipTreeNode = new ClipTreeNode(); foreach (string discoveredClipName in DiscoveredClipNames) { List<string> smartTokens = GetSmartTokens(discoveredClipName); if (smartTokens.Count == 0) { continue; } ClipTreeNode clipTreeNode2 = clipTreeNode; for (int i = 0; i < smartTokens.Count; i++) { string key = smartTokens[i]; if (!clipTreeNode2.Children.TryGetValue(key, out var value)) { value = new ClipTreeNode(); clipTreeNode2.Children[key] = value; } clipTreeNode2 = value; } if (!clipTreeNode2.Clips.Contains(discoveredClipName)) { clipTreeNode2.Clips.Add(discoveredClipName); } } StringBuilder stringBuilder = new StringBuilder(); WriteNodeJson(stringBuilder, clipTreeNode, 0); File.WriteAllText(outputPath, stringBuilder.ToString(), Encoding.UTF8); } private static List<string> GetSmartTokens(string clipName) { List<string> list = new List<string>(); foreach (Match item in Regex.Matches(clipName.ToLowerInvariant(), "[a-z0-9]+")) { string text = item.Value.Trim(); if (!string.IsNullOrWhiteSpace(text)) { list.Add(text); } } return list; } private static void WriteNodeJson(StringBuilder sb, ClipTreeNode node, int indent) { string value = new string(' ', indent * 2); string value2 = new string(' ', (indent + 1) * 2); sb.AppendLine("{"); List<string> list = new List<string>(node.Children.Keys); list.Sort(StringComparer.Ordinal); bool flag = true; for (int i = 0; i < list.Count; i++) { if (!flag) { sb.AppendLine(","); } flag = false; string text = list[i]; sb.Append(value2).Append('"').Append(text) .Append("\": "); WriteNodeJson(sb, node.Children[text], indent + 1); } if (node.Clips.Count > 0) { if (!flag) { sb.AppendLine(","); } flag = false; node.Clips.Sort(StringComparer.Ordinal); string s = ((node.Clips.Count == 1) ? node.Clips[0] : string.Join(" | ", node.Clips)); sb.Append(value2).Append("\"name\": \"").Append(JsonEscapeString(s)) .Append('"'); } if (!flag) { sb.AppendLine(); } sb.Append(value).Append("}"); } } } namespace NobleMod.Patches { [HarmonyPatch(typeof(AudioSource), "PlayOneShot", new Type[] { typeof(AudioClip) })] internal static class CustomSoundPlayOneShotPatch { private static bool _loggedRuntimeError; private static readonly Dictionary<string, int> DebugEntryCounts = new Dictionary<string, int>(); [HarmonyPrefix] private static void BeforePlayOneShot(AudioSource __instance, ref AudioClip clip) { LogHookEntry("AudioSource.PlayOneShot(AudioClip)", __instance, clip); SafeTryReplace(__instance, ref clip); } internal static void TryReplace(AudioSource source, ref AudioClip clip) { if (!ModConfig.EnableCustomSounds.Value || (Object)(object)source == (Object)null || (Object)(object)clip == (Object)null) { return; } if (SoundBank.IsManagedClip(clip)) { SoundBank.TryRerollManagedClipOnSource(source, ref clip); } else { if (!IsAudioSourceInScope(source)) { return; } SoundBank.RememberVanillaPlaybackContext(source, clip); AudioClip replacementClip; switch (SoundBank.TryResolveReplacement(((Object)clip).name, clip, out replacementClip)) { case SoundReplacementResolve.NoMatch: SoundBank.TrackDiscoveredClip(((Object)clip).name); if (ModConfig.LogUnknownVanillaClipNamesOnce.Value && SoundBank.ShouldLogUnknownClip(((Object)clip).name)) { Plugin.Log.LogInfo((object)("No mapping for vanilla clip: '" + ((Object)clip).name + "'")); } return; case SoundReplacementResolve.KeepVanilla: return; } if (ModConfig.LogReplacements.Value) { Plugin.Log.LogInfo((object)("Replace '" + ((Object)clip).name + "' -> '" + ((Object)replacementClip).name + "'")); } clip = replacementClip; } } internal static void SafeTryReplace(AudioSource source, ref AudioClip clip) { try { TryReplace(source, ref clip); } catch (Exception arg) { if (!_loggedRuntimeError) { _loggedRuntimeError = true; Plugin.Log.LogError((object)$"Runtime error in audio replacement patch: {arg}"); } } } internal static bool IsAudioSourceInScope(AudioSource source) { return SoundBank.IsAudioSourceInFilterScope(source); } internal static void LogHookEntry(string hookName, AudioSource source, AudioClip clip) { if (ModConfig.DebugLogHookEntrypoints.Value && !ShouldSuppressDebugEntry(hookName, source, clip)) { int num = Mathf.Max(0, ModConfig.DebugLogHookEntrypointsPerMethod.Value); if (!DebugEntryCounts.TryGetValue(hookName, out var value)) { value = 0; } if (num <= 0 || value < num) { DebugEntryCounts[hookName] = value + 1; string text = (((Object)(object)source != (Object)null) ? ((Object)source).name : "<null-source>"); string text2 = (((Object)(object)clip != (Object)null) ? ((Object)clip).name : "<null-clip>"); Plugin.Log.LogInfo((object)("[HookEntry] " + hookName + " src='" + text + "' clip='" + text2 + "'")); } } } internal static bool ShouldSuppressDebugEntry(string hookName, AudioSource source, AudioClip clip) { if (!ModConfig.DebugSuppressMenuSpam.Value) { return false; } string text = (((Object)(object)source != (Object)null) ? ((Object)source).name : string.Empty); string text2 = (((Object)(object)clip != (Object)null) ? ((Object)clip).name : string.Empty); string text3 = (hookName + " " + text + " " + text2).ToLowerInvariant(); if (text3.Contains("menu") || text3.Contains("ui ")) { return true; } if (hookName.IndexOf("PlayHelper", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } return false; } } [HarmonyPatch(typeof(AudioSource), "PlayOneShot", new Type[] { typeof(AudioClip), typeof(float) })] internal static class CustomSoundPlayOneShotWithVolumePatch { [HarmonyPrefix] private static void BeforePlayOneShot(AudioSource __instance, ref AudioClip clip, float volumeScale) { CustomSoundPlayOneShotPatch.LogHookEntry("AudioSource.PlayOneShot(AudioClip,float)", __instance, clip); CustomSoundPlayOneShotPatch.SafeTryReplace(__instance, ref clip); } } [HarmonyPatch] internal static class CustomSoundAudioSourcePlayPatch { private static MethodBase TargetMethod() { return AccessTools.Method(typeof(AudioSource), "Play", Type.EmptyTypes, (Type[])null); } [HarmonyPrefix] private static void BeforePlay(AudioSource __instance) { CustomSoundPlayOneShotPatch.LogHookEntry("AudioSource.Play()", __instance, ((Object)(object)__instance != (Object)null) ? __instance.clip : null); if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.clip == (Object)null)) { AudioClip clip = __instance.clip; CustomSoundPlayOneShotPatch.SafeTryReplace(__instance, ref clip); __instance.clip = clip; } } } [HarmonyPatch] internal static class CustomSoundGameSoundPatch { private static bool _loggedRuntimeError; private static readonly Dictionary<string, int> DebugEntryCounts = new Dictionary<string, int>(); private static IEnumerable<MethodBase> TargetMethods() { Type type = AccessTools.TypeByName("Sound"); if (type == null) { return Enumerable.Empty<MethodBase>(); } return from m in AccessTools.GetDeclaredMethods(type) where m.Name == "Play" || m.Name == "PlayLoop" || m.Name == "PlayOneShot" select m; } [HarmonyPrefix] private static void BeforeSoundMethod(object __instance, MethodBase __originalMethod) { try { if (!ModConfig.EnableCustomSounds.Value || __instance == null) { return; } Traverse val = Traverse.Create(__instance); AudioSource val2 = val.Property("Source", (object[])null).GetValue<AudioSource>() ?? val.Field("Source").GetValue<AudioSource>(); LogHookEntry(__originalMethod.Name, val2); if ((Object)(object)val2 == (Object)null || (Object)(object)val2.clip == (Object)null) { return; } if (SoundBank.IsManagedClip(val2.clip)) { AudioClip clip = val2.clip; if (CustomSoundPlayOneShotPatch.IsAudioSourceInScope(val2) && SoundBank.TryRerollManagedClipOnSource(val2, ref clip)) { val2.clip = clip; if (SoundBank.IsManagedClip(clip)) { val.Field("LoopClip").SetValue((object)clip); } else { SyncSoundLoopClipIfStaleCustom(val, clip); } } } else { if (!CustomSoundPlayOneShotPatch.IsAudioSourceInScope(val2)) { return; } SoundBank.RememberVanillaPlaybackContext(val2, val2.clip); AudioClip clip2 = val2.clip; AudioClip replacementClip; switch (SoundBank.TryResolveReplacement(((Object)clip2).name, clip2, out replacementClip)) { case SoundReplacementResolve.NoMatch: SoundBank.TrackDiscoveredClip(((Object)clip2).name); if (ModConfig.LogUnknownVanillaClipNamesOnce.Value && SoundBank.ShouldLogUnknownClip(((Object)clip2).name)) { Plugin.Log.LogInfo((object)("[Sound." + __originalMethod.Name + "] No mapping for vanilla clip: '" + ((Object)clip2).name + "'")); } SyncSoundLoopClipIfStaleCustom(val, clip2); return; case SoundReplacementResolve.KeepVanilla: SyncSoundLoopClipIfStaleCustom(val, clip2); return; } if (ModConfig.LogReplacements.Value) { Plugin.Log.LogInfo((object)("[Sound." + __originalMethod.Name + "] Replace '" + ((Object)clip2).name + "' -> '" + ((Object)replacementClip).name + "'")); } val2.clip = replacementClip; val.Field("LoopClip").SetValue((object)replacementClip); } } catch (Exception arg) { if (!_loggedRuntimeError) { _loggedRuntimeError = true; Plugin.Log.LogError((object)$"Runtime error in Sound patch: {arg}"); } } } private static void SyncSoundLoopClipIfStaleCustom(Traverse tr, AudioClip vanillaClip) { if ((Object)(object)vanillaClip == (Object)null) { return; } try { AudioClip value = tr.Field("LoopClip").GetValue<AudioClip>(); if ((Object)(object)value == (Object)null || SoundBank.IsManagedClip(value)) { tr.Field("LoopClip").SetValue((object)vanillaClip); } } catch { } } private static void LogHookEntry(string methodName, AudioSource source) { if (!ModConfig.DebugLogHookEntrypoints.Value) { return; } string text = "Sound." + methodName; int num = Mathf.Max(0, ModConfig.DebugLogHookEntrypointsPerMethod.Value); if (!DebugEntryCounts.TryGetValue(text, out var value)) { value = 0; } if (num <= 0 || value < num) { DebugEntryCounts[text] = value + 1; string text2 = (((Object)(object)source != (Object)null) ? ((Object)source).name : "<null-source>"); string text3 = (((Object)(object)source != (Object)null && (Object)(object)source.clip != (Object)null) ? ((Object)source.clip).name : "<null-clip>"); if (!CustomSoundPlayOneShotPatch.ShouldSuppressDebugEntry(text, source, ((Object)(object)source != (Object)null) ? source.clip : null)) { Plugin.Log.LogInfo((object)("[HookEntry] " + text + " src='" + text2 + "' clip='" + text3 + "'")); } } } } [HarmonyPatch] internal static class CustomSoundAudioSourcePlayAnyPatch { private static bool _loggedRuntimeError; private static readonly Dictionary<string, int> DebugEntryCounts = new Dictionary<string, int>(); private static IEnumerable<MethodBase> TargetMethods() { return from m in AccessTools.GetDeclaredMethods(typeof(AudioSource)) where m.Name.StartsWith("Play", StringComparison.Ordinal) where !IsHandledByDedicatedAudioSourcePlayPatch(m) select m; } private static bool IsHandledByDedicatedAudioSourcePlayPatch(MethodInfo m) { if (m.Name == "Play" && m.GetParameters().Length == 0) { return true; } if (m.Name == "PlayOneShot") { ParameterInfo[] parameters = m.GetParameters(); if (parameters.Length == 1 && parameters[0].ParameterType == typeof(AudioClip)) { return true; } if (parameters.Length == 2 && parameters[0].ParameterType == typeof(AudioClip) && parameters[1].ParameterType == typeof(float)) { return true; } } return false; } [HarmonyPrefix] private static void BeforeAnyPlay(MethodBase __originalMethod, AudioSource __instance, object[] __args) { try { if (!ModConfig.EnableCustomSounds.Value) { return; } AudioClip val = null; if (__args != null) { foreach (object obj in __args) { AudioClip val2 = (AudioClip)((obj is AudioClip) ? obj : null); if (val2 != null) { val = val2; break; } } } AudioClip val3 = (((Object)(object)__instance != (Object)null) ? __instance.clip : null); AudioClip val4 = val ?? val3; LogHookEntry("AudioSource." + __originalMethod.Name, __instance, val4); if ((Object)(object)__instance == (Object)null || (Object)(object)val4 == (Object)null) { return; } if (SoundBank.IsManagedClip(val4)) { AudioClip clip = val4; if (!CustomSoundPlayOneShotPatch.IsAudioSourceInScope(__instance) || !SoundBank.TryRerollManagedClipOnSource(__instance, ref clip)) { return; } if ((Object)(object)val != (Object)null && __args != null) { for (int j = 0; j < __args.Length; j++) { if (__args[j] is AudioClip) { __args[j] = clip; break; } } } else { __instance.clip = clip; } } else { if (!CustomSoundPlayOneShotPatch.IsAudioSourceInScope(__instance)) { return; } SoundBank.RememberVanillaPlaybackContext(__instance, val4); AudioClip replacementClip; switch (SoundBank.TryResolveReplacement(((Object)val4).name, val4, out replacementClip)) { case SoundReplacementResolve.NoMatch: SoundBank.TrackDiscoveredClip(((Object)val4).name); if (ModConfig.LogUnknownVanillaClipNamesOnce.Value && SoundBank.ShouldLogUnknownClip(((Object)val4).name)) { Plugin.Log.LogInfo((object)("[AudioSource." + __originalMethod.Name + "] No mapping for vanilla clip: '" + ((Object)val4).name + "'")); } return; case SoundReplacementResolve.KeepVanilla: return; } if (ModConfig.LogReplacements.Value) { Plugin.Log.LogInfo((object)("[AudioSource." + __originalMethod.Name + "] Replace '" + ((Object)val4).name + "' -> '" + ((Object)replacementClip).name + "'")); } if ((Object)(object)val != (Object)null && __args != null) { for (int k = 0; k < __args.Length; k++) { if (__args[k] is AudioClip) { __args[k] = replacementClip; break; } } } else { __instance.clip = replacementClip; } } } catch (Exception arg) { if (!_loggedRuntimeError) { _loggedRuntimeError = true; Plugin.Log.LogError((object)$"Runtime error in AudioSource Play* patch: {arg}"); } } } private static void LogHookEntry(string hookName, AudioSource source, AudioClip clip) { if (ModConfig.DebugLogHookEntrypoints.Value && !CustomSoundPlayOneShotPatch.ShouldSuppressDebugEntry(hookName, source, clip)) { int num = Mathf.Max(0, ModConfig.DebugLogHookEntrypointsPerMethod.Value); if (!DebugEntryCounts.TryGetValue(hookName, out var value)) { value = 0; } if (num <= 0 || value < num) { DebugEntryCounts[hookName] = value + 1; string text = (((Object)(object)source != (Object)null) ? ((Object)source).name : "<null-source>"); string text2 = (((Object)(object)clip != (Object)null) ? ((Object)clip).name : "<null-clip>"); Plugin.Log.LogInfo((object)("[HookEntry] " + hookName + " src='" + text + "' clip='" + text2 + "'")); } } } } [HarmonyPatch] internal static class CustomSoundAudioSourceSetClipPatch { private static bool _loggedRuntimeError; private static readonly Dictionary<string, int> DebugEntryCounts = new Dictionary<string, int>(); private static MethodBase TargetMethod() { return AccessTools.PropertySetter(typeof(AudioSource), "clip"); } [HarmonyPrefix] private static void BeforeSetClip(AudioSource __instance, ref AudioClip value) { try { if (!ModConfig.EnableCustomSounds.Value) { return; } if (ModConfig.DebugLogHookEntrypoints.Value && !CustomSoundPlayOneShotPatch.ShouldSuppressDebugEntry("AudioSource.set_clip", __instance, value)) { int num = Mathf.Max(0, ModConfig.DebugLogHookEntrypointsPerMethod.Value); if (!DebugEntryCounts.TryGetValue("AudioSource.set_clip", out var value2)) { value2 = 0; } if (num <= 0 || value2 < num) { DebugEntryCounts["AudioSource.set_clip"] = value2 + 1; string text = (((Object)(object)__instance != (Object)null) ? ((Object)__instance).name : "<null-source>"); string text2 = (((Object)(object)value != (Object)null) ? ((Object)value).name : "<null-clip>"); Plugin.Log.LogInfo((object)("[HookEntry] AudioSource.set_clip src='" + text + "' clip='" + text2 + "'")); } } if ((Object)(object)__instance == (Object)null || (Object)(object)value == (Object)null) { return; } if (SoundBank.IsManagedClip(value)) { AudioClip clip = value; if (SoundBank.TryRerollManagedClipOnSource(__instance, ref clip)) { value = clip; } } else { if (!CustomSoundPlayOneShotPatch.IsAudioSourceInScope(__instance)) { return; } SoundBank.RememberVanillaPlaybackContext(__instance, value); AudioClip replacementClip; switch (SoundBank.TryResolveReplacement(((Object)value).name, value, out replacementClip)) { case SoundReplacementResolve.NoMatch: SoundBank.TrackDiscoveredClip(((Object)value).name); if (ModConfig.LogUnknownVanillaClipNamesOnce.Value && SoundBank.ShouldLogUnknownClip(((Object)value).name)) { Plugin.Log.LogInfo((object)("[AudioSource.set_clip] No mapping for vanilla clip: '" + ((Object)value).name + "'")); } return; case SoundReplacementResolve.KeepVanilla: return; } if (ModConfig.LogReplacements.Value) { Plugin.Log.LogInfo((object)("[AudioSource.set_clip] Replace '" + ((Object)value).name + "' -> '" + ((Object)replacementClip).name + "'")); } value = replacementClip; } } catch (Exception arg) { if (!_loggedRuntimeError) { _loggedRuntimeError = true; Plugin.Log.LogError((object)$"Runtime error in AudioSource.set_clip patch: {arg}"); } } } } [HarmonyPatch] internal static class LevelEnemyOverridePatches { private static readonly HashSet<int> LoggedLevelNoRule = new HashSet<int>(); private static readonly HashSet<string> LoggedFallbacks = new HashSet<string>(); private static readonly HashSet<int> AppliedOverrideLevels = new HashSet<int>(); private static MethodBase TargetMethod() { Type type = AccessTools.TypeByName("EnemyDirector"); if (type == null) { return null; } return AccessTools.Method(type, "GetEnemy", (Type[])null, (Type[])null); } [HarmonyPostfix] private static void AfterGetEnemy(MethodBase __originalMethod, object __instance, ref object __result) { if (__instance == null) { return; } try { if (!ModConfig.EnableSpawnOverrides.Value || __result == null || !LooksLikeEnemySetupResult(__result)) { return; } int num = TryGetCurrentLevelNumber(); if (num <= 0 || AppliedOverrideLevels.Contains(num)) { return; } if (!LevelEnemyOverrideBank.TryGetMobKey(num, out var mobKey)) { if (ModConfig.LogSpawnOverrides.Value && LoggedLevelNoRule.Add(num)) { Plugin.Log.LogInfo((object)$"[SpawnOverride] Aucun override pour niveau {num}. Spawn vanilla conserve."); } return; } object obj = FindBestSetupMatch(__instance, mobKey, __result.GetType()); if (obj == null) { if (ModConfig.LogSpawnOverrides.Value) { string item = $"{num}:{mobKey}"; if (LoggedFallbacks.Add(item)) { Plugin.Log.LogWarning((object)$"[SpawnOverride] Niveau {num}: mob '{mobKey}' introuvable, fallback vanilla conserve."); } } } else { __result = obj; AppliedOverrideLevels.Add(num); if (ModConfig.LogSpawnOverrides.Value) { Plugin.Log.LogInfo((object)$"[SpawnOverride] Niveau {num}: mob principal force vers '{mobKey}' (applique une seule fois)."); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[SpawnOverride] Erreur runtime: " + ex.Message)); } } private static bool LooksLikeEnemySetupResult(object result) { string name = result.GetType().Name; if (name.IndexOf("Enemy", StringComparison.OrdinalIgnoreCase) >= 0 && name.IndexOf("Setup", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } try { return AccessTools.Field(result.GetType(), "spawnObjects") != null; } catch { return false; } } private static int TryGetCurrentLevelNumber() { try { Type type = AccessTools.TypeByName("RunManager"); if (type == null) { return 0; } object obj = AccessTools.Field(type, "instance")?.GetValue(null); if (obj == null) { return 0; } object value = Traverse.Create(obj).Field("levelsCompleted").GetValue(); if (value == null) { return 0; } return Convert.ToInt32(value) + 1; } catch { return 0; } } private static object FindBestSetupMatch(object enemyDirector, string desiredMobKey, Type expectedSetupType) { List<object> list = CollectEnemySetups(enemyDirector, expectedSetupType); if (list.Count == 0) { return null; } string token = desiredMobKey.Trim().ToLowerInvariant(); foreach (object item in list) { if (SetupContainsToken(item, token)) { return item; } } foreach (object item2 in list) { if (SetupMatchesAlias(item2, token)) { return item2; } } return null; } private static List<object> CollectEnemySetups(object enemyDirector, Type expectedSetupType) { List<object> list = new List<object>(); List<FieldInfo> declaredFields = AccessTools.GetDeclaredFields(enemyDirector.GetType()); for (int i = 0; i < declaredFields.Count; i++) { FieldInfo fieldInfo = declaredFields[i]; object value; try { value = fieldInfo.GetValue(enemyDirector); } catch { continue; } if (value is IEnumerable enumerable && !(value is string)) { foreach (object item in enumerable) { if (item != null && expectedSetupType.IsInstanceOfType(item) && !list.Contains(item)) { list.Add(item); } } } else if (value != null && expectedSetupType.IsInstanceOfType(value) && !list.Contains(value)) { list.Add(value); } } return list; } private static bool SetupContainsToken(object setup, string token) { return BuildSearchBlob(setup).IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0; } private static bool SetupMatchesAlias(object setup, string token) { string text = BuildSearchBlob(setup).ToLowerInvariant(); switch (token) { case "huntsman": case "hunter": return text.Contains("hunter"); case "headman": return text.Contains("headman"); default: return false; } } private static string BuildSearchBlob(object setup) { List<string> list = new List<string>(); Type type = setup.GetType(); list.Add(type.Name); string value = Traverse.Create(setup).Field("name").GetValue<string>(); if (!string.IsNullOrWhiteSpace(value)) { list.Add(value); } if (Traverse.Create(setup).Field("spawnObjects").GetValue() is IEnumerable enumerable) { foreach (object item in enumerable) { if (item != null) { Traverse obj = Traverse.Create(item); string value2 = obj.Field("prefabName").GetValue<string>(); string value3 = obj.Field("resourcePath").GetValue<string>(); if (!string.IsNullOrWhiteSpace(value2)) { list.Add(value2); } if (!string.IsNullOrWhiteSpace(value3)) { list.Add(value3); } } } } return string.Join(" ", list); } } }