using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Logging; using HarmonyLib; using LethalEmotesAPI.ImportV2; using Microsoft.CodeAnalysis; using UnityEngine; [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("LGUEmotes")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+8c5521e37f37e3d76518a16d04e5d1051842f105")] [assembly: AssemblyProduct("LGUEmotes")] [assembly: AssemblyTitle("LGUEmotes")] [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 LGUEmotes { [BepInPlugin("com.y4ngz.lguemotes", "LGUEmotes", "0.2.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { private struct Registered { public string slug; public string displayName; public AnimationClip clip; public AudioClip audio; public int rarity; public bool looping; } [Serializable] public class EmoteRegistry { public string bundle_name; public string source_humanoid_avatar_fbx; public float fbx_animation_sample_rate; public List emotes; } [Serializable] public class EmoteEntry { public string slug; public string display_name; public string bucket; public string source_fbx; public string animation_asset_name; public string audio_asset_name; public string audio_path; public int rarity; public float speed_multiplier; public float snippet_length_sec; public float snippet_bpm; public bool loop_time; public float trim_start_sec; public float trim_end_sec; public string shares_motion_with; public bool disabled; public bool loop_audio = true; public EmoteSegments segments; } [Serializable] public class EmoteSegments { public EmoteSegment begin; public EmoteSegment loop; public EmoteSegment stop; } [Serializable] public class EmoteSegment { public string source_fbx; public string animation_asset_name; public bool loop_time; } public const string GUID = "com.y4ngz.lguemotes"; public const string NAME = "LGUEmotes"; public const string VERSION = "0.2.0"; private const string BUNDLE_FILENAME = "y4ngz_emotes.lethalbundle"; private const string REGISTRY_ASSET = "y4ngz_emotes_registry"; internal static ManualLogSource Log; private AssetBundle _bundle; private Dictionary _clipsByName; private Dictionary _audioByName; private readonly HashSet _seededAudioKeys = new HashSet(); private readonly HashSet _clipsAlreadyRegistered = new HashSet(); private readonly List _registered = new List(); private static Harmony _harmony; private static Type _tEmoteAudioPlayer; private static Type _tEmoteAudioSource; private static FieldInfo _fShipSpeaker; private static PropertyInfo _pShipSpeaker; private static bool _ueDumpDone; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; string text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "y4ngz_emotes.lethalbundle"); if (!File.Exists(text)) { Log.LogError((object)("emote bundle not found at " + text + " — emotes will not be registered")); return; } _bundle = AssetBundle.LoadFromFile(text); if ((Object)(object)_bundle == (Object)null) { Log.LogError((object)("AssetBundle.LoadFromFile returned null for " + text)); return; } _clipsByName = new Dictionary(); AnimationClip[] array = _bundle.LoadAllAssets(); foreach (AnimationClip val in array) { if (!((Object)(object)val == (Object)null) && !((Object)val).name.StartsWith("__preview__")) { _clipsByName[((Object)val).name] = val; } } Log.LogInfo((object)$"loaded {_clipsByName.Count} animation clips from bundle"); AudioClip[] array2 = _bundle.LoadAllAssets(); Log.LogInfo((object)$"[AUDIO-DIAG] bundle.LoadAllAssets() returned {array2.Length} clips:"); _audioByName = new Dictionary(); AudioClip[] array3 = array2; foreach (AudioClip val2 in array3) { if (!((Object)(object)val2 == (Object)null)) { _audioByName[((Object)val2).name] = val2; Log.LogInfo((object)$"[AUDIO-DIAG] '{((Object)val2).name}' length={val2.length:F2}s channels={val2.channels} freq={val2.frequency}Hz"); } } EmoteRegistry emoteRegistry = LoadRegistry(_bundle); if (emoteRegistry == null || emoteRegistry.emotes == null) { Log.LogError((object)"failed to parse registry asset 'y4ngz_emotes_registry' from bundle"); return; } Log.LogInfo((object)$"registry loaded with {emoteRegistry.emotes.Count} emotes"); int num = 0; foreach (EmoteEntry emote in emoteRegistry.emotes) { if (TryRegister(emote)) { num++; } } Log.LogInfo((object)$"registered {num}/{emoteRegistry.emotes.Count} emotes with LethalEmotesAPI"); TryIntegrateWithTooManyEmotes(); } private EmoteRegistry LoadRegistry(AssetBundle bundle) { TextAsset val = bundle.LoadAsset("y4ngz_emotes_registry"); if ((Object)(object)val == (Object)null) { Log.LogWarning((object)"registry TextAsset 'y4ngz_emotes_registry' not in bundle"); return null; } try { return JsonUtility.FromJson(val.text); } catch (Exception arg) { Log.LogError((object)$"JsonUtility.FromJson failed: {arg}"); return null; } } private bool TryRegister(EmoteEntry e) { try { if (e.disabled) { Log.LogInfo((object)("emote '" + e.slug + "': disabled in registry — skipped (no in-game registration)")); return false; } switch (e.bucket) { case "showpiece": return RegisterShowpieceOrLoopable(e, looping: false); case "loopable": return RegisterShowpieceOrLoopable(e, looping: true); case "pose": return RegisterPose(e); default: Log.LogWarning((object)("emote '" + e.slug + "': unknown bucket '" + e.bucket + "' — skipped")); return false; } } catch (Exception arg) { Log.LogError((object)$"emote '{e.slug}': registration threw — {arg}"); return false; } } private AnimationClip CloneClipIfShared(AnimationClip clip, string slug) { if ((Object)(object)clip == (Object)null) { return null; } if (_clipsAlreadyRegistered.Add(((Object)clip).GetInstanceID())) { return clip; } AnimationClip val = Object.Instantiate(clip); ((Object)val).name = ((Object)clip).name + "__" + slug; Log.LogInfo((object)("emote '" + slug + "': clip '" + ((Object)clip).name + "' shared with prior emote — registering clone '" + ((Object)val).name + "' to avoid LethalEmotesAPI dedup")); return val; } private bool RegisterShowpieceOrLoopable(EmoteEntry e, bool looping) { //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Expected O, but got Unknown _clipsByName.TryGetValue(e.animation_asset_name, out var value); if ((Object)(object)value == (Object)null) { Log.LogWarning((object)("emote '" + e.slug + "': clip '" + e.animation_asset_name + "' not in bundle")); return false; } if (!((Motion)value).isHumanMotion) { Log.LogWarning((object)("emote '" + e.slug + "': clip '" + ((Object)value).name + "' is not humanoid — skipped")); return false; } value = CloneClipIfShared(value, e.slug); AudioClip val = ((e.audio_asset_name != null) ? _bundle.LoadAsset(e.audio_asset_name) : null); Log.LogInfo((object)($"[AUDIO-DIAG] emote '{e.slug}' audio_asset_name='{e.audio_asset_name}' resolved={(Object)(object)val != (Object)null}" + (((Object)(object)val != (Object)null) ? $" -> name='{((Object)val).name}' length={val.length:F2}s" : ""))); if ((Object)(object)val == (Object)null) { Log.LogWarning((object)("emote '" + e.slug + "': audio '" + e.audio_asset_name + "' not in bundle (registering without audio)")); } CustomEmoteParams val2 = new CustomEmoteParams(); val2.displayName = e.display_name; val2.internalName = e.slug; val2.primaryAnimationClips = (AnimationClip[])(object)new AnimationClip[1] { value }; val2.primaryAudioClips = (AudioClip[])(object)((!((Object)(object)val != (Object)null)) ? null : new AudioClip[1] { val }); val2.audioLoops = e.loop_audio; val2.visible = true; val2.willGetClaimedByDMCA = false; EmoteImporter.ImportEmote(val2); _registered.Add(new Registered { slug = e.slug, displayName = e.display_name, clip = value, audio = val, rarity = ClampRarity(e.rarity), looping = looping }); Log.LogInfo((object)string.Format("registered {0} '{1}' (rarity={2})", looping ? "loopable" : "showpiece", e.slug, e.rarity)); return true; } private bool RegisterPose(EmoteEntry e) { //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Expected O, but got Unknown if (e.segments == null || e.segments.loop == null) { Log.LogWarning((object)("emote '" + e.slug + "': pose has no loop segment — skipped")); return false; } _clipsByName.TryGetValue(e.segments.loop.animation_asset_name, out var value); if ((Object)(object)value == (Object)null) { Log.LogWarning((object)("emote '" + e.slug + "': loop clip '" + e.segments.loop.animation_asset_name + "' not in bundle")); return false; } if (!((Motion)value).isHumanMotion) { Log.LogWarning((object)("emote '" + e.slug + "': loop clip is not humanoid — skipped")); return false; } value = CloneClipIfShared(value, e.slug); AudioClip val = ((e.audio_asset_name != null) ? _bundle.LoadAsset(e.audio_asset_name) : null); Log.LogInfo((object)($"[AUDIO-DIAG] emote '{e.slug}' (pose) audio_asset_name='{e.audio_asset_name}' resolved={(Object)(object)val != (Object)null}" + (((Object)(object)val != (Object)null) ? $" -> name='{((Object)val).name}' length={val.length:F2}s" : ""))); if ((Object)(object)val == (Object)null) { Log.LogWarning((object)("emote '" + e.slug + "': audio '" + e.audio_asset_name + "' not in bundle")); } CustomEmoteParams val2 = new CustomEmoteParams(); val2.displayName = e.display_name; val2.internalName = e.slug; val2.primaryAnimationClips = (AnimationClip[])(object)new AnimationClip[1] { value }; val2.primaryAudioClips = (AudioClip[])(object)((!((Object)(object)val != (Object)null)) ? null : new AudioClip[1] { val }); val2.audioLoops = e.loop_audio; val2.visible = true; val2.willGetClaimedByDMCA = false; EmoteImporter.ImportEmote(val2); _registered.Add(new Registered { slug = e.slug, displayName = e.display_name, clip = value, audio = val, rarity = ClampRarity(e.rarity), looping = true }); Log.LogInfo((object)("registered pose '" + e.slug + "' (loop segment only — begin/stop dropped)")); return true; } private static int ClampRarity(int r) { if (r >= 0) { if (r <= 3) { return r; } return 3; } return 0; } private void TryIntegrateWithTooManyEmotes() { if (_registered.Count == 0) { return; } Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "TooManyEmotes"); if (assembly == null) { Log.LogInfo((object)"TooManyEmotes not loaded — skipping LGUGui category injection"); return; } DumpTmeAssembly(assembly); try { Type type = assembly.GetType("TooManyEmotes.EmotesManager"); Type type2 = assembly.GetType("TooManyEmotes.UnlockableEmote"); Type type3 = assembly.GetType("TooManyEmotes.Audio.AudioManager") ?? assembly.GetType("TooManyEmotes.AudioManager"); Log.LogInfo((object)("[AUDIO-DIAG] AudioManager type resolved: " + (type3?.FullName ?? "null"))); if (type == null || type2 == null) { Log.LogWarning((object)"TooManyEmotes types not found — skipping LGUGui injection"); return; } DumpUnlockableEmote(type2); FieldInfo field = type.GetField("allUnlockableEmotes", BindingFlags.Static | BindingFlags.Public); FieldInfo field2 = type.GetField("allUnlockableEmotesDict", BindingFlags.Static | BindingFlags.Public); FieldInfo[] array = new FieldInfo[4] { type.GetField("allEmotesTier0", BindingFlags.Static | BindingFlags.Public), type.GetField("allEmotesTier1", BindingFlags.Static | BindingFlags.Public), type.GetField("allEmotesTier2", BindingFlags.Static | BindingFlags.Public), type.GetField("allEmotesTier3", BindingFlags.Static | BindingFlags.Public) }; if (field == null || field2 == null || array.Any((FieldInfo f) => f == null)) { Log.LogWarning((object)"TooManyEmotes EmotesManager fields not found — TME version mismatch?"); return; } IList list = (IList)field.GetValue(null); IDictionary dictionary = (IDictionary)field2.GetValue(null); if (list == null || dictionary == null) { Log.LogWarning((object)"TooManyEmotes EmotesManager lists are null — BuildEmotesList hasn't run yet (load order issue?)"); return; } IDictionary dictionary2 = null; if (type3 != null) { FieldInfo field3 = type3.GetField("audioClipsDictDmcaFree", BindingFlags.Static | BindingFlags.NonPublic); dictionary2 = field3?.GetValue(null) as IDictionary; Log.LogInfo((object)("[AUDIO-DIAG] audioClipsDictDmcaFree field=" + ((field3 != null) ? "found" : "NOT FOUND") + " dict=" + ((dictionary2 != null) ? "non-null" : "null"))); } int num = dictionary2?.Count ?? (-1); Log.LogInfo((object)$"[AUDIO-DIAG] audioClipsDictDmcaFree count before injection: {num}"); int count = list.Count; int num2 = 0; object obj = null; foreach (Registered item in _registered) { string slug = item.slug; if (dictionary.Contains(slug)) { continue; } object obj2 = Activator.CreateInstance(type2); SetField(obj2, "emoteId", count++); SetField(obj2, "emoteName", slug); SetField(obj2, "displayName", item.displayName); SetField(obj2, "animationClip", item.clip); SetField(obj2, "rarity", item.rarity); SetField(obj2, "purchasable", true); SetField(obj2, "canSyncEmote", true); list.Add(obj2); dictionary.Add(slug, obj2); (array[item.rarity].GetValue(null) as IList)?.Add(obj2); num2++; if ((Object)(object)item.audio != (Object)null) { try { bool num3 = TrySetMember(obj2, "overrideAudioClipName", item.slug); bool flag = TrySetMember(obj2, "overrideAudioLoopClipName", item.slug); if (!num3) { Log.LogWarning((object)("[AUDIO-DIAG] '" + item.slug + "': could not set overrideAudioClipName")); } if (!flag) { Log.LogWarning((object)("[AUDIO-DIAG] '" + item.slug + "': could not set overrideAudioLoopClipName")); } if (!TrySetProperty(obj2, "isBoomboxAudio", true)) { Log.LogWarning((object)("[AUDIO-DIAG] '" + item.slug + "': could not set isBoomboxAudio=true")); } if (dictionary2 != null) { string text = (((Object)(object)item.clip != (Object)null) ? ((Object)item.clip).name : null); SeedAudio(dictionary2, item.slug, item.audio); SeedAudio(dictionary2, text, item.audio); if (item.looping) { SeedAudio(dictionary2, item.slug + "_loop", item.audio); SeedAudio(dictionary2, text + "_loop", item.audio); } } if (!TrySetProperty(obj2, "isBoomboxAudio", false)) { Log.LogWarning((object)("[AUDIO-DIAG] '" + item.slug + "': could not set isBoomboxAudio=false")); } } catch (Exception ex) { Log.LogWarning((object)("[AUDIO-DIAG] '" + item.slug + "': audio wiring threw — " + ex.Message)); } } LogEmoteAudioState(obj2, item.slug, "post-wire"); if (obj == null && item.slug == "bundy_lost_it") { obj = obj2; } } if (obj == null && dictionary.Contains("bundy_lost_it")) { LogEmoteAudioState(dictionary["bundy_lost_it"], "bundy_lost_it (preexisting)"); } Log.LogInfo((object)$"injected {num2} emotes into TooManyEmotes EmotesManager (visible in LGUGui)"); Log.LogInfo((object)$"[AUDIO-DIAG] audioClipsDictDmcaFree count after injection: {dictionary2?.Count ?? (-1)} (was {num}, delta={(dictionary2?.Count ?? 0) - ((num >= 0) ? num : 0)})"); Log.LogInfo((object)string.Format("[AUDIO-DIAG] seeded {0} keys: {1}", _seededAudioKeys.Count, string.Join(", ", _seededAudioKeys.OrderBy((string s) => s)))); LogAudioClipImportSettings(dictionary2); HookTmeAudioPath(type3); HookTmeRoutingPath(assembly); } catch (Exception arg) { Log.LogError((object)$"TooManyEmotes integration threw — {arg}"); } } private void DumpTmeAssembly(Assembly tmeAsm) { StringBuilder sb = new StringBuilder(); Out("[TME-DUMP] assembly: " + tmeAsm.FullName); Out("[TME-DUMP] location: " + tmeAsm.Location); Type[] array; try { array = tmeAsm.GetTypes(); } catch (ReflectionTypeLoadException ex) { Out($"[TME-DUMP] ReflectionTypeLoadException — using LoaderExceptions-filtered types ({ex.Types.Length} entries, {ex.LoaderExceptions.Length} load errors)"); array = ex.Types.Where((Type t) => t != null).ToArray(); } Out($"[TME-DUMP] total types: {array.Length}"); Out(""); Out("[TME-DUMP] === Step 1: types with 'audio' in name ==="); List list = array.Where((Type t) => t.Name.IndexOf("audio", StringComparison.OrdinalIgnoreCase) >= 0).ToList(); if (list.Count == 0) { Out("[TME-DUMP] (none)"); } foreach (Type item in list) { Out($"[TME-DUMP] type: {item.FullName} (sealed={item.IsSealed} static={item.IsAbstract && item.IsSealed})"); foreach (FieldInfo item2 in from f in item.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where f.IsStatic select f) { string text = item2.FieldType.FullName ?? item2.FieldType.Name; string text2 = ((item2.FieldType.IsGenericType && item2.FieldType.GetGenericTypeDefinition() == typeof(Dictionary<, >)) ? " [DICT]" : ""); string text3 = (item2.IsPublic ? "public" : (item2.IsPrivate ? "private" : (item2.IsAssembly ? "internal" : "protected"))); Out("[TME-DUMP] static field: " + text3 + " " + text + " " + item2.Name + text2); } foreach (PropertyInfo item3 in item.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Where(delegate(PropertyInfo p) { MethodInfo? getMethod = p.GetGetMethod(nonPublic: true); return ((object)getMethod != null && getMethod.IsStatic) || (p.GetSetMethod(nonPublic: true)?.IsStatic ?? false); })) { Out("[TME-DUMP] static property: " + (item3.PropertyType.FullName ?? item3.PropertyType.Name) + " " + item3.Name); } } Out(""); Out("[TME-DUMP] === Step 2: methods with 'play'/'audio'/'sound' in name ==="); int num = 0; Type[] array2 = array; foreach (Type type in array2) { MethodInfo[] methods; try { methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } catch { continue; } MethodInfo[] array3 = methods; foreach (MethodInfo methodInfo in array3) { if (methodInfo.DeclaringType != type) { continue; } string name = methodInfo.Name; if (name.IndexOf("play", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("audio", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("sound", StringComparison.OrdinalIgnoreCase) >= 0) { string text4 = string.Join(", ", from p in methodInfo.GetParameters() select (p.ParameterType.FullName ?? p.ParameterType.Name) + " " + p.Name); string text5 = methodInfo.ReturnType.FullName ?? methodInfo.ReturnType.Name; string text6 = (methodInfo.IsStatic ? "static " : "") + (methodInfo.IsPublic ? "public " : (methodInfo.IsPrivate ? "private " : "internal ")); Out("[TME-DUMP] " + text6 + text5 + " " + type.FullName + "." + name + "(" + text4 + ")"); num++; } } } Out($"[TME-DUMP] === total method matches: {num} ==="); Out(""); Out("[TME-DUMP] === Step 3: every static Dictionary<,> field in TME ==="); int num2 = 0; array2 = array; foreach (Type type2 in array2) { FieldInfo[] fields; try { fields = type2.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } catch { continue; } FieldInfo[] array4 = fields; foreach (FieldInfo fieldInfo in array4) { if (fieldInfo.IsStatic && fieldInfo.FieldType.IsGenericType && !(fieldInfo.FieldType.GetGenericTypeDefinition() != typeof(Dictionary<, >))) { Type[] genericArguments = fieldInfo.FieldType.GetGenericArguments(); string text7 = genericArguments[0].FullName ?? genericArguments[0].Name; string text8 = genericArguments[1].FullName ?? genericArguments[1].Name; string text9 = (fieldInfo.IsPublic ? "public" : (fieldInfo.IsPrivate ? "private" : "internal")); int? num3 = null; try { num3 = (fieldInfo.GetValue(null) as IDictionary)?.Count; } catch { } Out($"[TME-DUMP] {text9} static Dictionary<{text7}, {text8}> {type2.FullName}.{fieldInfo.Name} count={num3}"); num2++; } } } Out($"[TME-DUMP] === total static dicts: {num2} ==="); try { string text10 = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "tme_dump.txt"); File.WriteAllText(text10, sb.ToString()); Log.LogInfo((object)("[TME-DUMP] written to " + text10)); } catch (Exception ex2) { Log.LogWarning((object)("[TME-DUMP] failed to write tme_dump.txt: " + ex2.Message)); } void Out(string s) { Log.LogInfo((object)s); sb.AppendLine(s); } } private void HookTmeAudioPath(Type tAudioManager) { //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Expected O, but got Unknown if (tAudioManager == null) { Log.LogWarning((object)"[AUDIO-DIAG] AudioManager type null — cannot hook"); return; } Log.LogInfo((object)"[AUDIO-DIAG] enumerating TooManyEmotes.AudioManager methods:"); MethodInfo[] methods = tAudioManager.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo[] array = methods; foreach (MethodInfo methodInfo in array) { if (!(methodInfo.DeclaringType != tAudioManager)) { string text = string.Join(", ", from p in methodInfo.GetParameters() select p.ParameterType.Name + " " + p.Name); Log.LogInfo((object)("[AUDIO-DIAG] " + (methodInfo.IsStatic ? "static " : "") + methodInfo.ReturnType.Name + " " + methodInfo.Name + "(" + text + ")")); } } _harmony = new Harmony("com.y4ngz.lguemotes.audiohook"); HarmonyMethod val = new HarmonyMethod(typeof(Plugin).GetMethod("TmeAudioPostfix", BindingFlags.Static | BindingFlags.NonPublic)); int num = 0; array = methods; foreach (MethodInfo methodInfo2 in array) { if (methodInfo2.DeclaringType != tAudioManager) { continue; } string name = methodInfo2.Name; switch (name) { default: if (!(name == "AddAudioClip")) { continue; } break; case "LoadAudioClip": case "GetAudioClip": case "AudioExists": case "TryGetAudioClip": case "PlayAudio": case "PlayEmoteAudio": case "BuildAudioClipList": break; } if (methodInfo2.IsAbstract || methodInfo2.ContainsGenericParameters) { continue; } try { _harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)("[AUDIO-DIAG] patched postfix on AudioManager." + name + "(" + string.Join(",", from p in methodInfo2.GetParameters() select p.ParameterType.Name) + ")")); num++; } catch (Exception ex) { Log.LogWarning((object)("[AUDIO-DIAG] failed to patch AudioManager." + name + ": " + ex.Message)); } } Log.LogInfo((object)$"[AUDIO-DIAG] patched {num} TME AudioManager methods"); } private static void TmeAudioPostfix(MethodBase __originalMethod, object[] __args, object __result) { try { string text = ((__args == null) ? "" : string.Join(", ", __args.Select(delegate(object a) { if (a != null) { Object val2 = (Object)((a is Object) ? a : null); if (val2 == null) { return a.ToString(); } return ((object)val2).GetType().Name + "('" + val2.name + "')"; } return "null"; }))); object obj; if (__result != null) { Object val = (Object)((__result is Object) ? __result : null); obj = ((val != null) ? (((object)val).GetType().Name + "('" + val.name + "')") : __result.ToString()); } else { obj = "null"; } string text2 = (string)obj; Log.LogInfo((object)("[AUDIO-DIAG-RT] AudioManager." + __originalMethod.Name + "(" + text + ") -> " + text2)); } catch (Exception ex) { Log.LogWarning((object)("[AUDIO-DIAG-RT] postfix log threw: " + ex.Message)); } } private void HookTmeRoutingPath(Assembly tmeAsm) { //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Expected O, but got Unknown //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Expected O, but got Unknown try { _tEmoteAudioPlayer = tmeAsm.GetType("TooManyEmotes.EmoteAudioPlayer") ?? tmeAsm.GetType("TooManyEmotes.Audio.EmoteAudioPlayer"); _tEmoteAudioSource = tmeAsm.GetType("TooManyEmotes.EmoteAudioSource") ?? tmeAsm.GetType("TooManyEmotes.Audio.EmoteAudioSource"); Type type = tmeAsm.GetType("TooManyEmotes.EmoteAudioPlayerManager") ?? tmeAsm.GetType("TooManyEmotes.Audio.EmoteAudioPlayerManager"); Log.LogInfo((object)("[ROUTING-DIAG] EmoteAudioPlayer=" + (_tEmoteAudioPlayer?.FullName ?? "null") + " EmoteAudioSource=" + (_tEmoteAudioSource?.FullName ?? "null") + " EmoteAudioPlayerManager=" + (type?.FullName ?? "null"))); if (type != null) { _fShipSpeaker = type.GetField("shipSpeakerAudioPlayer", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); _pShipSpeaker = type.GetProperty("shipSpeakerAudioPlayer", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); Log.LogInfo((object)("[ROUTING-DIAG] shipSpeakerAudioPlayer field=" + ((_fShipSpeaker != null) ? "found" : "no") + " property=" + ((_pShipSpeaker != null) ? "found" : "no"))); } if (_harmony == null) { _harmony = new Harmony("com.y4ngz.lguemotes.routinghook"); } HarmonyMethod postfix = new HarmonyMethod(typeof(Plugin).GetMethod("TmeRoutingPostfix", BindingFlags.Static | BindingFlags.NonPublic)); int num = 0; num += PatchSetAudioFromEmote(_tEmoteAudioPlayer, postfix); num += PatchSetAudioFromEmote(_tEmoteAudioSource, postfix); Log.LogInfo((object)$"[ROUTING-DIAG] patched {num} SetAudioFromEmote method(s)"); } catch (Exception ex) { Log.LogWarning((object)("[ROUTING-DIAG] HookTmeRoutingPath threw: " + ex.Message)); } } private static int PatchSetAudioFromEmote(Type t, HarmonyMethod postfix) { if (t == null) { return 0; } int num = 0; MethodInfo[] methods = t.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.DeclaringType != t || methodInfo.Name != "SetAudioFromEmote" || methodInfo.IsAbstract || methodInfo.ContainsGenericParameters) { continue; } try { _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, postfix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)("[ROUTING-DIAG] patched " + t.Name + ".SetAudioFromEmote(" + string.Join(",", from p in methodInfo.GetParameters() select p.ParameterType.Name) + ")")); num++; } catch (Exception ex) { Log.LogWarning((object)("[ROUTING-DIAG] failed to patch " + t.Name + ".SetAudioFromEmote: " + ex.Message)); } } return num; } private static void TmeRoutingPostfix(object __instance, object[] __args) { //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) try { if (__instance == null) { Log.LogInfo((object)"[ROUTING-DIAG-RT] SetAudioFromEmote __instance=null"); return; } Type type = __instance.GetType(); string text = ""; bool flag = false; bool flag2 = false; if (__args != null) { foreach (object obj in __args) { if (obj == null) { continue; } Type type2 = obj.GetType(); FieldInfo field = type2.GetField("emoteName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { text = field.GetValue(obj)?.ToString() ?? "null"; MemberInfo memberInfo = (MemberInfo)(((object)type2.GetField("isBoomboxAudio", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) ?? ((object)type2.GetProperty("isBoomboxAudio", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))); if (memberInfo is FieldInfo fieldInfo) { flag = (bool)fieldInfo.GetValue(obj); flag2 = true; } else if (memberInfo is PropertyInfo propertyInfo && propertyInfo.CanRead) { flag = (bool)propertyInfo.GetValue(obj); flag2 = true; } break; } } } object obj2 = null; if (_fShipSpeaker != null) { obj2 = _fShipSpeaker.GetValue(null); } else if (_pShipSpeaker != null) { obj2 = _pShipSpeaker.GetValue(null); } bool flag3 = obj2 != null && __instance == obj2; AudioSource val = null; Component val2 = (Component)((__instance is Component) ? __instance : null); if (val2 != null) { FieldInfo field2 = type.GetField("audioSource", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field2 != null) { object? value = field2.GetValue(__instance); val = (AudioSource)((value is AudioSource) ? value : null); } if ((Object)(object)val == (Object)null) { PropertyInfo property = type.GetProperty("audioSource", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead) { object? value2 = property.GetValue(__instance); val = (AudioSource)((value2 is AudioSource) ? value2 : null); } } if ((Object)(object)val == (Object)null) { val = val2.GetComponent(); } } string text2 = ""; string text3 = ""; Component val3 = (Component)((__instance is Component) ? __instance : null); if (val3 != null && (Object)(object)val3 != (Object)null) { Transform val4 = (((Object)(object)val3.transform != (Object)null) ? val3.transform.parent : null); text3 = (((Object)(object)val4 != (Object)null) ? ("parent='" + ((Object)val4).name + "'") : "parent=null"); Vector3 val5 = (((Object)(object)val3.transform != (Object)null) ? val3.transform.position : Vector3.zero); text3 += $" pos=({val5.x:F2},{val5.y:F2},{val5.z:F2})"; } if ((Object)(object)val != (Object)null) { text2 = string.Format("spatialBlend={0:F2} clip={1} loop={2} volume={3:F2} outputMixerGroup={4}", val.spatialBlend, ((Object)(object)val.clip != (Object)null) ? ("'" + ((Object)val.clip).name + "'") : "null", val.loop, val.volume, ((Object)(object)val.outputAudioMixerGroup != (Object)null) ? ((Object)val.outputAudioMixerGroup).name : "null"); } Log.LogInfo((object)string.Format("[ROUTING-DIAG-RT] {0}.SetAudioFromEmote(emoteName='{1}') instance={2} isShipSpeaker={3} {4} AudioSource[{5}] isBoomboxAudio={6}", type.Name, text, type.Name, flag3, text3, text2, flag2 ? flag.ToString() : "")); } catch (Exception ex) { Log.LogWarning((object)("[ROUTING-DIAG-RT] postfix threw: " + ex.Message)); } } private static bool TrySetProperty(object obj, string name, object value) { PropertyInfo property = obj.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property == null || !property.CanWrite) { return false; } property.SetValue(obj, value); return true; } private void SeedAudio(IDictionary dict, string key, AudioClip clip) { if (dict != null && !string.IsNullOrEmpty(key) && !((Object)(object)clip == (Object)null)) { if (dict.Contains(key)) { _seededAudioKeys.Add(key + " (preexisting)"); return; } dict.Add(key, clip); _seededAudioKeys.Add(key); } } private static bool TrySetMember(object obj, string name, object value) { Type type = obj.GetType(); PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanWrite) { property.SetValue(obj, value); return true; } string[] array = new string[4] { name, "<" + name + ">k__BackingField", "_" + name, "m_" + name }; foreach (string name2 in array) { FieldInfo field = type.GetField(name2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { field.SetValue(obj, value); return true; } } return false; } private void DumpUnlockableEmote(Type t) { if (_ueDumpDone || t == null) { return; } _ueDumpDone = true; StringBuilder sb = new StringBuilder(); Out($"[UE-DUMP] type: {t.FullName} (sealed={t.IsSealed})"); Out("[UE-DUMP] === fields ==="); FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (!(fieldInfo.DeclaringType != t)) { string text = (fieldInfo.IsPublic ? "public" : (fieldInfo.IsPrivate ? "private" : (fieldInfo.IsAssembly ? "internal" : "protected"))); string text2 = (fieldInfo.IsStatic ? "static " : "") + (fieldInfo.IsInitOnly ? "readonly " : ""); string text3 = fieldInfo.FieldType.FullName ?? fieldInfo.FieldType.Name; Out("[UE-DUMP] " + text + " " + text2 + text3 + " " + fieldInfo.Name); } } Out("[UE-DUMP] === constructors ==="); ConstructorInfo[] constructors = t.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (ConstructorInfo constructorInfo in constructors) { if (!(constructorInfo.DeclaringType != t)) { string text4 = (constructorInfo.IsPublic ? "public" : (constructorInfo.IsPrivate ? "private" : (constructorInfo.IsAssembly ? "internal" : "protected"))); string text5 = string.Join(", ", from p in constructorInfo.GetParameters() select (p.ParameterType.FullName ?? p.ParameterType.Name) + " " + p.Name); Out("[UE-DUMP] " + text4 + " .ctor(" + text5 + ")"); } } Out("[UE-DUMP] === properties ==="); PropertyInfo[] properties = t.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (!(propertyInfo.DeclaringType != t)) { string text6 = propertyInfo.PropertyType.FullName ?? propertyInfo.PropertyType.Name; Out($"[UE-DUMP] {text6} {propertyInfo.Name} CanRead={propertyInfo.CanRead} CanWrite={propertyInfo.CanWrite}"); } } Out("[UE-DUMP] === methods ==="); MethodInfo[] methods = t.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.DeclaringType != t)) { string text7 = (methodInfo.IsPublic ? "public" : (methodInfo.IsPrivate ? "private" : (methodInfo.IsAssembly ? "internal" : "protected"))); string text8 = (methodInfo.IsStatic ? "static " : ""); string text9 = string.Join(", ", from p in methodInfo.GetParameters() select (p.ParameterType.FullName ?? p.ParameterType.Name) + " " + p.Name); string text10 = methodInfo.ReturnType.FullName ?? methodInfo.ReturnType.Name; Out("[UE-DUMP] " + text7 + " " + text8 + text10 + " " + methodInfo.Name + "(" + text9 + ")"); } } try { string text11 = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "unlockable_emote_dump.txt"); File.WriteAllText(text11, sb.ToString()); Log.LogInfo((object)("[UE-DUMP] written to " + text11)); } catch (Exception ex) { Log.LogWarning((object)("[UE-DUMP] failed to write file: " + ex.Message)); } void Out(string s) { Log.LogInfo((object)s); sb.AppendLine(s); } } private static void LogEmoteAudioState(object ue, string slug) { LogEmoteAudioState(ue, slug, ""); } private static void LogEmoteAudioState(object ue, string slug, string phase) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) try { Type t = ue.GetType(); string text = Read("overrideAudioClipName"); string text2 = Read("overrideAudioLoopClipName"); string text3 = Read("audioClipName"); string text4 = Read("audioLoopClipName"); string text5 = Read("hasAudio"); string text6 = Read("isBoomboxAudio"); string text7 = Read("loopable"); string text8 = ""; FieldInfo field = t.GetField("animationClip", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { object? value = field.GetValue(ue); AnimationClip val = (AnimationClip)((value is AnimationClip) ? value : null); text8 = ((!((Object)(object)val != (Object)null)) ? "null" : $"name='{((Object)val).name}' isLooping={((Motion)val).isLooping} wrapMode={val.wrapMode}"); } string text9 = ""; MethodInfo method = t.GetMethod("LoadAudioClip", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method != null) { try { object obj = method.Invoke(ue, null); if (obj == null) { text9 = "null"; } else { Object val2 = (Object)((obj is Object) ? obj : null); text9 = ((val2 == null) ? obj.ToString() : (((object)val2).GetType().Name + "('" + val2.name + "')")); } } catch (Exception ex) { text9 = ""; } } string text10 = (string.IsNullOrEmpty(phase) ? "" : ("[" + phase + "] ")); Log.LogInfo((object)("[AUDIO-DIAG] " + text10 + "'" + slug + "' overrideAudioClipName='" + text + "' overrideAudioLoopClipName='" + text2 + "' audioClipName='" + text3 + "' audioLoopClipName='" + text4 + "' hasAudio=" + text5 + " isBoomboxAudio=" + text6 + " loopable=" + text7 + " animClip[" + text8 + "] LoadAudioClip()=" + text9)); string Read(string name) { FieldInfo field2 = t.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field2 != null) { return field2.GetValue(ue)?.ToString() ?? "null"; } PropertyInfo property = t.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.CanRead) { try { return property.GetValue(ue)?.ToString() ?? "null"; } catch (Exception ex3) { return ""; } } return ""; } } catch (Exception ex2) { Log.LogWarning((object)("[AUDIO-DIAG] LogEmoteAudioState('" + slug + "') threw: " + ex2.Message)); } } private static void SetField(object obj, string name, object value) { FieldInfo? field = obj.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { throw new MissingFieldException(obj.GetType().FullName, name); } field.SetValue(obj, value); } private void LogAudioClipImportSettings(IDictionary tmeAudioDict) { try { Log.LogInfo((object)"[SPATIAL-DIAG] === per-clip AudioClip settings (ours, then TME baseline) ==="); foreach (Registered item in _registered) { LogOneClip("ours", item.slug, item.audio); } if (tmeAudioDict != null) { string[] obj = new string[4] { "controller_crew_start", "controller_crew_loop", "twerk_start", "twerk_loop" }; bool flag = false; string[] array = obj; foreach (string text in array) { if (tmeAudioDict.Contains(text)) { object? obj2 = tmeAudioDict[text]; AudioClip val = (AudioClip)((obj2 is AudioClip) ? obj2 : null); if (val != null) { LogOneClip("tme", text, val); flag = true; } } } if (!flag) { foreach (object key in tmeAudioDict.Keys) { if (key is string text2 && !_seededAudioKeys.Contains(text2)) { object? obj3 = tmeAudioDict[text2]; AudioClip val2 = (AudioClip)((obj3 is AudioClip) ? obj3 : null); if (val2 != null) { LogOneClip("tme-fallback", text2, val2); break; } } } } } Log.LogInfo((object)"[SPATIAL-DIAG] === end ==="); } catch (Exception ex) { Log.LogWarning((object)("[SPATIAL-DIAG] LogAudioClipImportSettings threw: " + ex.Message)); } } private static void LogOneClip(string source, string label, AudioClip c) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)c == (Object)null) { Log.LogInfo((object)("[SPATIAL-DIAG] " + source + " '" + label + "' clip=null")); return; } string text = ""; try { PropertyInfo property = typeof(AudioClip).GetProperty("loadInBackground", BindingFlags.Instance | BindingFlags.Public); if (property != null) { text += $" loadInBackground={property.GetValue(c)}"; } } catch { } Log.LogInfo((object)$"[SPATIAL-DIAG] {source} '{label}' name='{((Object)c).name}' loadType={c.loadType} preloadAudioData={c.preloadAudioData} ambisonic={c.ambisonic} channels={c.channels} frequency={c.frequency}Hz length={c.length:F2}s samples={c.samples} loadState={c.loadState}{text}"); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "LGUEmotes"; public const string PLUGIN_NAME = "LGUEmotes"; public const string PLUGIN_VERSION = "1.0.0"; } }