/ src / Ryujinx.HLE / HOS / Applets / Controller / ControllerApplet.cs
ControllerApplet.cs
  1  using Ryujinx.Common.Logging;
  2  using Ryujinx.Common.Memory;
  3  using Ryujinx.HLE.HOS.Services.Am.AppletAE;
  4  using Ryujinx.HLE.HOS.Services.Hid;
  5  using Ryujinx.HLE.HOS.Services.Hid.Types;
  6  using System;
  7  using System.IO;
  8  using System.Runtime.CompilerServices;
  9  using System.Runtime.InteropServices;
 10  using static Ryujinx.HLE.HOS.Services.Hid.HidServer.HidUtils;
 11  
 12  namespace Ryujinx.HLE.HOS.Applets
 13  {
 14      internal class ControllerApplet : IApplet
 15      {
 16          private readonly Horizon _system;
 17  
 18          private AppletSession _normalSession;
 19  
 20          public event EventHandler AppletStateChanged;
 21  
 22          public ControllerApplet(Horizon system)
 23          {
 24              _system = system;
 25          }
 26  
 27          public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
 28          {
 29              _normalSession = normalSession;
 30  
 31              byte[] launchParams = _normalSession.Pop();
 32              byte[] controllerSupportArgPrivate = _normalSession.Pop();
 33              ControllerSupportArgPrivate privateArg = IApplet.ReadStruct<ControllerSupportArgPrivate>(controllerSupportArgPrivate);
 34  
 35              Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ArgPriv {privateArg.PrivateSize} {privateArg.ArgSize} {privateArg.Mode} " +
 36                          $"HoldType:{(NpadJoyHoldType)privateArg.NpadJoyHoldType} StyleSets:{(ControllerType)privateArg.NpadStyleSet}");
 37  
 38              if (privateArg.Mode != ControllerSupportMode.ShowControllerSupport)
 39              {
 40                  _normalSession.Push(BuildResponse()); // Dummy response for other modes
 41                  AppletStateChanged?.Invoke(this, null);
 42  
 43                  return ResultCode.Success;
 44              }
 45  
 46              byte[] controllerSupportArg = _normalSession.Pop();
 47  
 48              ControllerSupportArgHeader argHeader;
 49  
 50              if (privateArg.ArgSize == Marshal.SizeOf<ControllerSupportArgV7>())
 51              {
 52                  ControllerSupportArgV7 arg = IApplet.ReadStruct<ControllerSupportArgV7>(controllerSupportArg);
 53                  argHeader = arg.Header;
 54  
 55                  Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version 7 EnableExplainText={arg.EnableExplainText != 0}");
 56                  // Read enable text here?
 57              }
 58              else if (privateArg.ArgSize == Marshal.SizeOf<ControllerSupportArgVPre7>())
 59              {
 60                  ControllerSupportArgVPre7 arg = IApplet.ReadStruct<ControllerSupportArgVPre7>(controllerSupportArg);
 61                  argHeader = arg.Header;
 62  
 63                  Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerSupportArg Version Pre-7 EnableExplainText={arg.EnableExplainText != 0}");
 64                  // Read enable text here?
 65              }
 66              else
 67              {
 68                  Logger.Stub?.PrintStub(LogClass.ServiceHid, "ControllerSupportArg Version Unknown");
 69  
 70                  argHeader = IApplet.ReadStruct<ControllerSupportArgHeader>(controllerSupportArg); // Read just the header
 71              }
 72  
 73              int playerMin = argHeader.PlayerCountMin;
 74              int playerMax = argHeader.PlayerCountMax;
 75              bool singleMode = argHeader.EnableSingleMode != 0;
 76  
 77              Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet Arg {playerMin} {playerMax} {argHeader.EnableTakeOverConnection} {argHeader.EnableSingleMode}");
 78  
 79              if (singleMode)
 80              {
 81                  // Applications can set an arbitrary player range even with SingleMode, so clamp it
 82                  playerMin = playerMax = 1;
 83              }
 84  
 85              int configuredCount;
 86              PlayerIndex primaryIndex;
 87              while (!_system.Device.Hid.Npads.Validate(playerMin, playerMax, (ControllerType)privateArg.NpadStyleSet, out configuredCount, out primaryIndex))
 88              {
 89                  ControllerAppletUIArgs uiArgs = new()
 90                  {
 91                      PlayerCountMin = playerMin,
 92                      PlayerCountMax = playerMax,
 93                      SupportedStyles = (ControllerType)privateArg.NpadStyleSet,
 94                      SupportedPlayers = _system.Device.Hid.Npads.GetSupportedPlayers(),
 95                      IsDocked = _system.State.DockedMode,
 96                  };
 97  
 98                  if (!_system.Device.UIHandler.DisplayMessageDialog(uiArgs))
 99                  {
100                      break;
101                  }
102              }
103  
104              ControllerSupportResultInfo result = new()
105              {
106                  PlayerCount = (sbyte)configuredCount,
107                  SelectedId = (uint)GetNpadIdTypeFromIndex(primaryIndex),
108              };
109  
110              Logger.Stub?.PrintStub(LogClass.ServiceHid, $"ControllerApplet ReturnResult {result.PlayerCount} {result.SelectedId}");
111  
112              _normalSession.Push(BuildResponse(result));
113              AppletStateChanged?.Invoke(this, null);
114  
115              _system.ReturnFocus();
116  
117              return ResultCode.Success;
118          }
119  
120          public ResultCode GetResult()
121          {
122              return ResultCode.Success;
123          }
124  
125          private static byte[] BuildResponse(ControllerSupportResultInfo result)
126          {
127              using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
128              using BinaryWriter writer = new(stream);
129  
130              writer.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref result, Unsafe.SizeOf<ControllerSupportResultInfo>())));
131  
132              return stream.ToArray();
133          }
134  
135          private static byte[] BuildResponse()
136          {
137              using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
138              using BinaryWriter writer = new(stream);
139  
140              writer.Write((ulong)ResultCode.Success);
141  
142              return stream.ToArray();
143          }
144      }
145  }