Decompiled source of LegacyBaseFovCompat v0.2.1

plugins/LegacyBaseFovCompat.dll

Decompiled 9 hours ago
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<float> sliderValue;

		private readonly Func<bool> sprintFovEnabled;

		private readonly Func<object, object> currentCamera;

		public float SliderFov => sliderValue();

		public bool SprintFovEnabled => sprintFovEnabled();

		public object CurrentCamera => currentCamera(main);

		private AddFoVSettingsBridge(object main, Func<float> sliderValue, Func<bool> sprintFovEnabled, Func<object, object> 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<float>)Delegate.CreateDelegate(typeof(Func<float>), value, method), (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), value2, method2), CreateFieldGetter(field3));
		}

		private static Func<object, object> 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<object, object>)dynamicMethod.CreateDelegate(typeof(Func<object, object>));
		}
	}
	public static class FovPatch
	{
		private const float MinFov = 1f;

		private const float MaxFov = 170f;

		private const float TraceInterval = 5f;

		public static Action<object> LogInfo = delegate
		{
		};

		public static Action<object> LogError = delegate
		{
		};

		public static Func<bool> 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<Instruction, bool>[7]
			{
				(Instruction x) => ILPatternMatchingExt.MatchCall(x, typeof(CharacterCameraParamsData), "Blend"),
				(Instruction x) => ILPatternMatchingExt.MatchLdloc(x, ref num),
				(Instruction x) => ILPatternMatchingExt.MatchLdfld<CharacterCameraParamsData>(x, "fov"),
				(Instruction x) => ILPatternMatchingExt.MatchLdfld<BlendableFloat>(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<bool> trace = ((BaseUnityPlugin)this).Config.Bind<bool>("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<bool>("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<AssemblyInformationalVersionAttribute>()?.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<object> 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.###}");
		}
	}
}