From 313f8d2eb6e4833170d685d6afd940c077e066f6 Mon Sep 17 00:00:00 2001
From: Ac_K <Acoustik666@gmail.com>
Date: Sun, 15 Nov 2020 22:30:20 +0100
Subject: [PATCH] am/lbl/hid/pctl: Enabled VR Rendering (#1688)

* am/lbl/hid/pctl: Enabled VR Rendering

This PR enable VR rendering on games which support it through the Toy-Con VR Goggles.

Please remember Ryujinx currently don't support console SixAxis sensor and for now, in some games, the view can't be moved.

Everything is implemented accordingly to RE:
- am: ICommonStateGetter: SetVrModeEnabled, BeginVrModeEx, EndVrModeEx.
- lbl: ILblController: SetBrightnessReflectionDelayLevel, GetBrightnessReflectionDelayLevel, SetCurrentAmbientLightSensorMapping, GetCurrentAmbientLightSensorMapping, SetCurrentBrightnessSettingForVrMode, GetCurrentBrightnessSettingForVrMode, EnableVrMode, DisableVrMode, IsVrModeEnabled.
- pctl: IParentalControlService: ConfirmStereoVisionPermission, ConfirmStereoVisionRestrictionConfigurable, GetStereoVisionRestriction, SetStereoVisionRestriction, ResetConfirmedStereoVisionPermission, IsStereoVisionPermitted.
- hid: IHidServer: ResetSevenSixAxisSensorTimestamp is stubbed because we don't support console SixAxisSensor for now.

Maybe we could add a setting later to enable or disable VR. But I think it's fine to keep this always available since you have to enable it in games.

* Fix permission flag check

* Address gdkchan feedback
---
 .../SystemAppletProxy/ICommonStateGetter.cs   |  68 ++++++++--
 Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs    |  11 ++
 .../HOS/Services/Lbl/ILblController.cs        |  88 ++++++++++++-
 .../HOS/Services/Lbl/LblControllerServer.cs   |  54 ++++++++
 .../IParentalControlService.cs                | 121 ++++++++++++++++++
 Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs   |   8 +-
 6 files changed, 337 insertions(+), 13 deletions(-)
 create mode 100644 Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs

diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs
index 82e18922..16b78734 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs
@@ -8,15 +8,17 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
 {
     class ICommonStateGetter : IpcService
     {
-        private Apm.ManagerServer       apmManagerServer;
-        private Apm.SystemManagerServer apmSystemManagerServer;
+        private Apm.ManagerServer       _apmManagerServer;
+        private Apm.SystemManagerServer _apmSystemManagerServer;
+        private Lbl.LblControllerServer _lblControllerServer;
 
-        private bool _vrModeEnabled = false;
+        private bool _vrModeEnabled = true;
 
         public ICommonStateGetter(ServiceCtx context)
         {
-            apmManagerServer       = new Apm.ManagerServer(context);
-            apmSystemManagerServer = new Apm.SystemManagerServer(context);
+            _apmManagerServer       = new Apm.ManagerServer(context);
+            _apmSystemManagerServer = new Apm.SystemManagerServer(context);
+            _lblControllerServer    = new Lbl.LblControllerServer(context);
         }
 
         [Command(0)]
@@ -66,7 +68,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
         // GetPerformanceMode() -> nn::apm::PerformanceMode
         public ResultCode GetPerformanceMode(ServiceCtx context)
         {
-            return (ResultCode)apmManagerServer.GetPerformanceMode(context);
+            return (ResultCode)_apmManagerServer.GetPerformanceMode(context);
         }
 
         [Command(8)]
@@ -98,6 +100,56 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
             return ResultCode.Success;
         }
 
+        [Command(51)] // 3.0.0+
+        // SetVrModeEnabled(b8)
+        public ResultCode SetVrModeEnabled(ServiceCtx context)
+        {
+            bool vrModeEnabled = context.RequestData.ReadBoolean();
+
+            UpdateVrMode(vrModeEnabled);
+
+            return ResultCode.Success;
+        }
+
+        [Command(53)] // 7.0.0+
+        // BeginVrModeEx()
+        public ResultCode BeginVrModeEx(ServiceCtx context)
+        {
+            UpdateVrMode(true);
+
+            return ResultCode.Success;
+        }
+
+        [Command(54)] // 7.0.0+
+        // EndVrModeEx()
+        public ResultCode EndVrModeEx(ServiceCtx context)
+        {
+            UpdateVrMode(false);
+
+            return ResultCode.Success;
+        }
+
+        private void UpdateVrMode(bool vrModeEnabled)
+        {
+            if (_vrModeEnabled == vrModeEnabled)
+            {
+                return;
+            }
+
+            _vrModeEnabled = vrModeEnabled;
+
+            if (vrModeEnabled)
+            {
+                _lblControllerServer.EnableVrMode();
+            }
+            else
+            {
+                _lblControllerServer.DisableVrMode();
+            }
+
+            // TODO: It signals an internal event of ICommonStateGetter. We have to determine where this event is used. 
+        }
+
         [Command(60)] // 3.0.0+
         // GetDefaultDisplayResolution() -> (u32, u32)
         public ResultCode GetDefaultDisplayResolution(ServiceCtx context)
@@ -135,7 +187,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
                 return ResultCode.InvalidParameters;
             }
 
-            apmSystemManagerServer.SetCpuBoostMode((Apm.CpuBoostMode)cpuBoostMode);
+            _apmSystemManagerServer.SetCpuBoostMode((Apm.CpuBoostMode)cpuBoostMode);
 
             // TODO: It signals an internal event of ICommonStateGetter. We have to determine where this event is used. 
 
@@ -146,7 +198,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
         // GetCurrentPerformanceConfiguration() -> nn::apm::PerformanceConfiguration
         public ResultCode GetCurrentPerformanceConfiguration(ServiceCtx context)
         {
-            return (ResultCode)apmSystemManagerServer.GetCurrentPerformanceConfiguration(context);
+            return (ResultCode)_apmSystemManagerServer.GetCurrentPerformanceConfiguration(context);
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
index 4001cfdd..d3bf9319 100644
--- a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
+++ b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
@@ -1190,6 +1190,17 @@ namespace Ryujinx.HLE.HOS.Services.Hid
             return ResultCode.Success;
         }
 
+        [Command(310)] // 6.0.0+
+        // ResetSevenSixAxisSensorTimestamp(pid, nn::applet::AppletResourceUserId)
+        public ResultCode ResetSevenSixAxisSensorTimestamp(ServiceCtx context)
+        {
+            long appletResourceUserId = context.RequestData.ReadInt64();
+
+            Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+            return ResultCode.Success;
+        }
+
         [Command(400)]
         // IsUsbFullKeyControllerEnabled() -> bool IsEnabled
         public ResultCode IsUsbFullKeyControllerEnabled(ServiceCtx context)
diff --git a/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs b/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs
index de84095e..6964de6b 100644
--- a/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs
+++ b/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs
@@ -1,8 +1,92 @@
 namespace Ryujinx.HLE.HOS.Services.Lbl
 {
-    [Service("lbl")]
-    class ILblController : IpcService
+    abstract class ILblController : IpcService
     {
         public ILblController(ServiceCtx context) { }
+
+        protected abstract void SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode);
+        protected abstract float GetCurrentBrightnessSettingForVrMode();
+        internal abstract void EnableVrMode();
+        internal abstract void DisableVrMode();
+        protected abstract bool IsVrModeEnabled();
+
+        [Command(17)]
+        // SetBrightnessReflectionDelayLevel(float, float)
+        public ResultCode SetBrightnessReflectionDelayLevel(ServiceCtx context)
+        {
+            return ResultCode.Success;
+        }
+
+        [Command(18)]
+        // GetBrightnessReflectionDelayLevel(float) -> float
+        public ResultCode GetBrightnessReflectionDelayLevel(ServiceCtx context)
+        {
+            context.ResponseData.Write(0.0f);
+
+            return ResultCode.Success;
+        }
+
+        [Command(21)]
+        // SetCurrentAmbientLightSensorMapping(unknown<0xC>)
+        public ResultCode SetCurrentAmbientLightSensorMapping(ServiceCtx context)
+        {
+            return ResultCode.Success;
+        }
+
+        [Command(22)]
+        // GetCurrentAmbientLightSensorMapping() -> unknown<0xC>
+        public ResultCode GetCurrentAmbientLightSensorMapping(ServiceCtx context)
+        {
+            return ResultCode.Success;
+        }
+
+        [Command(24)] // 3.0.0+
+        // SetCurrentBrightnessSettingForVrMode(float)
+        public ResultCode SetCurrentBrightnessSettingForVrMode(ServiceCtx context)
+        {
+            float currentBrightnessSettingForVrMode = context.RequestData.ReadSingle();
+
+            SetCurrentBrightnessSettingForVrMode(currentBrightnessSettingForVrMode);
+
+            return ResultCode.Success;
+        }
+
+        [Command(25)] // 3.0.0+
+        // GetCurrentBrightnessSettingForVrMode() -> float
+        public ResultCode GetCurrentBrightnessSettingForVrMode(ServiceCtx context)
+        {
+            float currentBrightnessSettingForVrMode = GetCurrentBrightnessSettingForVrMode();
+
+            context.ResponseData.Write(currentBrightnessSettingForVrMode);
+
+            return ResultCode.Success;
+        }
+
+        [Command(26)] // 3.0.0+
+        // EnableVrMode()
+        public ResultCode EnableVrMode(ServiceCtx context)
+        {
+            EnableVrMode();
+
+            return ResultCode.Success;
+        }
+
+        [Command(27)] // 3.0.0+
+        // DisableVrMode()
+        public ResultCode DisableVrMode(ServiceCtx context)
+        {
+            DisableVrMode();
+
+            return ResultCode.Success;
+        }
+
+        [Command(28)] // 3.0.0+
+        // IsVrModeEnabled() -> bool
+        public ResultCode IsVrModeEnabled(ServiceCtx context)
+        {
+            context.ResponseData.Write(IsVrModeEnabled());
+
+            return ResultCode.Success;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs b/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs
new file mode 100644
index 00000000..b68be1f2
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs
@@ -0,0 +1,54 @@
+namespace Ryujinx.HLE.HOS.Services.Lbl
+{
+    [Service("lbl")]
+    class LblControllerServer : ILblController
+    {
+        private bool  _vrModeEnabled;
+        private float _currentBrightnessSettingForVrMode;
+
+        public LblControllerServer(ServiceCtx context) : base(context) { }
+
+        protected override void SetCurrentBrightnessSettingForVrMode(float currentBrightnessSettingForVrMode)
+        {
+            if (float.IsNaN(currentBrightnessSettingForVrMode) || float.IsInfinity(currentBrightnessSettingForVrMode))
+            {
+                _currentBrightnessSettingForVrMode = 0.0f;
+
+                return;
+            }
+
+            _currentBrightnessSettingForVrMode = currentBrightnessSettingForVrMode;
+        }
+
+        protected override float GetCurrentBrightnessSettingForVrMode()
+        {
+            if (float.IsNaN(_currentBrightnessSettingForVrMode) || float.IsInfinity(_currentBrightnessSettingForVrMode))
+            {
+                return 0.0f;
+            }
+
+            return _currentBrightnessSettingForVrMode;
+        }
+
+        internal override void EnableVrMode()
+        {
+            _vrModeEnabled = true;
+
+            // NOTE: Service check _vrModeEnabled field value in a thread and then change the screen brightness.
+            //       Since we don't support that. It's fine to do nothing.
+        }
+
+        internal override void DisableVrMode()
+        {
+            _vrModeEnabled = false;
+
+            // NOTE: Service check _vrModeEnabled field value in a thread and then change the screen brightness.
+            //       Since we don't support that. It's fine to do nothing.
+        }
+
+        protected override bool IsVrModeEnabled()
+        {
+            return _vrModeEnabled;
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
index af468fca..0c792efa 100644
--- a/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
+++ b/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
@@ -10,6 +10,9 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
         private ulong _titleId;
         private bool  _freeCommunicationEnabled;
         private int[] _ratingAge;
+        private bool  _featuresRestriction                 = false;
+        private bool  _stereoVisionRestrictionConfigurable = true;
+        private bool  _stereoVisionRestriction             = false;
 
         public IParentalControlService(ServiceCtx context, bool withInitialize, int permissionFlag)
         {
@@ -79,5 +82,123 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
 
             return ResultCode.Success;
         }
+
+        [Command(1013)] // 4.0.0+
+        // ConfirmStereoVisionPermission()
+        public ResultCode ConfirmStereoVisionPermission(ServiceCtx context)
+        {
+            return IsStereoVisionPermittedImpl();
+        }
+
+        [Command(1061)] // 4.0.0+
+        // ConfirmStereoVisionRestrictionConfigurable()
+        public ResultCode ConfirmStereoVisionRestrictionConfigurable(ServiceCtx context)
+        {
+            if ((_permissionFlag & 2) == 0)
+            {
+                return ResultCode.PermissionDenied;
+            }
+
+            if (_stereoVisionRestrictionConfigurable)
+            {
+                return ResultCode.Success;
+            }
+            else
+            {
+                return ResultCode.StereoVisionRestrictionConfigurableDisabled;
+            }
+        }
+
+        [Command(1062)] // 4.0.0+
+        // GetStereoVisionRestriction() -> bool
+        public ResultCode GetStereoVisionRestriction(ServiceCtx context)
+        {
+            if ((_permissionFlag & 0x200) == 0)
+            {
+                return ResultCode.PermissionDenied;
+            }
+
+            bool stereoVisionRestriction = false;
+
+            if (_stereoVisionRestrictionConfigurable)
+            {
+                stereoVisionRestriction = _stereoVisionRestriction;
+            }
+
+            context.ResponseData.Write(stereoVisionRestriction);
+
+            return ResultCode.Success;
+        }
+
+        [Command(1063)] // 4.0.0+
+        // SetStereoVisionRestriction(bool)
+        public ResultCode SetStereoVisionRestriction(ServiceCtx context)
+        {
+            if ((_permissionFlag & 0x200) == 0)
+            {
+                return ResultCode.PermissionDenied;
+            }
+
+            bool stereoVisionRestriction = context.RequestData.ReadBoolean();
+
+            if (!_featuresRestriction)
+            {
+                if (_stereoVisionRestrictionConfigurable)
+                {
+                    _stereoVisionRestriction = stereoVisionRestriction;
+
+                    // TODO: It signals an internal event of service. We have to determine where this event is used. 
+                }
+            }
+
+            return ResultCode.Success;
+        }
+
+        [Command(1064)] // 5.0.0+
+        // ResetConfirmedStereoVisionPermission()
+        public ResultCode ResetConfirmedStereoVisionPermission(ServiceCtx context)
+        {
+            return ResultCode.Success;
+        }
+
+        [Command(1065)] // 5.0.0+
+        // IsStereoVisionPermitted() -> bool
+        public ResultCode IsStereoVisionPermitted(ServiceCtx context)
+        {
+            bool isStereoVisionPermitted = false;
+
+            ResultCode resultCode = IsStereoVisionPermittedImpl();
+
+            if (resultCode == ResultCode.Success)
+            {
+                isStereoVisionPermitted = true;
+            }
+
+            context.ResponseData.Write(isStereoVisionPermitted);
+
+            return resultCode;
+        }
+
+        private ResultCode IsStereoVisionPermittedImpl()
+        {
+            /*
+                // TODO: Application Exemptions are readed from file "appExemptions.dat" in the service savedata.
+                //       Since we don't support the pctl savedata for now, this can be implemented later.
+
+                if (appExemption)
+                {
+                    return ResultCode.Success;
+                }
+            */
+
+            if (_stereoVisionRestrictionConfigurable && _stereoVisionRestriction)
+            {
+                return ResultCode.StereoVisionDenied;
+            }
+            else
+            {
+                return ResultCode.Success;
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs
index bf58628c..fcf06ee9 100644
--- a/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs
+++ b/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs
@@ -7,8 +7,10 @@
 
         Success = 0,
 
-        FreeCommunicationDisabled = (101 << ErrorCodeShift) | ModuleId,
-        InvalidPid                = (131 << ErrorCodeShift) | ModuleId,
-        PermissionDenied          = (133 << ErrorCodeShift) | ModuleId
+        FreeCommunicationDisabled                   = (101 << ErrorCodeShift) | ModuleId,
+        StereoVisionDenied                          = (104 << ErrorCodeShift) | ModuleId,
+        InvalidPid                                  = (131 << ErrorCodeShift) | ModuleId,
+        PermissionDenied                            = (133 << ErrorCodeShift) | ModuleId,
+        StereoVisionRestrictionConfigurableDisabled = (181 << ErrorCodeShift) | ModuleId,
     }
 }