using System; using System.Diagnostics; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HG.BlendableTypes; using IL.RoR2.CameraModes; using Microsoft.CodeAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using MonoMod.Cil; using RoR2; using RoR2.CameraModes; 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("LegacyBaseFovCompat")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.1.0")] [assembly: AssemblyInformationalVersion("0.2.1+28f179be37f2221235573792ed3e5bea41b748c2")] [assembly: AssemblyProduct("LegacyBaseFovCompat")] [assembly: AssemblyTitle("LegacyBaseFovCompat")] [assembly: AssemblyVersion("0.2.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LegacyBaseFovCompat { public sealed class AddFoVSettingsBridge { public const string Guid = "Quizzik.AddFoVSettings"; private readonly object main; private readonly Func sliderValue; private readonly Func sprintFovEnabled; private readonly Func currentCamera; public float SliderFov => sliderValue(); public bool SprintFovEnabled => sprintFovEnabled(); public object CurrentCamera => currentCamera(main); private AddFoVSettingsBridge(object main, Func sliderValue, Func sprintFovEnabled, Func currentCamera) { this.main = main; this.sliderValue = sliderValue; this.sprintFovEnabled = sprintFovEnabled; this.currentCamera = currentCamera; } public static AddFoVSettingsBridge TryCreate(object main, out string error) { error = null; if (main == null) { error = "plugin instance is null"; return null; } Type type = main.GetType(); FieldInfo field = type.GetField("FoVSlider", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo field2 = type.GetField("sprintFoVEffectsCheckbox", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo field3 = type.GetField("currentCamera", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null || field2 == null || field3 == null || field3.FieldType.IsValueType) { error = "expected fields FoVSlider, sprintFoVEffectsCheckbox, currentCamera not found"; return null; } object value = field.GetValue(main); object value2 = field2.GetValue(main); if (value == null || value2 == null) { error = "settings objects are not created yet"; return null; } MethodInfo method = value.GetType().GetMethod("GetValue", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); MethodInfo method2 = value2.GetType().GetMethod("GetValue", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method?.ReturnType != typeof(float) || method2?.ReturnType != typeof(bool)) { error = "expected MenuSlider.GetValue(): float and MenuCheckbox.GetValue(): bool not found"; return null; } return new AddFoVSettingsBridge(main, (Func)Delegate.CreateDelegate(typeof(Func), value, method), (Func)Delegate.CreateDelegate(typeof(Func), value2, method2), CreateFieldGetter(field3)); } private static Func CreateFieldGetter(FieldInfo field) { DynamicMethod dynamicMethod = new DynamicMethod("LBFC_get_" + field.Name, typeof(object), new Type[1] { typeof(object) }, typeof(AddFoVSettingsBridge).Module, skipVisibility: true); ILGenerator iLGenerator = dynamicMethod.GetILGenerator(); iLGenerator.Emit(OpCodes.Ldarg_0); iLGenerator.Emit(OpCodes.Castclass, field.DeclaringType); iLGenerator.Emit(OpCodes.Ldfld, field); iLGenerator.Emit(OpCodes.Ret); return (Func)dynamicMethod.CreateDelegate(typeof(Func)); } } public static class FovPatch { private const float MinFov = 1f; private const float MaxFov = 170f; private const float TraceInterval = 5f; public static Action LogInfo = delegate { }; public static Action LogError = delegate { }; public static Func TraceEnabled = () => false; public static AddFoVSettingsBridge AddFoVSettings; private static float nextTraceTime; private static bool lastWasScaled; public static bool HookApplied { get; private set; } public static void PatchUpdateInternal(ILContext il) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) HookApplied = false; ILCursor val = new ILCursor(il); int num = default(int); FieldReference val2 = default(FieldReference); if (!val.TryGotoNext((MoveType)2, new Func[7] { (Instruction x) => ILPatternMatchingExt.MatchCall(x, typeof(CharacterCameraParamsData), "Blend"), (Instruction x) => ILPatternMatchingExt.MatchLdloc(x, ref num), (Instruction x) => ILPatternMatchingExt.MatchLdfld(x, "fov"), (Instruction x) => ILPatternMatchingExt.MatchLdfld(x, "value"), (Instruction x) => ILPatternMatchingExt.MatchLdloc(x, ref num), (Instruction x) => ILPatternMatchingExt.MatchLdfld(x, ref val2) && ((MemberReference)val2).Name == "sprintFovMultiplier" && ((MemberReference)((MemberReference)val2).DeclaringType).FullName == "RoR2.CameraModes.CameraModePlayerBasic/InstanceData", (Instruction x) => ILPatternMatchingExt.MatchMul(x) })) { LogError("UpdateInternal pattern not found; the game's camera code changed again."); return; } val.Index -= 1; Instruction next = val.Next; val.MoveAfterLabels(); val.Emit(OpCodes.Ldarg_2); next.OpCode = OpCodes.Call; next.Operand = il.Import((MethodBase)typeof(FovPatch).GetMethod("ComputeFov", BindingFlags.Static | BindingFlags.Public)); HookApplied = true; } public static float ComputeFov(float parameterFov, float sprintMultiplier, in CameraModeContext context) { CameraRigController cameraRigController = context.cameraInfo.cameraRigController; if (!Object.op_Implicit((Object)(object)cameraRigController) || cameraRigController.localUserViewer == null) { return parameterFov * sprintMultiplier; } float baseFov = context.cameraInfo.baseFov; float value = CharacterCameraParamsData.basic.fov.value; AddFoVSettingsBridge addFoVSettings = AddFoVSettings; bool flag = addFoVSettings != null && addFoVSettings.CurrentCamera == cameraRigController; float num = (flag ? addFoVSettings.SliderFov : 0f); bool flag2 = !flag || addFoVSettings.SprintFovEnabled; bool scaled; float result = Calculate(parameterFov, sprintMultiplier, baseFov, value, flag, num, flag2, out scaled); if (TraceEnabled()) { Trace(cameraRigController, scaled, flag, flag2, baseFov, num, parameterFov, sprintMultiplier, result); } if (SprintTransitionTrace.Enabled) { SprintTransitionTrace.Record(Time.frameCount, context.targetInfo.isSprinting, flag2, sprintMultiplier, baseFov, num, parameterFov, result, Object.op_Implicit((Object)(object)cameraRigController.sceneCam) ? cameraRigController.sceneCam.fieldOfView : (-1f)); } return result; } public static float Calculate(float parameterFov, float sprintMultiplier, float baseFov, float defaultFov, bool useModSettings, float sliderFov, bool sprintFovEnabled, out bool scaled) { if (useModSettings && sliderFov > 0f && defaultFov > 0f) { float num = Scale(parameterFov, sliderFov, defaultFov, out scaled); if (sprintFovEnabled) { return num * sprintMultiplier; } scaled = true; return num; } return Scale(parameterFov, baseFov, defaultFov, out scaled) * sprintMultiplier; } public static float Scale(float parameterFov, float baseFov, float defaultFov, out bool scaled) { scaled = baseFov > 0f && defaultFov > 0f && Math.Abs(baseFov - defaultFov) > 0.001f; if (!scaled) { return parameterFov; } return Clamp(parameterFov * (baseFov / defaultFov)); } private static float Clamp(float fov) { if (!(fov < 1f)) { if (!(fov > 170f)) { return fov; } return 170f; } return 1f; } private static void Trace(CameraRigController rig, bool scaled, bool modCamera, bool sprintFov, float baseFov, float sliderFov, float parameterFov, float sprintMultiplier, float result) { float unscaledTime = Time.unscaledTime; if (scaled != lastWasScaled || !(unscaledTime < nextTraceTime)) { lastWasScaled = scaled; nextTraceTime = unscaledTime + 5f; LogInfo($"[LBFC] frame={Time.frameCount} rig={((Object)rig).name} scaled={scaled} modCamera={modCamera} sprintFov={sprintFov} " + $"baseFov={baseFov:0.###} slider={sliderFov:0.###} paramFov={parameterFov:0.###} multiplier={sprintMultiplier:0.###} -> {result:0.###}"); } } } [BepInPlugin("Nhiblam.LegacyBaseFovCompat", "LegacyBaseFovCompat", "0.2.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class LegacyBaseFovCompatPlugin : BaseUnityPlugin { [CompilerGenerated] private static class <>O { public static Manipulator <0>__PatchUpdateInternal; } public const string Guid = "Nhiblam.LegacyBaseFovCompat"; public const string PluginName = "LegacyBaseFovCompat"; public const string PluginVersion = "0.2.1"; private bool hookInstalled; private void Awake() { //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Expected O, but got Unknown ConfigEntry trace = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "TraceAppliedFov", false, "Troubleshooting: log the adjusted player camera FOV at most once every 5 seconds, and whenever scaling starts or stops."); FovPatch.LogInfo = ((BaseUnityPlugin)this).Logger.LogInfo; FovPatch.LogError = ((BaseUnityPlugin)this).Logger.LogError; FovPatch.TraceEnabled = () => trace.Value; SprintTransitionTrace.Log = ((BaseUnityPlugin)this).Logger.LogInfo; SprintTransitionTrace.Enabled = ((BaseUnityPlugin)this).Config.Bind("Diagnostics", "TraceSprintTransitions", false, $"Troubleshooting: log the frame before and {12} frames after a sprint start or stop, " + $"for the first {10} transitions of each session.").Value; ((BaseUnityPlugin)this).Logger.LogInfo((object)("LegacyBaseFovCompat 0.2.1 build=" + GetBuildId() + " sha256=" + GetOwnHash())); if (BindAddFoVSettings()) { object obj = <>O.<0>__PatchUpdateInternal; if (obj == null) { Manipulator val = FovPatch.PatchUpdateInternal; <>O.<0>__PatchUpdateInternal = val; obj = (object)val; } CameraModePlayerBasic.UpdateInternal += (Manipulator)obj; hookInstalled = true; if (FovPatch.HookApplied) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"CameraModePlayerBasic.UpdateInternal IL hook applied."); } else { ((BaseUnityPlugin)this).Logger.LogError((object)"CameraModePlayerBasic.UpdateInternal IL hook NOT applied; AddFoVSettings' FOV will not reach the camera."); } } } private void OnDestroy() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown if (hookInstalled) { object obj = <>O.<0>__PatchUpdateInternal; if (obj == null) { Manipulator val = FovPatch.PatchUpdateInternal; <>O.<0>__PatchUpdateInternal = val; obj = (object)val; } CameraModePlayerBasic.UpdateInternal -= (Manipulator)obj; } } private bool BindAddFoVSettings() { if (!Chainloader.PluginInfos.TryGetValue("Quizzik.AddFoVSettings", out var value) || !Object.op_Implicit((Object)(object)value.Instance)) { ((BaseUnityPlugin)this).Logger.LogError((object)"AddFoVSettings is not loaded; LegacyBaseFovCompat is inactive."); return false; } FovPatch.AddFoVSettings = AddFoVSettingsBridge.TryCreate(value.Instance, out var error); if (FovPatch.AddFoVSettings == null) { ((BaseUnityPlugin)this).Logger.LogError((object)$"AddFoVSettings {value.Metadata.Version} found but its settings could not be read ({error}); LegacyBaseFovCompat is inactive."); return false; } ((BaseUnityPlugin)this).Logger.LogInfo((object)$"AddFoVSettings {value.Metadata.Version} bound: slider={FovPatch.AddFoVSettings.SliderFov} sprintFov={FovPatch.AddFoVSettings.SprintFovEnabled}."); return true; } private static string GetBuildId() { return typeof(LegacyBaseFovCompatPlugin).Assembly.GetCustomAttribute()?.InformationalVersion ?? "unknown"; } private string GetOwnHash() { try { using SHA256 sHA = SHA256.Create(); using FileStream inputStream = File.OpenRead(((BaseUnityPlugin)this).Info.Location); return BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", ""); } catch (Exception ex) { return "unavailable (" + ex.GetType().Name + ")"; } } } public static class SprintTransitionTrace { private struct Sample { public int Frame; public bool Sprinting; public bool SprintFov; public float Multiplier; public float BaseFov; public float Slider; public float ParamFov; public float Result; public float UnityFovPrevFrame; } public const int FramesAfter = 12; public const int MaxTransitions = 10; public static Action Log = delegate { }; public static bool Enabled; private static int transitions; private static int remaining; private static int lastFrame = -1; private static bool hasPrevious; private static Sample previous; public static void Record(int frame, bool sprinting, bool sprintFov, float multiplier, float baseFov, float slider, float paramFov, float result, float unityFovPrevFrame) { if (Enabled && frame != lastFrame) { lastFrame = frame; Sample s = new Sample { Frame = frame, Sprinting = sprinting, SprintFov = sprintFov, Multiplier = multiplier, BaseFov = baseFov, Slider = slider, ParamFov = paramFov, Result = result, UnityFovPrevFrame = unityFovPrevFrame }; if (hasPrevious && sprinting != previous.Sprinting && transitions < 10) { transitions++; remaining = 12; Log($"[LBFC-SPRINT] transition {transitions}/{10} sprint {previous.Sprinting} -> {sprinting}"); Write("before", previous); } if (remaining > 0) { remaining--; Write("after", s); } previous = s; hasPrevious = true; } } private static void Write(string label, Sample s) { Log($"[LBFC-SPRINT] {label} frame={s.Frame} sprinting={s.Sprinting} sprintFov={s.SprintFov} multiplier={s.Multiplier:0.####} " + $"baseFov={s.BaseFov:0.###} slider={s.Slider:0.###} paramFov={s.ParamFov:0.###} result={s.Result:0.###} unityFovPrevFrame={s.UnityFovPrevFrame:0.###}"); } } }