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 LethalPhonesVariableCallTimeout v1.0.0
plugins/LethalPhonesCallTimeout/LethalPhones.CallTimeout.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using UnityEngine; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyDescription("Configurable fixed or receiving-ringtone-based unanswered call timeouts.")] [assembly: CompilationRelaxations(8)] [assembly: AssemblyTitle("LethalPhones Call Timeout")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyVersion("1.0.0.0")] namespace LethalPhonesCallTimeout; internal static class CallTimeoutPatches { internal static Func<TimeoutSettings> SettingsProvider = () => new TimeoutSettings(); internal static Action<string> Warning = delegate { }; private static bool lookupWarningLogged; internal static void Install(Harmony patcher) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Expected O, but got Unknown string[] array = new string[2] { "Scoops.misc.PlayerPhone", "Scoops.gameobjects.SwitchboardPhone" }; foreach (string typeName in array) { MethodInfo methodInfo = IteratorMoveNext(typeName, "CallTimeoutCoroutine", typeof(short)); RequireField(methodInfo.DeclaringType, "<>4__this"); RequireField(methodInfo.DeclaringType, "number"); patcher.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(CallTimeoutPatches), "TimeoutTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); } string[] array2 = new string[2] { "Scoops.misc.PlayerPhone", "Scoops.misc.PhoneBehavior" }; foreach (string typeName2 in array2) { MethodInfo methodInfo2 = IteratorMoveNext(typeName2, "PhoneRingCoroutine", typeof(int)); RequireField(methodInfo2.DeclaringType, "<>4__this"); RequireField(methodInfo2.DeclaringType, "repeats"); patcher.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(CallTimeoutPatches), "NoisePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } internal static float ResolveOutgoingTimeout(object caller, short number) { TimeoutSettings timeoutSettings = SettingsProvider(); float? ringtoneLength = null; if (timeoutSettings.Mode == TimeoutMode.RingtoneLength) { try { ringtoneLength = LethalPhonesRuntime.OutgoingRingtoneLength(number); } catch (Exception error) { WarnAboutFallback(error); } } return TimeoutPolicy.Resolve(timeoutSettings, ringtoneLength); } private static IEnumerable<CodeInstruction> TimeoutTranspiler(IEnumerable<CodeInstruction> instructions, MethodBase __originalMethod) { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Expected O, but got Unknown //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Expected O, but got Unknown //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Expected O, but got Unknown List<CodeInstruction> list = new List<CodeInstruction>(instructions); List<CodeInstruction> list2 = new List<CodeInstruction>(); Type declaringType = __originalMethod.DeclaringType; FieldInfo fieldInfo = RequireField(declaringType, "<>4__this"); FieldInfo fieldInfo2 = RequireField(declaringType, "number"); int num = 0; for (int i = 0; i < list.Count; i++) { ConstructorInfo constructorInfo = ((i + 1 < list.Count) ? (list[i + 1].operand as ConstructorInfo) : null); if (list[i].opcode == OpCodes.Ldc_R4 && (float)list[i].operand == 15f && list[i + 1].opcode == OpCodes.Newobj && constructorInfo != null && constructorInfo.DeclaringType == typeof(WaitForSeconds)) { CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(list[i].labels); val.blocks.AddRange(list[i].blocks); list2.Add(val); list2.Add(new CodeInstruction(OpCodes.Ldfld, (object)fieldInfo)); list2.Add(new CodeInstruction(OpCodes.Ldarg_0, (object)null)); list2.Add(new CodeInstruction(OpCodes.Ldfld, (object)fieldInfo2)); list2.Add(new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(CallTimeoutPatches), "ResolveOutgoingTimeout", (Type[])null, (Type[])null))); num++; } else { list2.Add(list[i]); } } if (num != 1) { throw new InvalidOperationException("Expected exactly one 15-second call timer; found " + num); } return list2; } private static void NoisePrefix(object __instance) { Type type = __instance.GetType(); object value = RequireField(type, "<>4__this").GetValue(__instance); if (value == null) { return; } string fullName = value.GetType().FullName; if (fullName != "Scoops.misc.PlayerPhone" && fullName != "Scoops.gameobjects.SwitchboardPhone") { return; } TimeoutSettings timeoutSettings = SettingsProvider(); float? ringtoneLength = null; if (timeoutSettings.Mode == TimeoutMode.RingtoneLength) { try { ringtoneLength = LethalPhonesRuntime.RingtoneLength(value); } catch (Exception error) { WarnAboutFallback(error); } } float num = TimeoutPolicy.Resolve(timeoutSettings, ringtoneLength); FieldInfo fieldInfo = RequireField(type, "repeats"); fieldInfo.SetValue(__instance, Math.Max((int)fieldInfo.GetValue(__instance), (int)Math.Ceiling(num / 4f))); } private static MethodInfo IteratorMoveNext(string typeName, string method, Type parameter) { Type type = AccessTools.TypeByName(typeName); MethodInfo methodInfo = ((type == null) ? null : AccessTools.DeclaredMethod(type, method, new Type[1] { parameter }, (Type[])null)); if (methodInfo == null || methodInfo.ReturnType != typeof(IEnumerator)) { throw new MissingMethodException(typeName, method); } MethodInfo methodInfo2 = AccessTools.EnumeratorMoveNext((MethodBase)methodInfo); if (methodInfo2 == null) { throw new MissingMethodException(typeName, method + ".MoveNext"); } return methodInfo2; } private static void WarnAboutFallback(Exception error) { if (!lookupWarningLogged) { lookupWarningLogged = true; Warning("Ringtone lookup failed; using FixedSeconds as fallback. Further identical warnings are suppressed. " + error.Message); } } private static FieldInfo RequireField(Type type, string name) { FieldInfo fieldInfo = AccessTools.Field(type, name); if (fieldInfo == null) { throw new MissingFieldException(type.FullName, name); } return fieldInfo; } } internal static class LethalPhonesRuntime { private static FieldInfo phones; private static Type behaviorType; private static FieldInfo number; private static FieldInfo ringtoneId; private static FieldInfo ringtones; private static FieldInfo switchboardRingtone; private static Type switchboardType; private static string defaultRingtone; internal static void Initialize() { Type type = (behaviorType = RequireType("Scoops.misc.PhoneBehavior")); phones = RequireField(RequireType("Scoops.service.PhoneNetworkHandler"), "allPhoneBehaviors"); number = RequireField(type, "phoneNumber"); ringtoneId = RequireField(type, "phoneRingtoneId"); Type type2 = RequireType("Scoops.customization.CustomizationManager"); ringtones = RequireField(type2, "ringtoneCustomizations"); defaultRingtone = (string)RequireField(type2, "DEFAULT_RINGTONE").GetValue(null); switchboardType = RequireType("Scoops.gameobjects.SwitchboardPhone"); switchboardRingtone = RequireField(RequireType("Scoops.service.PhoneAssetManager"), "switchboardRing"); } internal static float? OutgoingRingtoneLength(short targetNumber) { IEnumerable all = phones.GetValue(null) as IEnumerable; float? result = FindLength(all, targetNumber); if (result.HasValue) { return result; } if (typeof(Object).IsAssignableFrom(behaviorType)) { return FindLength(Object.FindObjectsOfType(behaviorType), targetNumber); } return null; } private static float? FindLength(IEnumerable all, short targetNumber) { if (all == null) { return null; } foreach (object item in all) { if (!IsMissing(item) && (short)number.GetValue(item) == targetNumber) { return RingtoneLength(item); } } return null; } internal static float? RingtoneLength(object phone) { if (IsMissing(phone)) { return null; } if (switchboardType.IsInstanceOfType(phone)) { return ClipLength(switchboardRingtone.GetValue(null)); } if (!(ringtones.GetValue(null) is IDictionary dictionary)) { return null; } object value = ((ringtoneId.GetValue(phone) is string key && dictionary.Contains(key)) ? dictionary[key] : null); if (IsMissing(value) && defaultRingtone != null && dictionary.Contains(defaultRingtone)) { value = dictionary[defaultRingtone]; } return ClipLength(value); } private static float? ClipLength(object value) { AudioClip val = (AudioClip)((value is AudioClip) ? value : null); if ((Object)(object)val == (Object)null) { return null; } float length = val.length; if (!TimeoutPolicy.ValidPositive(length)) { return null; } return length; } private static bool IsMissing(object value) { if (value == null) { return true; } Object val = (Object)((value is Object) ? value : null); if (!object.ReferenceEquals(val, null)) { return val == (Object)null; } return false; } private static Type RequireType(string name) { Type type = AccessTools.TypeByName(name); if (type == null) { throw new TypeLoadException(name); } return type; } private static FieldInfo RequireField(Type type, string name) { FieldInfo fieldInfo = AccessTools.Field(type, name); if (fieldInfo == null) { throw new MissingFieldException(type.FullName, name); } return fieldInfo; } } [BepInIncompatibility("private.phones.calltimeout")] [BepInPlugin("lethalphones.calltimeout", "LethalPhones Call Timeout", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string Id = "lethalphones.calltimeout"; private ConfigEntry<TimeoutMode> mode; private ConfigEntry<float> fixedSeconds; private ConfigEntry<float> maximumRingtoneSeconds; private ConfigEntry<float> ringtoneGraceSeconds; private Harmony harmony; private void Awake() { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Expected O, but got Unknown bool value = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable the unanswered-call timer patches.").Value; mode = ((BaseUnityPlugin)this).Config.Bind<TimeoutMode>("Calls", "Mode", TimeoutMode.Fixed, "Fixed: use FixedSeconds. RingtoneLength: use the called phone's ringtone length."); fixedSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Calls", "FixedSeconds", 45f, new ConfigDescription("Fixed timeout and fallback when a ringtone cannot be resolved (seconds).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3600f), new object[0])); maximumRingtoneSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Calls", "MaximumRingtoneSeconds", 300f, new ConfigDescription("Upper limit for ringtone-based timeouts (seconds).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3600f), new object[0])); ringtoneGraceSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Calls", "RingtoneGraceSeconds", 0f, new ConfigDescription("Optional extra seconds after the ringtone length to allow for network delay.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), new object[0])); if (!value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Call timeout patches disabled by configuration."); return; } harmony = new Harmony("lethalphones.calltimeout"); try { CallTimeoutPatches.SettingsProvider = ReadSettings; CallTimeoutPatches.Warning = delegate(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); }; LethalPhonesRuntime.Initialize(); CallTimeoutPatches.Install(harmony); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Concat("Call timeout patches active. Mode=", mode.Value, ", fixed/fallback=", fixedSeconds.Value, "s.")); } catch (Exception ex) { harmony.UnpatchSelf(); ((BaseUnityPlugin)this).Logger.LogError((object)("Call timeout patches disabled because the expected LethalPhones layout was not found: " + ex)); } } private TimeoutSettings ReadSettings() { TimeoutSettings timeoutSettings = new TimeoutSettings(); timeoutSettings.Mode = mode.Value; timeoutSettings.FixedSeconds = fixedSeconds.Value; timeoutSettings.MaximumRingtoneSeconds = maximumRingtoneSeconds.Value; timeoutSettings.RingtoneGraceSeconds = ringtoneGraceSeconds.Value; return timeoutSettings; } private void OnDestroy() { if (harmony != null) { harmony.UnpatchSelf(); } } } internal enum TimeoutMode { Fixed, RingtoneLength } internal sealed class TimeoutSettings { internal TimeoutMode Mode; internal float FixedSeconds = 45f; internal float MaximumRingtoneSeconds = 300f; internal float RingtoneGraceSeconds; } internal static class TimeoutPolicy { internal const float MinimumSeconds = 0.1f; internal const float MaximumSeconds = 3600f; internal static bool ValidPositive(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return value > 0f; } return false; } internal static float Resolve(TimeoutSettings settings, float? ringtoneLength) { if (settings == null) { settings = new TimeoutSettings(); } float result = Sanitize(settings.FixedSeconds, 45f); if (settings.Mode != TimeoutMode.RingtoneLength || !ringtoneLength.HasValue || !ValidPositive(ringtoneLength.Value)) { return result; } float val = Sanitize(settings.MaximumRingtoneSeconds, 300f); float num = settings.RingtoneGraceSeconds; if (float.IsNaN(num) || float.IsInfinity(num) || num < 0f) { num = 0f; } num = Math.Min(num, 10f); return Math.Max(0.1f, Math.Min(val, ringtoneLength.Value + num)); } private static float Sanitize(float value, float fallback) { if (!ValidPositive(value)) { value = fallback; } return Math.Max(0.1f, Math.Min(3600f, value)); } }