// Copyright HTC Corporation All Rights Reserved. using System.Runtime.InteropServices; using System; using UnityEngine; using AOT; using System.Collections.Generic; using System.Text; namespace VIVE.OpenXR { /// /// This class is made for all features that need to intercept OpenXR API calls. /// Some APIs will be called by Unity internally, and we need to intercept them in c# to get some information. /// Append more interceptable functions for this class by adding a new partial class. /// The partial class can help the delegate name be nice to read and search. /// Please create per function in one partial class. /// /// For all features want to use this class, please call in your feature class. /// For example: /// protected override IntPtr HookGetInstanceProcAddr(IntPtr func) /// { /// return ViveInterceptors.Instance.HookGetInstanceProcAddr(func); /// } /// partial class ViveInterceptors { public const string TAG = "VIVE.OpenXR.ViveInterceptors"; static StringBuilder m_sb = null; static StringBuilder sb { get { if (m_sb == null) { m_sb = new StringBuilder(); } return m_sb; } } static void DEBUG(StringBuilder msg) { Debug.LogFormat("{0} {1}", TAG, msg); } static void ERROR(StringBuilder msg) { Debug.LogErrorFormat("{0} {1}", TAG, msg); } public static ViveInterceptors instance = null; public static ViveInterceptors Instance { get { if (instance == null) instance = new ViveInterceptors(); return instance; } } public ViveInterceptors() { Debug.Log("ViveInterceptors"); } public delegate XrResult DelegateXrGetInstanceProcAddr(XrInstance instance, string name, out IntPtr function); private static readonly DelegateXrGetInstanceProcAddr hookXrGetInstanceProcAddrHandle = new DelegateXrGetInstanceProcAddr(XrGetInstanceProcAddrInterceptor); private static readonly IntPtr hookGetInstanceProcAddrHandlePtr = Marshal.GetFunctionPointerForDelegate(hookXrGetInstanceProcAddrHandle); static DelegateXrGetInstanceProcAddr XrGetInstanceProcAddrOriginal = null; [MonoPInvokeCallback(typeof(DelegateXrGetInstanceProcAddr))] private static XrResult XrGetInstanceProcAddrInterceptor(XrInstance instance, string name, out IntPtr function) { // Used to check if the original function is already hooked. if (instance == 0 && name == "ViveInterceptorHooked") { function = IntPtr.Zero; return XrResult.XR_SUCCESS; } // Custom interceptors if (name == "xrWaitFrame" && requiredFunctions.Contains(name)) { Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted."); var ret = XrGetInstanceProcAddrOriginal(instance, name, out function); if (ret == XrResult.XR_SUCCESS) { XrWaitFrameOriginal = Marshal.GetDelegateForFunctionPointer(function); function = xrWaitFrameInterceptorPtr; } return ret; } if (name == "xrEndFrame" && requiredFunctions.Contains(name)) { Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted."); var ret = XrGetInstanceProcAddrOriginal(instance, name, out function); if (ret == XrResult.XR_SUCCESS) { XrEndFrameOriginal = Marshal.GetDelegateForFunctionPointer(function); function = xrEndFrameInterceptorPtr; } return ret; } #if PERFORMANCE_TEST if (name == "xrLocateSpace" && requiredFunctions.Contains(name)) { Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted."); var ret = XrGetInstanceProcAddrOriginal(instance, name, out function); if (ret == XrResult.XR_SUCCESS) { XrLocateSpaceOriginal = Marshal.GetDelegateForFunctionPointer(function); function = xrLocateSpaceInterceptorPtr; } return ret; } #endif if (name == "xrPollEvent" && requiredFunctions.Contains(name)) { Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted."); var ret = XrGetInstanceProcAddrOriginal(instance, name, out function); if (ret == XrResult.XR_SUCCESS) { xrPollEventOrigin = Marshal.GetDelegateForFunctionPointer < xrPollEventDelegate > (function); function = xrPollEventPtr; } return ret; } if (name == "xrBeginSession" && requiredFunctions.Contains(name)) { Debug.Log($"{TAG}: XrGetInstanceProcAddrInterceptor() {name} is intercepted."); var ret = XrGetInstanceProcAddrOriginal(instance, name, out function); if (ret == XrResult.XR_SUCCESS) { xrBeginSessionOrigin = Marshal.GetDelegateForFunctionPointer(function); function = xrBeginSessionPtr; } return ret; } return XrGetInstanceProcAddrOriginal(instance, name, out function); } public IntPtr HookGetInstanceProcAddr(IntPtr func) { Debug.Log($"{TAG}: HookGetInstanceProcAddr"); if (XrGetInstanceProcAddrOriginal == null) { Debug.Log($"{TAG}: registering our own xrGetInstanceProcAddr"); XrGetInstanceProcAddrOriginal = Marshal.GetDelegateForFunctionPointer(func); #if UNITY_EDITOR if (Application.isEditor) { // This is a trick to check if the original function is already hooked by this class. Sometimes, the static XrGetInstanceProcAddrOriginal didn't work as expected. Debug.Log($"{TAG}: Check if duplicate hooked by this script with instance=0 and \"ViveInterceptorHooked\" name. If following a loader error, ignore it."); // E OpenXR-Loader: Error [SPEC | xrGetInstanceProcAddr | VUID-xrGetInstanceProcAddr-instance-parameter] : XR_NULL_HANDLE for instance but query for ViveInterceptorHooked requires a valid instance // Call XrGetInstanceProcAddrOriginal to check if the original function is already hooked by this class if (XrGetInstanceProcAddrOriginal(0, "ViveInterceptorHooked", out IntPtr function) == XrResult.XR_SUCCESS) { // If it is called successfully, it means the original function is already hooked. So we should return the original function. Debug.Log($"{TAG}: Already hooked"); return func; } } #endif return hookGetInstanceProcAddrHandlePtr; } else { // Dont return hookGetInstanceProcAddrHandlePtr again. // If this hook function is called by multiple features, it should only work at the first time. // If called by other features, it should return the original function. return func; } } static readonly List requiredFunctions = new List(); /// /// Call before to add required functions."/> /// /// public void AddRequiredFunction(string name) { if (requiredFunctions.Contains(name)) return; Debug.Log($"{TAG}: AddRequiredFunction({name})"); requiredFunctions.Add(name); } } }