using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CameraUnlock.Core.Data; using CameraUnlock.Core.Math; using CameraUnlock.Core.Processing; using CameraUnlock.Core.Protocol; using CameraUnlock.Core.Unity.Extensions; using CameraUnlock.Core.Unity.Rendering; using CameraUnlock.Core.Unity.Tracking; using HarmonyLib; using PeakHeadTracking.Camera; using PeakHeadTracking.Config; using PeakHeadTracking.Input; using PeakHeadTracking.Patches; using UnityEngine; using UnityEngine.SceneManagement; [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("itsloopyo")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Head tracking mod for PEAK using CameraUnlock.Core")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0+8937be8def25cb222c2095da8f21d067b4d44bab")] [assembly: AssemblyProduct("PeakHeadTracking")] [assembly: AssemblyTitle("PeakHeadTracking")] [assembly: AssemblyVersion("1.1.0.0")] namespace PeakHeadTracking { public static class GameTypeNames { private const string AssemblyName = "Assembly-CSharp"; public const string Character = "Character, Assembly-CSharp"; public const string CharacterAnimations = "CharacterAnimations, Assembly-CSharp"; public const string GUIManager = "GUIManager, Assembly-CSharp"; public const string LoadingScreenHandler = "LoadingScreenHandler, Assembly-CSharp"; public const string CameraQuad = "CameraQuad"; } [BepInPlugin("com.cameraunlock.peak.headtracking", "Peak Head Tracking", "1.1.0")] [BepInProcess("Peak.exe")] public class PeakHeadTrackingPlugin : BaseUnityPlugin { public const string PLUGIN_GUID = "com.cameraunlock.peak.headtracking"; public const string PLUGIN_NAME = "Peak Head Tracking"; public const string PLUGIN_VERSION = "1.1.0"; internal static ManualLogSource Logger; private Harmony harmony; private GameObject trackingManagerObject; private ModConfiguration modConfig; private ConfigurationManager configManager; private ProfileManager profileManager; private OpenTrackReceiver coreReceiver; private TrackingProcessor processor; private PoseInterpolator interpolator; private CameraController cameraController; private HotkeyManager hotkeyManager; private PositionProcessor positionProcessor; private PositionInterpolator positionInterpolator; private bool destroyed; private void Awake() { Logger = ((BaseUnityPlugin)this).Logger; Logger.LogInfo((object)"Initializing Peak Head Tracking v1.1.0"); try { InitializeConfiguration(); CreateTrackingManager(); InitializeHarmonyPatches(); InitializeComponents(); Logger.LogInfo((object)"Peak Head Tracking successfully loaded!"); } catch (Exception arg) { Logger.LogError((object)string.Format("Failed to initialize {0}: {1}", "Peak Head Tracking", arg)); throw; } } private void InitializeConfiguration() { Logger.LogDebug((object)"Initializing configuration..."); configManager = ConfigurationManager.Instance; configManager.Initialize(((BaseUnityPlugin)this).Config); modConfig = configManager.Config; profileManager = configManager.Profiles; configManager.ProfileChanged += OnProfileChanged; Logger.LogDebug((object)("Configuration loaded - Profile: " + profileManager.GetActiveProfileName() + ", " + $"UDP Port: {modConfig.UdpPort.Value}, " + $"Tracking Enabled: {modConfig.TrackingEnabled.Value}")); } private void CreateTrackingManager() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown Logger.LogDebug((object)"Creating tracking manager GameObject..."); trackingManagerObject = new GameObject("PeakHeadTrackingManager"); Object.DontDestroyOnLoad((Object)(object)trackingManagerObject); trackingManagerObject.hideFlags = (HideFlags)61; } private void InitializeHarmonyPatches() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown Logger.LogDebug((object)"Initializing Harmony patches..."); harmony = new Harmony("com.cameraunlock.peak.headtracking"); harmony.PatchAll(); IEnumerable patchedMethods = harmony.GetPatchedMethods(); int num = 0; foreach (MethodBase item in patchedMethods) { num++; Logger.LogDebug((object)("Patched: " + item.DeclaringType?.Name + "." + item.Name)); } Logger.LogInfo((object)$"Applied {num} Harmony patches"); } private void InitializeComponents() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Expected O, but got Unknown //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Expected O, but got Unknown //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Expected O, but got Unknown Logger.LogDebug((object)"Initializing components..."); coreReceiver = new OpenTrackReceiver(); coreReceiver.Log = delegate(string msg) { Logger.LogInfo((object)msg); }; processor = new TrackingProcessor { SmoothingFactor = modConfig.Smoothing.Value, Sensitivity = new SensitivitySettings(modConfig.YawSensitivity.Value, modConfig.PitchSensitivity.Value, modConfig.RollSensitivity.Value, false, false, false), Deadzone = (DeadzoneSettings)(modConfig.EnableDeadzone.Value ? new DeadzoneSettings(modConfig.DeadzoneYaw.Value, modConfig.DeadzonePitch.Value, modConfig.DeadzoneRoll.Value) : DeadzoneSettings.None) }; interpolator = new PoseInterpolator(); cameraController = trackingManagerObject.AddComponent(); cameraController.Initialize(modConfig, coreReceiver, processor, interpolator); positionProcessor = new PositionProcessor { Settings = new PositionSettings(modConfig.PositionSensitivityX.Value, modConfig.PositionSensitivityY.Value, modConfig.PositionSensitivityZ.Value, modConfig.PositionLimitX.Value, modConfig.PositionLimitY.Value, 0.1f, modConfig.PositionLimitZ.Value, modConfig.PositionSmoothing.Value, true, false, false) }; positionInterpolator = new PositionInterpolator(); CameraPatches.SetReceiver(coreReceiver); CameraPatches.SetPositionProcessors(positionProcessor, positionInterpolator, modConfig.PositionEnabled); CameraPatches.SetNearClipConfig(modConfig.NearClipOverride); CameraPatches.SetReticleConfig(modConfig.ShowReticle); hotkeyManager = trackingManagerObject.AddComponent(); hotkeyManager.Initialize(modConfig, cameraController, coreReceiver); SetupConfigurationCallbacks(); Logger.LogDebug((object)"All components initialized"); } private void Start() { Logger.LogDebug((object)"Plugin Start() called"); if (modConfig.TrackingEnabled.Value) { coreReceiver.Start(modConfig.UdpPort.Value); cameraController.SetTrackingEnabled(enabled: true); Logger.LogInfo((object)"Head tracking started"); } else { Logger.LogInfo((object)"Head tracking disabled by configuration"); } } private void SetupConfigurationCallbacks() { configManager.RegisterSettingChangeCallback("UdpPort", delegate { Logger.LogInfo((object)$"UDP Port changed to {modConfig.UdpPort.Value}, restarting receiver..."); OpenTrackReceiver obj2 = coreReceiver; if (obj2 != null) { obj2.Dispose(); } OpenTrackReceiver obj3 = coreReceiver; if (obj3 != null) { obj3.Start(modConfig.UdpPort.Value); } }); configManager.RegisterSettingChangeCallback("TrackingEnabled", delegate { if (modConfig.TrackingEnabled.Value) { if (!coreReceiver.IsReceiving && !coreReceiver.IsFailed) { coreReceiver.Start(modConfig.UdpPort.Value); } cameraController?.SetTrackingEnabled(enabled: true); Logger.LogInfo((object)"Tracking enabled via configuration"); } else { OpenTrackReceiver obj = coreReceiver; if (obj != null) { obj.Dispose(); } cameraController?.SetTrackingEnabled(enabled: false); Logger.LogInfo((object)"Tracking disabled via configuration"); } }); configManager.RegisterSettingChangeCallback("UpdateRate", delegate { Logger.LogDebug((object)$"Update rate changed to {modConfig.UpdateRate.Value} Hz"); }); } private void OnProfileChanged(object sender, string profileName) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) Logger.LogInfo((object)("Switched to profile: " + profileName)); if (coreReceiver != null) { coreReceiver.Dispose(); coreReceiver.Start(modConfig.UdpPort.Value); } processor.Sensitivity = new SensitivitySettings(modConfig.YawSensitivity.Value, modConfig.PitchSensitivity.Value, modConfig.RollSensitivity.Value, false, false, false); processor.SmoothingFactor = modConfig.Smoothing.Value; processor.Deadzone = (DeadzoneSettings)(modConfig.EnableDeadzone.Value ? new DeadzoneSettings(modConfig.DeadzoneYaw.Value, modConfig.DeadzonePitch.Value, modConfig.DeadzoneRoll.Value) : DeadzoneSettings.None); cameraController?.Initialize(modConfig, coreReceiver, processor, interpolator); hotkeyManager?.Initialize(modConfig, cameraController, coreReceiver); if (modConfig.TrackingEnabled.Value) { cameraController?.SetTrackingEnabled(enabled: true); } } private void OnDestroy() { if (!destroyed) { destroyed = true; Logger.LogInfo((object)"Shutting down Peak Head Tracking..."); CameraPatches.UnregisterCameraCallback(); Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; if ((Object)(object)trackingManagerObject != (Object)null) { Object.DestroyImmediate((Object)(object)trackingManagerObject); trackingManagerObject = null; } if (coreReceiver != null) { coreReceiver.Dispose(); coreReceiver = null; } CameraPatches.SetReceiver(null); if (configManager != null) { configManager.ProfileChanged -= OnProfileChanged; } configManager?.Cleanup(); configManager = null; modConfig = null; Logger.LogInfo((object)"Cleanup complete"); } } private void OnApplicationQuit() { OnDestroy(); } } public static class ReflectionUtils { public static Func CreateStaticFieldGetter(FieldInfo field) { return Expression.Lambda>(Expression.Convert(Expression.Field(null, field), typeof(TResult)), Array.Empty()).Compile(); } public static Func CreateInstanceFieldGetter(Type instanceType, FieldInfo field) { ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "instance"); return Expression.Lambda>(Expression.Convert(Expression.Field(Expression.Convert(parameterExpression, instanceType), field), typeof(TResult)), new ParameterExpression[1] { parameterExpression }).Compile(); } public static Func CreateStaticPropertyGetter(PropertyInfo property) { return Expression.Lambda>(Expression.Convert(Expression.Property(null, property), typeof(TResult)), Array.Empty()).Compile(); } } } namespace PeakHeadTracking.Patches { public static class CameraPatches { private static OpenTrackReceiver receiver; private static PositionProcessor positionProcessor; private static PositionInterpolator positionInterpolator; private static ConfigEntry positionEnabledConfig; private static ConfigEntry showReticleConfig; private static ConfigEntry nearClipConfig; private static float storedNearClipPlane; private static float currentYaw = 0f; private static float currentPitch = 0f; private static bool headTrackingEnabled = false; private static bool rotationEnabled = true; private static bool hasLoggedFirstApplication = false; private static volatile float processedYaw = 0f; private static volatile float processedPitch = 0f; private static volatile float processedRoll = 0f; private static bool callbackRegistered = false; private static bool matrixModifiedThisFrame = false; private static int lastDiagnosticFrame = -1; private const int DiagnosticLogInterval = 300; public static float CurrentYaw => currentYaw; public static float CurrentPitch => currentPitch; internal static float ProcessedYaw => processedYaw; internal static float ProcessedPitch => processedPitch; internal static float ProcessedRoll => processedRoll; public static void SetReceiver(OpenTrackReceiver coreReceiver) { receiver = coreReceiver; } public static void SetPositionProcessors(PositionProcessor posProccesor, PositionInterpolator posInterp, ConfigEntry posEnabled) { positionProcessor = posProccesor; positionInterpolator = posInterp; positionEnabledConfig = posEnabled; } public static void SetNearClipConfig(ConfigEntry config) { nearClipConfig = config; } public static void SetReticleConfig(ConfigEntry config) { showReticleConfig = config; } public static void RecenterPosition() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (positionProcessor != null && receiver != null) { positionProcessor.SetCenter(receiver.GetLatestPosition()); PositionInterpolator obj = positionInterpolator; if (obj != null) { obj.Reset(); } } } public static void SetHeadTrackingInput(float yaw, float pitch) { currentYaw = yaw; currentPitch = pitch; } public static void SetProcessedRotation(float yaw, float pitch, float roll) { processedYaw = yaw; processedPitch = pitch; processedRoll = roll; currentYaw = yaw; currentPitch = pitch; } public static void SetRotationEnabled(bool enabled) { rotationEnabled = enabled; } public static void SetHeadTrackingEnabled(bool enabled) { headTrackingEnabled = enabled; if (enabled && !callbackRegistered) { RegisterCameraCallback(); } if (!enabled) { ReticleCompensation.ResetReticlePosition(); } } public static void RegisterCameraCallback() { if (callbackRegistered) { ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogWarning((object)"[RegisterCameraCallback] Already registered, skipping"); } return; } ManualLogSource logger2 = PeakHeadTrackingPlugin.Logger; if (logger2 != null) { logger2.LogDebug((object)("Render pipeline: " + (RenderPipelineHelper.IsSRP ? "SRP" : "Legacy"))); } RenderPipelineHelper.RegisterCallbacks((Action)OnPreRender, (Action)OnPostRender); callbackRegistered = true; ManualLogSource logger3 = PeakHeadTrackingPlugin.Logger; if (logger3 != null) { logger3.LogDebug((object)"Camera render callback registered"); } } public static void UnregisterCameraCallback() { if (callbackRegistered) { RenderPipelineHelper.UnregisterCallbacks(); callbackRegistered = false; ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogDebug((object)"Camera render callback unregistered"); } } } public static Vector2 GetHeadTrackingInput() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) return new Vector2(currentYaw, currentPitch); } public static bool IsHeadTrackingEnabled() { return headTrackingEnabled; } private static void OnPreRender(Camera cam) { //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: 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_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: 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) //IL_0148: Unknown result type (might be due to invalid IL or missing references) Camera main = Camera.main; if ((Object)(object)main == (Object)null) { LogDiagnostic("[HeadTracking] Camera.main is null - waiting for camera"); } else { if ((Object)(object)cam != (Object)(object)main || !headTrackingEnabled) { return; } if (receiver == null) { LogDiagnostic("[HeadTracking] ERROR: Receiver is null - tracking disabled"); return; } if (GameplayStateDetection.ShouldSkipHeadTracking()) { ReticleCompensation.ResetReticlePosition(); return; } float num = processedYaw; float num2 = processedPitch; float num3 = processedRoll; bool flag = Mathf.Abs(num) >= 0.1f || Mathf.Abs(num2) >= 0.1f || Mathf.Abs(num3) >= 0.1f; bool flag2 = positionProcessor != null && positionEnabledConfig != null && positionEnabledConfig.Value && receiver != null; bool flag3 = rotationEnabled && flag; if (!flag3 && !flag2) { return; } if (flag3) { ViewMatrixModifier.ApplyHeadRotation(cam, num, 0f - num2, num3); matrixModifiedThisFrame = true; } if (flag2) { if (!matrixModifiedThisFrame) { cam.ResetWorldToCameraMatrix(); } PositionData latestPosition = receiver.GetLatestPosition(); PositionData val = positionInterpolator.Update(latestPosition, Time.deltaTime); Quat4 val2 = QuaternionUtils.FromYawPitchRoll(num, 0f - num2, num3); Vec3 val3 = positionProcessor.Process(val, val2, Time.deltaTime); cam.worldToCameraMatrix = Matrix4x4.Translate(-UnityTypeExtensions.ToUnity(val3)) * cam.worldToCameraMatrix; matrixModifiedThisFrame = true; } storedNearClipPlane = cam.nearClipPlane; if (nearClipConfig != null && cam.nearClipPlane < nearClipConfig.Value) { cam.nearClipPlane = nearClipConfig.Value; } if (showReticleConfig != null && showReticleConfig.Value && ReticleCompensation.CanUpdateReticle()) { ReticleCompensation.UpdateReticlePosition(cam); } else { ReticleCompensation.ResetReticlePosition(); } if (!hasLoggedFirstApplication) { ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogInfo((object)$"[ApplyHeadTracking] SUCCESS! Applied via ViewMatrixModifier: Yaw={num:F2}, Pitch={num2:F2}, Roll={num3:F2}"); } hasLoggedFirstApplication = true; } } } private static void OnPostRender(Camera cam) { Camera main = Camera.main; if (!((Object)(object)main == (Object)null) && !((Object)(object)cam != (Object)(object)main) && matrixModifiedThisFrame) { matrixModifiedThisFrame = false; ViewMatrixModifier.Reset(cam); cam.nearClipPlane = storedNearClipPlane; } } private static void LogDiagnostic(string message) { int frameCount = Time.frameCount; if (frameCount - lastDiagnosticFrame >= 300) { lastDiagnosticFrame = frameCount; ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogWarning((object)message); } } } } [HarmonyPatch] public static class CameraQuadPatches { private static bool patchActive; private static Quaternion storedRotation; [HarmonyTargetMethod] public static MethodBase TargetMethod() { try { Type type = AccessTools.TypeByName("CameraQuad"); if (type == null) { ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogWarning((object)"[CameraQuadPatch] CameraQuad type not found - patch will not be applied"); } return null; } MethodInfo methodInfo = AccessTools.Method(type, "LateUpdate", (Type[])null, (Type[])null); if (methodInfo == null) { ManualLogSource logger2 = PeakHeadTrackingPlugin.Logger; if (logger2 != null) { logger2.LogWarning((object)"[CameraQuadPatch] CameraQuad.LateUpdate method not found"); } return null; } patchActive = true; ManualLogSource logger3 = PeakHeadTrackingPlugin.Logger; if (logger3 != null) { logger3.LogInfo((object)"[CameraQuadPatch] Successfully targeting CameraQuad.LateUpdate"); } return methodInfo; } catch (Exception ex) { ManualLogSource logger4 = PeakHeadTrackingPlugin.Logger; if (logger4 != null) { logger4.LogError((object)("[CameraQuadPatch] Error finding target method: " + ex.Message)); } return null; } } [HarmonyPrefix] public static void LateUpdate_Prefix() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (patchActive && CameraPatches.IsHeadTrackingEnabled()) { Camera main = Camera.main; if (!((Object)(object)main == (Object)null)) { float processedYaw = CameraPatches.ProcessedYaw; float processedPitch = CameraPatches.ProcessedPitch; float processedRoll = CameraPatches.ProcessedRoll; storedRotation = ((Component)main).transform.rotation; ((Component)main).transform.rotation = CameraRotationComposer.ComposeAdditive(storedRotation, processedYaw, processedPitch, processedRoll); } } } [HarmonyPostfix] public static void LateUpdate_Postfix() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (patchActive && CameraPatches.IsHeadTrackingEnabled()) { Camera main = Camera.main; if (!((Object)(object)main == (Object)null)) { ((Component)main).transform.rotation = storedRotation; } } } } internal static class GameplayStateDetection { private static bool loadingReflectionInitialized = false; private static Func getIsLoading; private static Type characterType; private static bool gameplayReflectionInitialized = false; private static Func getLocalCharacter; private static Func getGUIManagerInstance; private static Func getPauseMenu; private static string cachedSceneName = ""; private static bool isOnTitleScene = false; private const string TitleSceneName = "Title"; private static void UpdateSceneCache() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; if (name != cachedSceneName) { cachedSceneName = name; isOnTitleScene = name == "Title"; ReticleCompensation.InvalidateCache(); } } internal static bool ShouldSkipHeadTracking() { UpdateSceneCache(); if (isOnTitleScene) { return true; } if (!gameplayReflectionInitialized) { gameplayReflectionInitialized = true; characterType = Type.GetType("Character, Assembly-CSharp"); if (characterType != null) { FieldInfo field = characterType.GetField("localCharacter", BindingFlags.Static | BindingFlags.Public); if (field != null) { getLocalCharacter = ReflectionUtils.CreateStaticFieldGetter(field); } } if (ReticleCompensation.GUIManagerType != null && ReticleCompensation.GUIManagerInstanceField != null) { getGUIManagerInstance = ReflectionUtils.CreateStaticFieldGetter(ReticleCompensation.GUIManagerInstanceField); FieldInfo field2 = ReticleCompensation.GUIManagerType.GetField("pauseMenu", BindingFlags.Instance | BindingFlags.Public); if (field2 != null) { getPauseMenu = ReflectionUtils.CreateInstanceFieldGetter(ReticleCompensation.GUIManagerType, field2); } } ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogInfo((object)$"[GameplayDetection] Compiled delegates - localCharacter: {getLocalCharacter != null}, guiInstance: {getGUIManagerInstance != null}, pauseMenu: {getPauseMenu != null}"); } } if (getLocalCharacter == null) { throw new InvalidOperationException("Cannot detect gameplay state: getLocalCharacter delegate was not compiled during reflection setup"); } if (getLocalCharacter() == null) { return true; } if (getGUIManagerInstance != null && getPauseMenu != null) { object obj = getGUIManagerInstance(); if (obj != null) { object obj2 = getPauseMenu(obj); GameObject val = (GameObject)((obj2 is GameObject) ? obj2 : null); if (val != null && val.activeSelf) { return true; } } } if (!loadingReflectionInitialized) { loadingReflectionInitialized = true; Type type = Type.GetType("LoadingScreenHandler, Assembly-CSharp"); if (type != null) { PropertyInfo property = type.GetProperty("loading", BindingFlags.Static | BindingFlags.Public); if (property != null) { getIsLoading = ReflectionUtils.CreateStaticPropertyGetter(property); } } } if (getIsLoading != null && getIsLoading()) { return true; } return false; } } [HarmonyPatch] public static class HeadRotationPatches { private static readonly int AN_LOOK_Y = Animator.StringToHash("Look Y"); private static readonly int AN_LOOK_X = Animator.StringToHash("Look X"); private const float DegreesNormalizationFactor = 90f; private static Type characterAnimationsType; private static Type characterType; private static Func getCharacterFromAnimations; private static Func getLocalCharacter; private static Func getRefsFromCharacter; private static Func getAnimatorFromRefs; private static bool reflectionInitialized = false; private static bool reflectionFailed = false; private static bool hasLoggedSuccess = false; [HarmonyTargetMethod] public static MethodBase TargetMethod() { characterAnimationsType = Type.GetType("CharacterAnimations, Assembly-CSharp"); if (characterAnimationsType == null) { throw new TypeLoadException("[HeadRotation] CharacterAnimations type not found in Assembly-CSharp"); } MethodInfo? method = characterAnimationsType.GetMethod("Update", BindingFlags.Instance | BindingFlags.NonPublic); if (method == null) { throw new MissingMethodException("[HeadRotation] CharacterAnimations.Update method not found"); } ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogInfo((object)"[HeadRotation] Found target method: CharacterAnimations.Update"); return method; } return method; } private static void InitializeReflection() { if (reflectionFailed) { throw new InvalidOperationException("HeadRotationPatches reflection initialization previously failed. Cannot proceed."); } if (!reflectionInitialized) { reflectionInitialized = true; if (characterAnimationsType == null) { characterAnimationsType = Type.GetType("CharacterAnimations, Assembly-CSharp"); } if (characterAnimationsType == null) { reflectionFailed = true; throw new TypeLoadException("[HeadRotation] CharacterAnimations type not found in reflection init"); } FieldInfo field = characterAnimationsType.GetField("character", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field == null) { reflectionFailed = true; throw new MissingFieldException("[HeadRotation] CharacterAnimations.character field not found"); } characterType = Type.GetType("Character, Assembly-CSharp"); if (characterType == null) { reflectionFailed = true; throw new TypeLoadException("[HeadRotation] Character type not found"); } FieldInfo field2 = characterType.GetField("localCharacter", BindingFlags.Static | BindingFlags.Public); if (field2 == null) { reflectionFailed = true; throw new MissingFieldException("[HeadRotation] Character.localCharacter field not found"); } FieldInfo field3 = characterType.GetField("refs", BindingFlags.Instance | BindingFlags.Public); if (field3 == null) { reflectionFailed = true; throw new MissingFieldException("[HeadRotation] Character.refs field not found"); } Type fieldType = field3.FieldType; FieldInfo field4 = fieldType.GetField("animator", BindingFlags.Instance | BindingFlags.Public); if (field4 == null) { reflectionFailed = true; throw new MissingFieldException("[HeadRotation] CharacterRefs.animator field not found"); } getCharacterFromAnimations = ReflectionUtils.CreateInstanceFieldGetter(characterAnimationsType, field); getLocalCharacter = ReflectionUtils.CreateStaticFieldGetter(field2); getRefsFromCharacter = ReflectionUtils.CreateInstanceFieldGetter(characterType, field3); getAnimatorFromRefs = ReflectionUtils.CreateInstanceFieldGetter(fieldType, field4); ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogInfo((object)"[HeadRotation] Reflection initialized with compiled delegates"); } } } [HarmonyPostfix] public static void Postfix(object __instance) { InitializeReflection(); if (!CameraPatches.IsHeadTrackingEnabled()) { return; } float currentYaw = CameraPatches.CurrentYaw; float currentPitch = CameraPatches.CurrentPitch; if (Mathf.Abs(currentYaw) < 0.1f && Mathf.Abs(currentPitch) < 0.1f) { return; } object obj = getCharacterFromAnimations(__instance); if (obj == null) { return; } object obj2 = getLocalCharacter(); if (obj2 == null || obj != obj2) { return; } object obj3 = getRefsFromCharacter(obj2); if (obj3 == null) { return; } Animator val = getAnimatorFromRefs(obj3); if ((Object)(object)val == (Object)null) { return; } float @float = val.GetFloat(AN_LOOK_X); float float2 = val.GetFloat(AN_LOOK_Y); float num = currentYaw / 90f; float num2 = currentPitch / 90f; float num3 = @float + num; float num4 = float2 - num2; val.SetFloat(AN_LOOK_X, num3); val.SetFloat(AN_LOOK_Y, num4); if (!hasLoggedSuccess) { ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogInfo((object)$"[HeadRotation] SUCCESS! Modified animator Look params: LookX {@float:F2}->{num3:F2}, LookY {float2:F2}->{num4:F2}"); } hasLoggedSuccess = true; } } } internal static class ReticleCompensation { private static Type guiManagerType; private static FieldInfo instanceField; private static bool reticleReflectionInitialized = false; private static Func getGUIManagerInstance; private static Func getReticleDefault; private static bool reticleParentFound = false; private static RectTransform reticleParentTransform; private static Canvas cachedReticleCanvas; private static float cachedCanvasScaleFactor = 1f; private static Func getInteractName; private static Func getInteractPromptPrimary; private static Func getInteractPromptSecondary; private static Func getInteractPromptHold; private static Func getInteractPromptLunge; private static RectTransform[] interactTransforms; private static bool interactElementsFound = false; private static bool interactElementsSearched = false; internal static Type GUIManagerType => guiManagerType; internal static FieldInfo GUIManagerInstanceField => instanceField; internal static void InitializeReticleReflection() { if (reticleReflectionInitialized) { return; } reticleReflectionInitialized = true; guiManagerType = Type.GetType("GUIManager, Assembly-CSharp"); if (guiManagerType == null) { throw new InvalidOperationException("[Reticle] GUIManager type not found - game version may be incompatible"); } instanceField = guiManagerType.GetField("instance", BindingFlags.Static | BindingFlags.Public); FieldInfo field = guiManagerType.GetField("reticleDefault", BindingFlags.Instance | BindingFlags.Public); if (instanceField == null || field == null) { throw new InvalidOperationException("[Reticle] GUIManager required fields not found - game version may be incompatible"); } getGUIManagerInstance = ReflectionUtils.CreateStaticFieldGetter(instanceField); getReticleDefault = ReflectionUtils.CreateInstanceFieldGetter(guiManagerType, field); string[] array = new string[5] { "interactName", "interactPromptPrimary", "interactPromptSecondary", "interactPromptHold", "interactPromptLunge" }; Func[] array2 = new Func[array.Length]; for (int i = 0; i < array.Length; i++) { FieldInfo field2 = guiManagerType.GetField(array[i], BindingFlags.Instance | BindingFlags.Public); if (field2 != null) { array2[i] = ReflectionUtils.CreateInstanceFieldGetter(guiManagerType, field2); } } getInteractName = array2[0]; getInteractPromptPrimary = array2[1]; getInteractPromptSecondary = array2[2]; getInteractPromptHold = array2[3]; getInteractPromptLunge = array2[4]; ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogInfo((object)"[Reticle] Reflection initialized successfully"); } } private static void FindReticleParent() { if (guiManagerType == null || (reticleParentFound && (Object)(object)reticleParentTransform != (Object)null)) { return; } if (reticleParentFound) { ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogInfo((object)"[Reticle] Cached reticle parent became invalid, re-finding..."); } reticleParentFound = false; reticleParentTransform = null; cachedReticleCanvas = null; cachedCanvasScaleFactor = 1f; } object obj = getGUIManagerInstance(); if (obj == null) { return; } object obj2 = getReticleDefault(obj); GameObject val = (GameObject)((obj2 is GameObject) ? obj2 : null); if ((Object)(object)val == (Object)null) { return; } Transform parent = val.transform.parent; if (!((Object)(object)parent != (Object)null)) { return; } reticleParentTransform = ((Component)parent).GetComponent(); if ((Object)(object)reticleParentTransform != (Object)null) { reticleParentFound = true; cachedReticleCanvas = ((Component)reticleParentTransform).GetComponentInParent(); if ((Object)(object)cachedReticleCanvas != (Object)null && cachedReticleCanvas.scaleFactor > 0f) { cachedCanvasScaleFactor = cachedReticleCanvas.scaleFactor; } else { cachedCanvasScaleFactor = 1f; } ManualLogSource logger2 = PeakHeadTrackingPlugin.Logger; if (logger2 != null) { logger2.LogInfo((object)$"[Reticle] Found reticle parent: {((Object)parent).name}, canvas scale: {cachedCanvasScaleFactor}"); } } } private static void FindInteractElements() { if (interactElementsFound || interactElementsSearched || getInteractName == null || getGUIManagerInstance == null) { return; } interactElementsSearched = true; object obj = getGUIManagerInstance(); if (obj == null) { interactElementsSearched = false; return; } Func[] obj2 = new Func[5] { getInteractName, getInteractPromptPrimary, getInteractPromptSecondary, getInteractPromptHold, getInteractPromptLunge }; List list = new List(); Func[] array = obj2; foreach (Func func in array) { if (func == null) { continue; } object obj3 = func(obj); GameObject val = (GameObject)((obj3 is GameObject) ? obj3 : null); if (!((Object)(object)val == (Object)null)) { RectTransform component = val.GetComponent(); if ((Object)(object)component != (Object)null) { list.Add(component); } } } if (list.Count > 0) { interactTransforms = list.ToArray(); interactElementsFound = true; ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogInfo((object)$"[Reticle] Found {interactTransforms.Length} interact elements to compensate"); } } } internal static bool CanUpdateReticle() { InitializeReticleReflection(); FindReticleParent(); if (reticleParentFound) { return (Object)(object)reticleParentTransform != (Object)null; } return false; } internal static void UpdateReticlePosition(Camera cam) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (!reticleParentFound || (Object)(object)reticleParentTransform == (Object)null) { throw new InvalidOperationException("Reticle parent transform not found. Caller must check CanUpdateReticle() before calling."); } Vector3 forward = ((Component)cam).transform.forward; Vector2 anchoredPosition = CanvasCompensation.CalculateAimScreenOffset(cam, forward, cachedCanvasScaleFactor); reticleParentTransform.anchoredPosition = anchoredPosition; FindInteractElements(); if (!interactElementsFound) { return; } RectTransform[] array = interactTransforms; foreach (RectTransform val in array) { if ((Object)(object)val != (Object)null) { val.anchoredPosition = anchoredPosition; } } } public static void ResetReticlePosition() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (reticleParentFound && (Object)(object)reticleParentTransform != (Object)null) { reticleParentTransform.anchoredPosition = Vector2.zero; } if (!interactElementsFound) { return; } RectTransform[] array = interactTransforms; foreach (RectTransform val in array) { if ((Object)(object)val != (Object)null) { val.anchoredPosition = Vector2.zero; } } } internal static void InvalidateCache() { if (reticleParentFound && (Object)(object)reticleParentTransform == (Object)null) { reticleParentFound = false; cachedReticleCanvas = null; cachedCanvasScaleFactor = 1f; } if (!interactElementsFound) { return; } bool flag = false; RectTransform[] array = interactTransforms; for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i] == (Object)null) { flag = true; break; } } if (flag) { interactElementsFound = false; interactElementsSearched = false; interactTransforms = null; } } } internal static class TrackingConstants { internal const float MovementThreshold = 0.1f; } } namespace PeakHeadTracking.Input { public class HotkeyManager : MonoBehaviour { private ModConfiguration config; private CameraController cameraController; private OpenTrackReceiver coreReceiver; private bool wasTogglePressed; private bool wasRecenterPressed; private bool wasReloadPressed; private bool wasCyclePressed; private int trackingModeIndex; public void Initialize(ModConfiguration modConfig, CameraController camController, OpenTrackReceiver trackReceiver) { config = modConfig; cameraController = camController; coreReceiver = trackReceiver; PeakHeadTrackingPlugin.Logger.LogDebug((object)"HotkeyManager initialized"); } private void Update() { if (config != null) { HandleToggleTracking(); HandleRecenterView(); HandleReloadConfig(); HandleCycleTrackingMode(); } } private static bool IsChordHeld(KeyCode letter) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) if ((Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305)) && (Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303))) { return Input.GetKey(letter); } return false; } private void HandleToggleTracking() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) bool flag = Input.GetKey(config.ToggleTrackingKey.Value) || IsChordHeld((KeyCode)121); if (flag && !wasTogglePressed) { bool flag2 = !config.TrackingEnabled.Value; config.TrackingEnabled.Value = flag2; if (flag2 && !coreReceiver.IsReceiving && !coreReceiver.IsFailed) { coreReceiver.Start(config.UdpPort.Value); } cameraController.SetTrackingEnabled(flag2); PeakHeadTrackingPlugin.Logger.LogInfo((object)("Tracking toggled: " + (flag2 ? "ON" : "OFF"))); } wasTogglePressed = flag; } private void HandleRecenterView() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) bool flag = Input.GetKey(config.RecenterKey.Value) || IsChordHeld((KeyCode)116); if (flag && !wasRecenterPressed) { cameraController.RecenterView(); PeakHeadTrackingPlugin.Logger.LogInfo((object)"View recentered"); } wasRecenterPressed = flag; } private void HandleReloadConfig() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) bool key = Input.GetKey(config.ReloadConfigKey.Value); if (key && !wasReloadPressed) { config.Reload(); coreReceiver.Dispose(); coreReceiver.Start(config.UdpPort.Value); PeakHeadTrackingPlugin.Logger.LogInfo((object)"Configuration reloaded"); } wasReloadPressed = key; } private void HandleCycleTrackingMode() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) bool flag = Input.GetKey(config.TogglePositionKey.Value) || IsChordHeld((KeyCode)103); if (flag && !wasCyclePressed) { trackingModeIndex = (trackingModeIndex + 1) % 3; ApplyTrackingMode(); } wasCyclePressed = flag; } private void ApplyTrackingMode() { bool rotationEnabled; bool value; string text; switch (trackingModeIndex) { case 1: rotationEnabled = true; value = false; text = "rotation only (position disabled)"; break; case 2: rotationEnabled = false; value = true; text = "position only (rotation disabled)"; break; default: rotationEnabled = true; value = true; text = "full (rotation + position)"; break; } config.PositionEnabled.Value = value; CameraPatches.SetRotationEnabled(rotationEnabled); PeakHeadTrackingPlugin.Logger.LogInfo((object)("Tracking mode: " + text)); } public void ResetStates() { wasTogglePressed = false; wasRecenterPressed = false; wasReloadPressed = false; wasCyclePressed = false; } private void OnDisable() { ResetStates(); } } } namespace PeakHeadTracking.Config { public static class ConfigCategories { public const string CONNECTION = "01. Connection"; public const string GENERAL = "02. General"; public const string SENSITIVITY = "03. Sensitivity"; public const string LIMITS = "04. Rotation Limits"; public const string SMOOTHING = "05. Smoothing"; public const string DEADZONE = "06. Deadzone"; public const string HOTKEYS = "07. Hotkeys"; public const string ADVANCED = "08. Advanced"; } public class ConfigChangedEventArgs : EventArgs { public string SettingName { get; set; } public object OldValue { get; set; } public object NewValue { get; set; } public string Category { get; set; } } public class ConfigProfile { public string Name { get; set; } public string Description { get; set; } public DateTime CreatedDate { get; set; } public DateTime ModifiedDate { get; set; } public string GameName { get; set; } public Dictionary Settings { get; set; } = new Dictionary(); public bool IsDefault { get; set; } public bool IsReadOnly { get; set; } public void ExportFromConfig(ModConfiguration config) { //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) Settings.Clear(); Settings["UdpPort"] = config.UdpPort.Value; Settings["ReconnectTimeout"] = config.ReconnectTimeout.Value; Settings["PacketBufferSize"] = config.PacketBufferSize.Value; Settings["TrackingEnabled"] = config.TrackingEnabled.Value; Settings["EnableAudioFeedback"] = config.EnableAudioFeedback.Value; Settings["YawSensitivity"] = config.YawSensitivity.Value; Settings["PitchSensitivity"] = config.PitchSensitivity.Value; Settings["RollSensitivity"] = config.RollSensitivity.Value; Settings["InvertYaw"] = config.InvertYaw.Value; Settings["InvertPitch"] = config.InvertPitch.Value; Settings["InvertRoll"] = config.InvertRoll.Value; Settings["EnableRoll"] = config.EnableRoll.Value; Settings["Smoothing"] = config.Smoothing.Value; Settings["EnableDeadzone"] = config.EnableDeadzone.Value; Settings["DeadzoneYaw"] = config.DeadzoneYaw.Value; Settings["DeadzonePitch"] = config.DeadzonePitch.Value; Settings["DeadzoneRoll"] = config.DeadzoneRoll.Value; Settings["ToggleTrackingKey"] = config.ToggleTrackingKey.Value; Settings["RecenterKey"] = config.RecenterKey.Value; Settings["ReloadConfigKey"] = config.ReloadConfigKey.Value; Settings["DebugLogging"] = config.DebugLogging.Value; Settings["UpdateRate"] = config.UpdateRate.Value; ModifiedDate = DateTime.Now; } public void ImportToConfig(ModConfiguration config) { //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) if (Settings.TryGetValue("UdpPort", out var value)) { config.UdpPort.Value = Convert.ToInt32(value); } if (Settings.TryGetValue("ReconnectTimeout", out var value2)) { config.ReconnectTimeout.Value = Convert.ToInt32(value2); } if (Settings.TryGetValue("PacketBufferSize", out var value3)) { config.PacketBufferSize.Value = Convert.ToInt32(value3); } if (Settings.TryGetValue("TrackingEnabled", out var value4)) { config.TrackingEnabled.Value = Convert.ToBoolean(value4); } if (Settings.TryGetValue("EnableAudioFeedback", out var value5)) { config.EnableAudioFeedback.Value = Convert.ToBoolean(value5); } if (Settings.TryGetValue("YawSensitivity", out var value6)) { config.YawSensitivity.Value = Convert.ToSingle(value6); } if (Settings.TryGetValue("PitchSensitivity", out var value7)) { config.PitchSensitivity.Value = Convert.ToSingle(value7); } if (Settings.TryGetValue("RollSensitivity", out var value8)) { config.RollSensitivity.Value = Convert.ToSingle(value8); } if (Settings.TryGetValue("InvertYaw", out var value9)) { config.InvertYaw.Value = Convert.ToBoolean(value9); } if (Settings.TryGetValue("InvertPitch", out var value10)) { config.InvertPitch.Value = Convert.ToBoolean(value10); } if (Settings.TryGetValue("InvertRoll", out var value11)) { config.InvertRoll.Value = Convert.ToBoolean(value11); } if (Settings.TryGetValue("EnableRoll", out var value12)) { config.EnableRoll.Value = Convert.ToBoolean(value12); } if (Settings.TryGetValue("Smoothing", out var value13)) { config.Smoothing.Value = Convert.ToSingle(value13); } if (Settings.TryGetValue("EnableDeadzone", out var value14)) { config.EnableDeadzone.Value = Convert.ToBoolean(value14); } if (Settings.TryGetValue("DeadzoneYaw", out var value15)) { config.DeadzoneYaw.Value = Convert.ToSingle(value15); } if (Settings.TryGetValue("DeadzonePitch", out var value16)) { config.DeadzonePitch.Value = Convert.ToSingle(value16); } if (Settings.TryGetValue("DeadzoneRoll", out var value17)) { config.DeadzoneRoll.Value = Convert.ToSingle(value17); } if (Settings.TryGetValue("ToggleTrackingKey", out var value18)) { config.ToggleTrackingKey.Value = (KeyCode)value18; } if (Settings.TryGetValue("RecenterKey", out var value19)) { config.RecenterKey.Value = (KeyCode)value19; } if (Settings.TryGetValue("ReloadConfigKey", out var value20)) { config.ReloadConfigKey.Value = (KeyCode)value20; } if (Settings.TryGetValue("DebugLogging", out var value21)) { config.DebugLogging.Value = Convert.ToBoolean(value21); } if (Settings.TryGetValue("UpdateRate", out var value22)) { config.UpdateRate.Value = Convert.ToInt32(value22); } } public ConfigProfile Clone(string newName) { return new ConfigProfile { Name = newName, Description = Description + " (Copy)", CreatedDate = DateTime.Now, ModifiedDate = DateTime.Now, GameName = GameName, IsDefault = false, IsReadOnly = false, Settings = new Dictionary(Settings) }; } } public class ConfigurationManager { private static ConfigurationManager instance; private ModConfiguration modConfig; private ProfileManager profileManager; private bool isInitialized; private readonly Dictionary> settingCallbacks = new Dictionary>(); public static ConfigurationManager Instance => instance ?? (instance = new ConfigurationManager()); public ModConfiguration Config => modConfig; public ProfileManager Profiles => profileManager; public event EventHandler ConfigChanged; public event EventHandler ProfileChanged; public void Initialize(ConfigFile config) { if (!isInitialized) { modConfig = new ModConfiguration(); modConfig.Initialize(config); profileManager = new ProfileManager(config); if (profileManager.GetActiveProfile() != null) { profileManager.ApplyProfileToConfig(modConfig); } SetupConfigurationWatchers(); isInitialized = true; PeakHeadTrackingPlugin.Logger.LogInfo((object)"Configuration manager initialized"); } } private void SetupConfigurationWatchers() { modConfig.YawSensitivity.SettingChanged += delegate { OnConfigChanged("YawSensitivity", modConfig.YawSensitivity.Value, "03. Sensitivity"); }; modConfig.PitchSensitivity.SettingChanged += delegate { OnConfigChanged("PitchSensitivity", modConfig.PitchSensitivity.Value, "03. Sensitivity"); }; modConfig.RollSensitivity.SettingChanged += delegate { OnConfigChanged("RollSensitivity", modConfig.RollSensitivity.Value, "03. Sensitivity"); }; modConfig.InvertYaw.SettingChanged += delegate { OnConfigChanged("InvertYaw", modConfig.InvertYaw.Value, "03. Sensitivity"); }; modConfig.InvertPitch.SettingChanged += delegate { OnConfigChanged("InvertPitch", modConfig.InvertPitch.Value, "03. Sensitivity"); }; modConfig.InvertRoll.SettingChanged += delegate { OnConfigChanged("InvertRoll", modConfig.InvertRoll.Value, "03. Sensitivity"); }; modConfig.EnableDeadzone.SettingChanged += delegate { OnConfigChanged("EnableDeadzone", modConfig.EnableDeadzone.Value, "06. Deadzone"); }; modConfig.DeadzoneYaw.SettingChanged += delegate { OnConfigChanged("DeadzoneYaw", modConfig.DeadzoneYaw.Value, "06. Deadzone"); }; modConfig.DeadzonePitch.SettingChanged += delegate { OnConfigChanged("DeadzonePitch", modConfig.DeadzonePitch.Value, "06. Deadzone"); }; modConfig.DeadzoneRoll.SettingChanged += delegate { OnConfigChanged("DeadzoneRoll", modConfig.DeadzoneRoll.Value, "06. Deadzone"); }; modConfig.EnablePitchLimits.SettingChanged += delegate { OnConfigChanged("EnablePitchLimits", modConfig.EnablePitchLimits.Value, "04. Rotation Limits"); }; modConfig.MinPitch.SettingChanged += delegate { OnConfigChanged("MinPitch", modConfig.MinPitch.Value, "04. Rotation Limits"); }; modConfig.MaxPitch.SettingChanged += delegate { OnConfigChanged("MaxPitch", modConfig.MaxPitch.Value, "04. Rotation Limits"); }; modConfig.EnableRoll.SettingChanged += delegate { OnConfigChanged("EnableRoll", modConfig.EnableRoll.Value, "04. Rotation Limits"); }; modConfig.EnableRollLimits.SettingChanged += delegate { OnConfigChanged("EnableRollLimits", modConfig.EnableRollLimits.Value, "04. Rotation Limits"); }; modConfig.MaxRoll.SettingChanged += delegate { OnConfigChanged("MaxRoll", modConfig.MaxRoll.Value, "04. Rotation Limits"); }; } private void OnConfigChanged(string settingName, object newValue, string category) { this.ConfigChanged?.Invoke(this, new ConfigChangedEventArgs { SettingName = settingName, NewValue = newValue, Category = category }); if (!settingCallbacks.TryGetValue(settingName, out var value)) { return; } foreach (Action item in value) { item?.Invoke(); } } public void RegisterSettingChangeCallback(string settingName, Action callback) { if (!settingCallbacks.ContainsKey(settingName)) { settingCallbacks[settingName] = new List(); } settingCallbacks[settingName].Add(callback); } public void UnregisterSettingChangeCallback(string settingName, Action callback) { if (settingCallbacks.TryGetValue(settingName, out var value)) { value.Remove(callback); } } public void ReloadConfiguration() { modConfig?.Reload(); if (profileManager?.GetActiveProfile() != null) { profileManager.ApplyProfileToConfig(modConfig); } PeakHeadTrackingPlugin.Logger.LogInfo((object)"Configuration reloaded"); } public void SaveConfiguration() { modConfig?.Save(); profileManager?.SaveCurrentToProfile(modConfig); PeakHeadTrackingPlugin.Logger.LogInfo((object)"Configuration saved"); } public void SwitchProfile(string profileName) { profileManager.LoadProfile(profileName); profileManager.ApplyProfileToConfig(modConfig); this.ProfileChanged?.Invoke(this, profileName); PeakHeadTrackingPlugin.Logger.LogInfo((object)("Switched to profile: " + profileName)); } public void ResetToDefaults() { modConfig.YawSensitivity.Value = (float)((ConfigEntryBase)modConfig.YawSensitivity).DefaultValue; modConfig.PitchSensitivity.Value = (float)((ConfigEntryBase)modConfig.PitchSensitivity).DefaultValue; modConfig.RollSensitivity.Value = (float)((ConfigEntryBase)modConfig.RollSensitivity).DefaultValue; modConfig.InvertYaw.Value = (bool)((ConfigEntryBase)modConfig.InvertYaw).DefaultValue; modConfig.InvertPitch.Value = (bool)((ConfigEntryBase)modConfig.InvertPitch).DefaultValue; modConfig.InvertRoll.Value = (bool)((ConfigEntryBase)modConfig.InvertRoll).DefaultValue; SaveConfiguration(); } public void ExportConfiguration(string filePath) { using (StreamWriter streamWriter = new StreamWriter(filePath)) { streamWriter.WriteLine("# PeakHeadTracking Configuration Export"); streamWriter.WriteLine($"# Exported: {DateTime.Now}"); streamWriter.WriteLine("# Profile: " + profileManager.GetActiveProfileName()); streamWriter.WriteLine(); streamWriter.WriteLine($"UdpPort={modConfig.UdpPort.Value}"); streamWriter.WriteLine($"TrackingEnabled={modConfig.TrackingEnabled.Value}"); streamWriter.WriteLine($"YawSensitivity={modConfig.YawSensitivity.Value}"); streamWriter.WriteLine($"PitchSensitivity={modConfig.PitchSensitivity.Value}"); streamWriter.WriteLine($"RollSensitivity={modConfig.RollSensitivity.Value}"); streamWriter.WriteLine($"InvertYaw={modConfig.InvertYaw.Value}"); streamWriter.WriteLine($"InvertPitch={modConfig.InvertPitch.Value}"); streamWriter.WriteLine($"InvertRoll={modConfig.InvertRoll.Value}"); } PeakHeadTrackingPlugin.Logger.LogInfo((object)("Configuration exported to " + filePath)); } public void Cleanup() { settingCallbacks.Clear(); this.ConfigChanged = null; this.ProfileChanged = null; isInitialized = false; } } public class ModConfiguration { private ConfigFile configFile; public ConfigEntry UdpPort { get; private set; } public ConfigEntry ReconnectTimeout { get; private set; } public ConfigEntry PacketBufferSize { get; private set; } public ConfigEntry TrackingEnabled { get; private set; } public ConfigEntry EnableAudioFeedback { get; private set; } public ConfigEntry YawSensitivity { get; private set; } public ConfigEntry PitchSensitivity { get; private set; } public ConfigEntry RollSensitivity { get; private set; } public ConfigEntry InvertYaw { get; private set; } public ConfigEntry InvertPitch { get; private set; } public ConfigEntry InvertRoll { get; private set; } public ConfigEntry EnablePitchLimits { get; private set; } public ConfigEntry MinPitch { get; private set; } public ConfigEntry MaxPitch { get; private set; } public ConfigEntry EnableRoll { get; private set; } public ConfigEntry EnableRollLimits { get; private set; } public ConfigEntry MaxRoll { get; private set; } public ConfigEntry Smoothing { get; private set; } public ConfigEntry EnableDeadzone { get; private set; } public ConfigEntry DeadzoneYaw { get; private set; } public ConfigEntry DeadzonePitch { get; private set; } public ConfigEntry DeadzoneRoll { get; private set; } public ConfigEntry ToggleTrackingKey { get; private set; } public ConfigEntry RecenterKey { get; private set; } public ConfigEntry ReloadConfigKey { get; private set; } public ConfigEntry TogglePositionKey { get; private set; } public ConfigEntry ToggleReticleKey { get; private set; } public ConfigEntry ShowReticle { get; private set; } public ConfigEntry DebugLogging { get; private set; } public ConfigEntry UpdateRate { get; private set; } public ConfigEntry MaintainRelativePosition { get; private set; } public ConfigEntry PositionEnabled { get; private set; } public ConfigEntry PositionSensitivityX { get; private set; } public ConfigEntry PositionSensitivityY { get; private set; } public ConfigEntry PositionSensitivityZ { get; private set; } public ConfigEntry PositionLimitX { get; private set; } public ConfigEntry PositionLimitY { get; private set; } public ConfigEntry PositionLimitZ { get; private set; } public ConfigEntry PositionSmoothing { get; private set; } public ConfigEntry NearClipOverride { get; private set; } public void Initialize(ConfigFile config) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Expected O, but got Unknown //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Expected O, but got Unknown //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Expected O, but got Unknown //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Expected O, but got Unknown //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Expected O, but got Unknown //IL_02c8: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Expected O, but got Unknown //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Expected O, but got Unknown //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_0360: Expected O, but got Unknown //IL_038f: Unknown result type (might be due to invalid IL or missing references) //IL_0399: Expected O, but got Unknown //IL_03c8: Unknown result type (might be due to invalid IL or missing references) //IL_03d2: Expected O, but got Unknown //IL_04d0: Unknown result type (might be due to invalid IL or missing references) //IL_04da: Expected O, but got Unknown //IL_0541: Unknown result type (might be due to invalid IL or missing references) //IL_054b: Expected O, but got Unknown //IL_057a: Unknown result type (might be due to invalid IL or missing references) //IL_0584: Expected O, but got Unknown //IL_05b3: Unknown result type (might be due to invalid IL or missing references) //IL_05bd: Expected O, but got Unknown //IL_05ec: Unknown result type (might be due to invalid IL or missing references) //IL_05f6: Expected O, but got Unknown //IL_0625: Unknown result type (might be due to invalid IL or missing references) //IL_062f: Expected O, but got Unknown //IL_065e: Unknown result type (might be due to invalid IL or missing references) //IL_0668: Expected O, but got Unknown //IL_0697: Unknown result type (might be due to invalid IL or missing references) //IL_06a1: Expected O, but got Unknown //IL_06d0: Unknown result type (might be due to invalid IL or missing references) //IL_06da: Expected O, but got Unknown configFile = config; UdpPort = config.Bind("01. Connection", "UDP Port", 4242, new ConfigDescription("Port number for OpenTrack UDP connection", (AcceptableValueBase)(object)new AcceptableValueRange(1024, 65535), Array.Empty())); ReconnectTimeout = config.Bind("01. Connection", "Reconnect Timeout", 5, new ConfigDescription("Seconds to wait before attempting reconnection", (AcceptableValueBase)(object)new AcceptableValueRange(1, 60), Array.Empty())); PacketBufferSize = config.Bind("01. Connection", "Packet Buffer Size", 100, new ConfigDescription("Maximum number of packets to buffer", (AcceptableValueBase)(object)new AcceptableValueRange(10, 500), Array.Empty())); TrackingEnabled = config.Bind("02. General", "Tracking Enabled", true, "Enable head tracking on startup"); EnableAudioFeedback = config.Bind("02. General", "Enable Audio Feedback", true, "Play sounds for tracking state changes"); YawSensitivity = config.Bind("03. Sensitivity", "Yaw Sensitivity", 1f, new ConfigDescription("Yaw (left/right) rotation multiplier", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 5f), Array.Empty())); PitchSensitivity = config.Bind("03. Sensitivity", "Pitch Sensitivity", 1f, new ConfigDescription("Pitch (up/down) rotation multiplier", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 5f), Array.Empty())); RollSensitivity = config.Bind("03. Sensitivity", "Roll Sensitivity", 1f, new ConfigDescription("Roll (tilt) rotation multiplier", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 5f), Array.Empty())); InvertYaw = config.Bind("03. Sensitivity", "Invert Yaw", false, "Invert yaw (left/right) axis"); InvertPitch = config.Bind("03. Sensitivity", "Invert Pitch", false, "Invert pitch (up/down) axis"); InvertRoll = config.Bind("03. Sensitivity", "Invert Roll", false, "Invert roll (tilt) axis"); EnablePitchLimits = config.Bind("04. Rotation Limits", "Enable Pitch Limits", true, "Limit pitch rotation range"); MinPitch = config.Bind("04. Rotation Limits", "Minimum Pitch", -85f, new ConfigDescription("Minimum pitch angle (looking down)", (AcceptableValueBase)(object)new AcceptableValueRange(-90f, 0f), Array.Empty())); MaxPitch = config.Bind("04. Rotation Limits", "Maximum Pitch", 85f, new ConfigDescription("Maximum pitch angle (looking up)", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 90f), Array.Empty())); EnableRoll = config.Bind("04. Rotation Limits", "Enable Roll", true, "Enable roll (head tilt) rotation"); EnableRollLimits = config.Bind("04. Rotation Limits", "Enable Roll Limits", true, "Limit roll rotation range"); MaxRoll = config.Bind("04. Rotation Limits", "Maximum Roll", 30f, new ConfigDescription("Maximum roll angle in either direction", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 90f), Array.Empty())); Smoothing = config.Bind("05. Smoothing", "Smoothing", 0f, new ConfigDescription("Smoothing factor (higher = smoother but adds latency). Remote connections automatically use a minimum of 0.15 for network latency compensation.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); EnableDeadzone = config.Bind("06. Deadzone", "Enable Deadzone", false, "Ignore small movements near center"); DeadzoneYaw = config.Bind("06. Deadzone", "Yaw Deadzone", 0f, new ConfigDescription("Deadzone for yaw axis (degrees)", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); DeadzonePitch = config.Bind("06. Deadzone", "Pitch Deadzone", 0f, new ConfigDescription("Deadzone for pitch axis (degrees)", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); DeadzoneRoll = config.Bind("06. Deadzone", "Roll Deadzone", 0f, new ConfigDescription("Deadzone for roll axis (degrees)", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())); ToggleTrackingKey = config.Bind("07. Hotkeys", "Toggle Tracking", (KeyCode)279, "Key to enable/disable tracking"); RecenterKey = config.Bind("07. Hotkeys", "Recenter View", (KeyCode)278, "Key to recenter the view"); ReloadConfigKey = config.Bind("07. Hotkeys", "Reload Config", (KeyCode)293, "Key to reload configuration"); TogglePositionKey = config.Bind("07. Hotkeys", "Toggle Position", (KeyCode)280, "Key to cycle tracking mode (full -> rotation only -> position only)"); ToggleReticleKey = config.Bind("07. Hotkeys", "Toggle Reticle", (KeyCode)277, "Key to toggle reticle compensation on/off"); ShowReticle = config.Bind("02. General", "Show Reticle", true, "Show reticle compensation (moves crosshair to show aim point during head tracking)"); DebugLogging = config.Bind("08. Advanced", "Debug Logging", false, "Enable detailed debug logging"); UpdateRate = config.Bind("08. Advanced", "Update Rate", 60, new ConfigDescription("Target update rate in Hz", (AcceptableValueBase)(object)new AcceptableValueRange(30, 120), Array.Empty())); MaintainRelativePosition = config.Bind("08. Advanced", "Maintain Relative Position", true, "Maintain camera position relative to target"); PositionEnabled = config.Bind("02. General", "Position Enabled", true, "Enable positional tracking (lean in/out/side-to-side)"); PositionSensitivityX = config.Bind("03. Sensitivity", "Position Sensitivity X", 2f, new ConfigDescription("Multiplier for lateral (left/right) position", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); PositionSensitivityY = config.Bind("03. Sensitivity", "Position Sensitivity Y", 2f, new ConfigDescription("Multiplier for vertical (up/down) position", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); PositionSensitivityZ = config.Bind("03. Sensitivity", "Position Sensitivity Z", 2f, new ConfigDescription("Multiplier for depth (forward/back) position", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); PositionLimitX = config.Bind("03. Sensitivity", "Position Limit X", 0.3f, new ConfigDescription("Maximum lateral displacement in meters", (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 0.5f), Array.Empty())); PositionLimitY = config.Bind("03. Sensitivity", "Position Limit Y", 0.2f, new ConfigDescription("Maximum vertical displacement in meters", (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 0.5f), Array.Empty())); PositionLimitZ = config.Bind("03. Sensitivity", "Position Limit Z", 0.4f, new ConfigDescription("Maximum depth displacement in meters", (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 0.5f), Array.Empty())); PositionSmoothing = config.Bind("05. Smoothing", "Position Smoothing", 0.15f, new ConfigDescription("Smoothing for positional tracking (0 = instant, 1 = very slow)", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); NearClipOverride = config.Bind("08. Advanced", "Near Clip Override", 0.15f, new ConfigDescription("Minimum near clip plane distance in meters. Prevents seeing through the character model during head bobbing.", (AcceptableValueBase)(object)new AcceptableValueRange(0.01f, 0.5f), Array.Empty())); } public void Reload() { ConfigFile obj = configFile; if (obj != null) { obj.Reload(); } } public void Save() { ConfigFile obj = configFile; if (obj != null) { obj.Save(); } } } public class ProfileManager { private readonly string profilesDirectory; private readonly ConfigFile mainConfig; private readonly Dictionary profiles = new Dictionary(); private ConfigProfile activeProfile; private string activeProfileName; private List cachedProfileNames; public ProfileManager(ConfigFile config) { mainConfig = config; profilesDirectory = Path.Combine(Paths.ConfigPath, "PeakHeadTracking", "Profiles"); if (!Directory.Exists(profilesDirectory)) { Directory.CreateDirectory(profilesDirectory); } LoadProfiles(); if (profiles.Count == 0) { CreateDefaultProfiles(); } string value = mainConfig.Bind("Profile", "LastActiveProfile", "Default", "Last active profile name").Value; if (profiles.ContainsKey(value)) { LoadProfile(value); } else { LoadProfile("Default"); } } private void CreateDefaultProfiles() { ConfigProfile configProfile = new ConfigProfile { Name = "Default", Description = "Default configuration for most games", CreatedDate = DateTime.Now, ModifiedDate = DateTime.Now, GameName = "General", IsDefault = true, IsReadOnly = false }; configProfile.Settings["YawSensitivity"] = 1f; configProfile.Settings["PitchSensitivity"] = 1f; configProfile.Settings["RollSensitivity"] = 1f; configProfile.Settings["Smoothing"] = 0f; profiles["Default"] = configProfile; SaveProfile(configProfile); ConfigProfile configProfile2 = new ConfigProfile { Name = "FPS_Competitive", Description = "Optimized for competitive FPS games", CreatedDate = DateTime.Now, ModifiedDate = DateTime.Now, GameName = "FPS", IsDefault = false, IsReadOnly = false }; configProfile2.Settings["YawSensitivity"] = 0.8f; configProfile2.Settings["PitchSensitivity"] = 0.8f; configProfile2.Settings["RollSensitivity"] = 0.5f; configProfile2.Settings["Smoothing"] = 0f; profiles["FPS_Competitive"] = configProfile2; SaveProfile(configProfile2); ConfigProfile configProfile3 = new ConfigProfile { Name = "Simulation", Description = "Realistic head movement for simulation games", CreatedDate = DateTime.Now, ModifiedDate = DateTime.Now, GameName = "Simulation", IsDefault = false, IsReadOnly = false }; configProfile3.Settings["YawSensitivity"] = 1.2f; configProfile3.Settings["PitchSensitivity"] = 1.2f; configProfile3.Settings["RollSensitivity"] = 1f; configProfile3.Settings["Smoothing"] = 0.1f; profiles["Simulation"] = configProfile3; SaveProfile(configProfile3); } private void LoadProfiles() { profiles.Clear(); string[] files = Directory.GetFiles(profilesDirectory, "*.profile"); foreach (string text in files) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); ConfigProfile configProfile = LoadProfileFromFile(text); if (configProfile != null) { profiles[fileNameWithoutExtension] = configProfile; } } } private ConfigProfile LoadProfileFromFile(string filePath) { string[] array = File.ReadAllLines(filePath); ConfigProfile configProfile = new ConfigProfile(); string[] array2 = array; foreach (string text in array2) { if (string.IsNullOrWhiteSpace(text) || text.StartsWith("#")) { continue; } string[] array3 = text.Split(new char[1] { '=' }); if (array3.Length != 2) { continue; } string text2 = array3[0].Trim(); string text3 = array3[1].Trim(); switch (text2) { case "Name": configProfile.Name = text3; continue; case "Description": configProfile.Description = text3; continue; case "GameName": configProfile.GameName = text3; continue; case "CreatedDate": configProfile.CreatedDate = DateTime.Parse(text3); continue; case "ModifiedDate": configProfile.ModifiedDate = DateTime.Parse(text3); continue; case "IsDefault": configProfile.IsDefault = bool.Parse(text3); continue; case "IsReadOnly": configProfile.IsReadOnly = bool.Parse(text3); continue; } if (text2.StartsWith("Setting.")) { string key = text2.Substring(8); configProfile.Settings[key] = ParseValue(text3); } } return configProfile; } private object ParseValue(string value) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (bool.TryParse(value, out var result)) { return result; } if (int.TryParse(value, out var result2)) { return result2; } if (float.TryParse(value, out var result3)) { return result3; } if (Enum.TryParse(value, out KeyCode result4)) { return result4; } return value; } public void SaveProfile(ConfigProfile profile) { if (profile.IsReadOnly) { throw new InvalidOperationException("Cannot save read-only profile: " + profile.Name); } using StreamWriter streamWriter = new StreamWriter(Path.Combine(profilesDirectory, profile.Name + ".profile")); streamWriter.WriteLine("# PeakHeadTracking Configuration Profile"); streamWriter.WriteLine($"# Generated: {DateTime.Now}"); streamWriter.WriteLine(); streamWriter.WriteLine("Name=" + profile.Name); streamWriter.WriteLine("Description=" + profile.Description); streamWriter.WriteLine("GameName=" + profile.GameName); streamWriter.WriteLine($"CreatedDate={profile.CreatedDate:yyyy-MM-dd HH:mm:ss}"); streamWriter.WriteLine($"ModifiedDate={profile.ModifiedDate:yyyy-MM-dd HH:mm:ss}"); streamWriter.WriteLine($"IsDefault={profile.IsDefault}"); streamWriter.WriteLine($"IsReadOnly={profile.IsReadOnly}"); streamWriter.WriteLine(); streamWriter.WriteLine("# Configuration Settings"); foreach (KeyValuePair setting in profile.Settings) { streamWriter.WriteLine($"Setting.{setting.Key}={setting.Value}"); } } public void LoadProfile(string profileName) { if (!profiles.ContainsKey(profileName)) { throw new KeyNotFoundException("Profile not found: " + profileName); } activeProfile = profiles[profileName]; activeProfileName = profileName; mainConfig.Bind("Profile", "LastActiveProfile", "Default", (ConfigDescription)null).Value = profileName; mainConfig.Save(); PeakHeadTrackingPlugin.Logger.LogInfo((object)("Loaded profile: " + profileName)); } public ConfigProfile CreateProfile(string name, string description, string gameName = "General") { if (profiles.ContainsKey(name)) { throw new InvalidOperationException("Profile already exists: " + name); } ConfigProfile configProfile = new ConfigProfile { Name = name, Description = description, CreatedDate = DateTime.Now, ModifiedDate = DateTime.Now, GameName = gameName, IsDefault = false, IsReadOnly = false }; profiles[name] = configProfile; cachedProfileNames = null; SaveProfile(configProfile); return configProfile; } public void DeleteProfile(string profileName) { if (!profiles.ContainsKey(profileName)) { throw new KeyNotFoundException("Profile not found: " + profileName); } ConfigProfile configProfile = profiles[profileName]; if (configProfile.IsReadOnly || configProfile.IsDefault) { throw new InvalidOperationException("Cannot delete protected profile: " + profileName); } profiles.Remove(profileName); cachedProfileNames = null; string path = Path.Combine(profilesDirectory, profileName + ".profile"); if (File.Exists(path)) { File.Delete(path); } if (activeProfileName == profileName) { LoadProfile("Default"); } } public ConfigProfile DuplicateProfile(string sourceName, string newName) { if (!profiles.ContainsKey(sourceName)) { throw new KeyNotFoundException("Source profile not found: " + sourceName); } if (profiles.ContainsKey(newName)) { throw new InvalidOperationException("Profile already exists: " + newName); } ConfigProfile configProfile = profiles[sourceName].Clone(newName); configProfile.IsReadOnly = false; configProfile.IsDefault = false; profiles[newName] = configProfile; cachedProfileNames = null; SaveProfile(configProfile); return configProfile; } public List GetProfileNames() { if (cachedProfileNames == null) { cachedProfileNames = profiles.Keys.ToList(); cachedProfileNames.Sort(StringComparer.Ordinal); } return cachedProfileNames; } public ConfigProfile GetActiveProfile() { return activeProfile; } public string GetActiveProfileName() { return activeProfileName; } public void SaveCurrentToProfile(ModConfiguration config) { if (activeProfile != null && !activeProfile.IsReadOnly) { activeProfile.ExportFromConfig(config); SaveProfile(activeProfile); } } public void ApplyProfileToConfig(ModConfiguration config) { if (activeProfile != null) { activeProfile.ImportToConfig(config); config.Save(); } } } } namespace PeakHeadTracking.Camera { [DefaultExecutionOrder(1000)] public class CameraController : MonoBehaviour { [CompilerGenerated] private sealed class d__16 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public CameraController <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__16(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown int num = <>1__state; CameraController cameraController = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } while ((Object)(object)cameraController.mainCamera == (Object)null && cameraController.cameraSearchAttempts < 10) { cameraController.FindMainCamera(); if ((Object)(object)cameraController.mainCamera == (Object)null) { cameraController.cameraSearchAttempts++; PeakHeadTrackingPlugin.Logger.LogDebug((object)$"Camera search attempt {cameraController.cameraSearchAttempts}/{10}"); <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; } } if ((Object)(object)cameraController.mainCamera == (Object)null) { PeakHeadTrackingPlugin.Logger.LogError((object)"Failed to find camera after maximum attempts"); } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private ModConfiguration config; private OpenTrackReceiver coreReceiver; private TrackingProcessor processor; private PoseInterpolator interpolator; private const int DebugLogIntervalFrames = 120; private Camera mainCamera; private Transform cameraTransform; private const float CAMERA_SEARCH_INTERVAL_SECONDS = 1f; private int cameraSearchAttempts; private const int MAX_CAMERA_SEARCH_ATTEMPTS = 10; private bool isTrackingActive; private bool isInitialized; private bool wasReceiving; public bool IsTrackingActive { get { if (isTrackingActive && coreReceiver != null) { return coreReceiver.IsReceiving; } return false; } } public void Initialize(ModConfiguration modConfig, OpenTrackReceiver trackReceiver, TrackingProcessor trackingProcessor, PoseInterpolator poseInterpolator) { config = modConfig; coreReceiver = trackReceiver; processor = trackingProcessor; interpolator = poseInterpolator; if (config.MaintainRelativePosition == null) { throw new InvalidOperationException("MaintainRelativePosition configuration is required"); } isInitialized = true; PeakHeadTrackingPlugin.Logger.LogDebug((object)"CameraController initialized"); } private void Start() { if (!isInitialized) { PeakHeadTrackingPlugin.Logger.LogWarning((object)"CameraController started without initialization"); return; } SceneManager.sceneLoaded += OnSceneLoaded; ((MonoBehaviour)this).StartCoroutine(FindCameraCoroutine()); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { PeakHeadTrackingPlugin.Logger.LogInfo((object)("Scene loaded: " + ((Scene)(ref scene)).name + " - re-finding camera")); mainCamera = null; cameraTransform = null; cameraSearchAttempts = 0; ((MonoBehaviour)this).StartCoroutine(FindCameraCoroutine()); } [IteratorStateMachine(typeof(d__16))] private IEnumerator FindCameraCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__16(0) { <>4__this = this }; } private void FindMainCamera() { mainCamera = Camera.main; if ((Object)(object)mainCamera == (Object)null) { GameObject val = GameObject.FindWithTag("MainCamera"); if ((Object)(object)val != (Object)null) { mainCamera = val.GetComponent(); } } if ((Object)(object)mainCamera == (Object)null) { Camera[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (Camera val2 in array) { if (((Behaviour)val2).enabled && ((Component)val2).gameObject.activeInHierarchy) { mainCamera = val2; break; } } } if ((Object)(object)mainCamera != (Object)null) { cameraTransform = ((Component)mainCamera).transform; PeakHeadTrackingPlugin.Logger.LogInfo((object)("Attached to camera: " + ((Object)mainCamera).name)); } } private void LateUpdate() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: 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) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) if (coreReceiver != null && isTrackingActive) { bool isReceiving = coreReceiver.IsReceiving; if (isReceiving && !wasReceiving) { RecenterView(); PeakHeadTrackingPlugin.Logger.LogInfo((object)"Auto-recentered: tracking data connected"); } wasReceiving = isReceiving; TrackingPose latestPose = coreReceiver.GetLatestPose(); TrackingPose val = interpolator.Update(latestPose, Time.deltaTime); Quat4 val2 = QuaternionUtils.FromYawPitchRoll(((TrackingPose)(ref val)).Yaw, ((TrackingPose)(ref val)).Pitch, ((TrackingPose)(ref val)).Roll); float num = default(float); float num2 = default(float); float num3 = default(float); QuaternionUtils.ToEulerYXZ(processor.CenterManager.ApplyOffsetQuat(val2), ref num, ref num2, ref num3); float num4 = num; DeadzoneSettings deadzone = processor.Deadzone; num = DeadzoneUtils.Apply(num4, ((DeadzoneSettings)(ref deadzone)).Yaw); float num5 = num2; deadzone = processor.Deadzone; num2 = DeadzoneUtils.Apply(num5, ((DeadzoneSettings)(ref deadzone)).Pitch); float num6 = num3; deadzone = processor.Deadzone; num3 = DeadzoneUtils.Apply(num6, ((DeadzoneSettings)(ref deadzone)).Roll); TrackingPose val3 = new TrackingPose(num, num2, num3, ((TrackingPose)(ref val)).TimestampTicks); TrackingPose val4 = ((TrackingPose)(ref val3)).ApplySensitivity(processor.Sensitivity); CameraPatches.SetProcessedRotation(((TrackingPose)(ref val4)).Yaw, ((TrackingPose)(ref val4)).Pitch, ((TrackingPose)(ref val4)).Roll); } if (config != null && config.DebugLogging.Value && Time.frameCount % 120 == 0) { LogDebugState(); } } [MethodImpl(MethodImplOptions.NoInlining)] private void LogDebugState() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_002b: Unknown result type (might be due to invalid IL or missing references) Vector2 headTrackingInput = CameraPatches.GetHeadTrackingInput(); ManualLogSource logger = PeakHeadTrackingPlugin.Logger; if (logger != null) { logger.LogDebug((object)$"[CameraController] isTrackingActive={isTrackingActive}, headTracking=({headTrackingInput.x:F1}, {headTrackingInput.y:F1})"); } } public void SetTrackingEnabled(bool enabled) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) isTrackingActive = enabled; CameraPatches.SetHeadTrackingEnabled(enabled); if (!enabled) { wasReceiving = false; CameraPatches.SetHeadTrackingInput(0f, 0f); if ((Object)(object)mainCamera != (Object)null) { ViewMatrixModifier.Reset(mainCamera); } } else { processor.ResetSmoothing(); interpolator.Reset(); TrackingPose latestPose = coreReceiver.GetLatestPose(); processor.RecenterTo(latestPose); } PeakHeadTrackingPlugin.Logger.LogInfo((object)("Tracking " + (enabled ? "enabled" : "disabled"))); } public void RecenterView() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) TrackingPose latestPose = coreReceiver.GetLatestPose(); processor.RecenterTo(latestPose); interpolator.Reset(); CameraPatches.RecenterPosition(); PeakHeadTrackingPlugin.Logger.LogInfo((object)"View recentered"); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; CameraPatches.UnregisterCameraCallback(); if ((Object)(object)mainCamera != (Object)null) { ViewMatrixModifier.Reset(mainCamera); } } } }