Client.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. using Force.Crc32;
  2. using Ryujinx.Common;
  3. using Ryujinx.Common.Configuration.Hid;
  4. using Ryujinx.Common.Configuration.Hid.Controller;
  5. using Ryujinx.Common.Configuration.Hid.Controller.Motion;
  6. using Ryujinx.Common.Logging;
  7. using Ryujinx.Configuration;
  8. using Ryujinx.Input.Motion.CemuHook.Protocol;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.IO;
  12. using System.Net;
  13. using System.Net.Sockets;
  14. using System.Numerics;
  15. using System.Threading.Tasks;
  16. namespace Ryujinx.Input.Motion.CemuHook
  17. {
  18. public class Client : IDisposable
  19. {
  20. public const uint Magic = 0x43555344; // DSUC
  21. public const ushort Version = 1001;
  22. private bool _active;
  23. private readonly Dictionary<int, IPEndPoint> _hosts;
  24. private readonly Dictionary<int, Dictionary<int, MotionInput>> _motionData;
  25. private readonly Dictionary<int, UdpClient> _clients;
  26. private readonly bool[] _clientErrorStatus = new bool[Enum.GetValues(typeof(PlayerIndex)).Length];
  27. private readonly long[] _clientRetryTimer = new long[Enum.GetValues(typeof(PlayerIndex)).Length];
  28. public Client()
  29. {
  30. _hosts = new Dictionary<int, IPEndPoint>();
  31. _motionData = new Dictionary<int, Dictionary<int, MotionInput>>();
  32. _clients = new Dictionary<int, UdpClient>();
  33. CloseClients();
  34. }
  35. public void CloseClients()
  36. {
  37. _active = false;
  38. lock (_clients)
  39. {
  40. foreach (var client in _clients)
  41. {
  42. try
  43. {
  44. client.Value?.Dispose();
  45. }
  46. catch (SocketException socketException)
  47. {
  48. Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to dispose motion client. Error: {socketException.ErrorCode}");
  49. }
  50. }
  51. _hosts.Clear();
  52. _clients.Clear();
  53. _motionData.Clear();
  54. }
  55. }
  56. public void RegisterClient(int player, string host, int port)
  57. {
  58. if (_clients.ContainsKey(player) || !CanConnect(player))
  59. {
  60. return;
  61. }
  62. lock (_clients)
  63. {
  64. if (_clients.ContainsKey(player) || !CanConnect(player))
  65. {
  66. return;
  67. }
  68. UdpClient client = null;
  69. try
  70. {
  71. IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(host), port);
  72. client = new UdpClient(host, port);
  73. _clients.Add(player, client);
  74. _hosts.Add(player, endPoint);
  75. _active = true;
  76. Task.Run(() =>
  77. {
  78. ReceiveLoop(player);
  79. });
  80. }
  81. catch (FormatException formatException)
  82. {
  83. if (!_clientErrorStatus[player])
  84. {
  85. Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to connect to motion source at {host}:{port}. Error: {formatException.Message}");
  86. _clientErrorStatus[player] = true;
  87. }
  88. }
  89. catch (SocketException socketException)
  90. {
  91. if (!_clientErrorStatus[player])
  92. {
  93. Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to connect to motion source at {host}:{port}. Error: {socketException.ErrorCode}");
  94. _clientErrorStatus[player] = true;
  95. }
  96. RemoveClient(player);
  97. client?.Dispose();
  98. SetRetryTimer(player);
  99. }
  100. catch (Exception exception)
  101. {
  102. Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to register motion client. Error: {exception.Message}");
  103. _clientErrorStatus[player] = true;
  104. RemoveClient(player);
  105. client?.Dispose();
  106. SetRetryTimer(player);
  107. }
  108. }
  109. }
  110. public bool TryGetData(int player, int slot, out MotionInput input)
  111. {
  112. lock (_motionData)
  113. {
  114. if (_motionData.ContainsKey(player))
  115. {
  116. if (_motionData[player].TryGetValue(slot, out input))
  117. {
  118. return true;
  119. }
  120. }
  121. }
  122. input = null;
  123. return false;
  124. }
  125. private void RemoveClient(int clientId)
  126. {
  127. _clients?.Remove(clientId);
  128. _hosts?.Remove(clientId);
  129. }
  130. private void Send(byte[] data, int clientId)
  131. {
  132. if (_clients.TryGetValue(clientId, out UdpClient _client))
  133. {
  134. if (_client != null && _client.Client != null && _client.Client.Connected)
  135. {
  136. try
  137. {
  138. _client?.Send(data, data.Length);
  139. }
  140. catch (SocketException socketException)
  141. {
  142. if (!_clientErrorStatus[clientId])
  143. {
  144. Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to send data request to motion source at {_client.Client.RemoteEndPoint}. Error: {socketException.ErrorCode}");
  145. }
  146. _clientErrorStatus[clientId] = true;
  147. RemoveClient(clientId);
  148. _client?.Dispose();
  149. SetRetryTimer(clientId);
  150. }
  151. catch (ObjectDisposedException)
  152. {
  153. _clientErrorStatus[clientId] = true;
  154. RemoveClient(clientId);
  155. _client?.Dispose();
  156. SetRetryTimer(clientId);
  157. }
  158. }
  159. }
  160. }
  161. private byte[] Receive(int clientId, int timeout = 0)
  162. {
  163. if (_hosts.TryGetValue(clientId, out IPEndPoint endPoint) && _clients.TryGetValue(clientId, out UdpClient _client))
  164. {
  165. if (_client != null && _client.Client != null && _client.Client.Connected)
  166. {
  167. _client.Client.ReceiveTimeout = timeout;
  168. var result = _client?.Receive(ref endPoint);
  169. if (result.Length > 0)
  170. {
  171. _clientErrorStatus[clientId] = false;
  172. }
  173. return result;
  174. }
  175. }
  176. throw new Exception($"Client {clientId} is not registered.");
  177. }
  178. private void SetRetryTimer(int clientId)
  179. {
  180. var elapsedMs = PerformanceCounter.ElapsedMilliseconds;
  181. _clientRetryTimer[clientId] = elapsedMs;
  182. }
  183. private void ResetRetryTimer(int clientId)
  184. {
  185. _clientRetryTimer[clientId] = 0;
  186. }
  187. private bool CanConnect(int clientId)
  188. {
  189. return _clientRetryTimer[clientId] == 0 || PerformanceCounter.ElapsedMilliseconds - 5000 > _clientRetryTimer[clientId];
  190. }
  191. public void ReceiveLoop(int clientId)
  192. {
  193. if (_hosts.TryGetValue(clientId, out IPEndPoint endPoint) && _clients.TryGetValue(clientId, out UdpClient _client))
  194. {
  195. if (_client != null && _client.Client != null && _client.Client.Connected)
  196. {
  197. try
  198. {
  199. while (_active)
  200. {
  201. byte[] data = Receive(clientId);
  202. if (data.Length == 0)
  203. {
  204. continue;
  205. }
  206. Task.Run(() => HandleResponse(data, clientId));
  207. }
  208. }
  209. catch (SocketException socketException)
  210. {
  211. if (!_clientErrorStatus[clientId])
  212. {
  213. Logger.Warning?.PrintMsg(LogClass.Hid, $"Unable to receive data from motion source at {endPoint}. Error: {socketException.ErrorCode}");
  214. }
  215. _clientErrorStatus[clientId] = true;
  216. RemoveClient(clientId);
  217. _client?.Dispose();
  218. SetRetryTimer(clientId);
  219. }
  220. catch (ObjectDisposedException)
  221. {
  222. _clientErrorStatus[clientId] = true;
  223. RemoveClient(clientId);
  224. _client?.Dispose();
  225. SetRetryTimer(clientId);
  226. }
  227. }
  228. }
  229. }
  230. public void HandleResponse(byte[] data, int clientId)
  231. {
  232. ResetRetryTimer(clientId);
  233. MessageType type = (MessageType)BitConverter.ToUInt32(data.AsSpan().Slice(16, 4));
  234. data = data.AsSpan()[16..].ToArray();
  235. using MemoryStream stream = new MemoryStream(data);
  236. using BinaryReader reader = new BinaryReader(stream);
  237. switch (type)
  238. {
  239. case MessageType.Protocol:
  240. break;
  241. case MessageType.Info:
  242. ControllerInfoResponse contollerInfo = reader.ReadStruct<ControllerInfoResponse>();
  243. break;
  244. case MessageType.Data:
  245. ControllerDataResponse inputData = reader.ReadStruct<ControllerDataResponse>();
  246. Vector3 accelerometer = new Vector3()
  247. {
  248. X = -inputData.AccelerometerX,
  249. Y = inputData.AccelerometerZ,
  250. Z = -inputData.AccelerometerY
  251. };
  252. Vector3 gyroscrope = new Vector3()
  253. {
  254. X = inputData.GyroscopePitch,
  255. Y = inputData.GyroscopeRoll,
  256. Z = -inputData.GyroscopeYaw
  257. };
  258. ulong timestamp = inputData.MotionTimestamp;
  259. InputConfig config = ConfigurationState.Instance.Hid.InputConfig.Value.Find(x => x.PlayerIndex == (PlayerIndex)clientId);
  260. lock (_motionData)
  261. {
  262. // Sanity check the configuration state and remove client if needed if needed.
  263. if (config is StandardControllerInputConfig controllerConfig &&
  264. controllerConfig.Motion.EnableMotion &&
  265. controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook &&
  266. controllerConfig.Motion is CemuHookMotionConfigController cemuHookConfig)
  267. {
  268. int slot = inputData.Shared.Slot;
  269. if (_motionData.ContainsKey(clientId))
  270. {
  271. if (_motionData[clientId].ContainsKey(slot))
  272. {
  273. MotionInput previousData = _motionData[clientId][slot];
  274. previousData.Update(accelerometer, gyroscrope, timestamp, cemuHookConfig.Sensitivity, (float)cemuHookConfig.GyroDeadzone);
  275. }
  276. else
  277. {
  278. MotionInput input = new MotionInput();
  279. input.Update(accelerometer, gyroscrope, timestamp, cemuHookConfig.Sensitivity, (float)cemuHookConfig.GyroDeadzone);
  280. _motionData[clientId].Add(slot, input);
  281. }
  282. }
  283. else
  284. {
  285. MotionInput input = new MotionInput();
  286. input.Update(accelerometer, gyroscrope, timestamp, cemuHookConfig.Sensitivity, (float)cemuHookConfig.GyroDeadzone);
  287. _motionData.Add(clientId, new Dictionary<int, MotionInput>() { { slot, input } });
  288. }
  289. }
  290. else
  291. {
  292. RemoveClient(clientId);
  293. }
  294. }
  295. break;
  296. }
  297. }
  298. public void RequestInfo(int clientId, int slot)
  299. {
  300. if (!_active)
  301. {
  302. return;
  303. }
  304. Header header = GenerateHeader(clientId);
  305. using (MemoryStream stream = new MemoryStream())
  306. using (BinaryWriter writer = new BinaryWriter(stream))
  307. {
  308. writer.WriteStruct(header);
  309. ControllerInfoRequest request = new ControllerInfoRequest()
  310. {
  311. Type = MessageType.Info,
  312. PortsCount = 4
  313. };
  314. request.PortIndices[0] = (byte)slot;
  315. writer.WriteStruct(request);
  316. header.Length = (ushort)(stream.Length - 16);
  317. writer.Seek(6, SeekOrigin.Begin);
  318. writer.Write(header.Length);
  319. header.Crc32 = Crc32Algorithm.Compute(stream.ToArray());
  320. writer.Seek(8, SeekOrigin.Begin);
  321. writer.Write(header.Crc32);
  322. byte[] data = stream.ToArray();
  323. Send(data, clientId);
  324. }
  325. }
  326. public unsafe void RequestData(int clientId, int slot)
  327. {
  328. if (!_active)
  329. {
  330. return;
  331. }
  332. Header header = GenerateHeader(clientId);
  333. using (MemoryStream stream = new MemoryStream())
  334. using (BinaryWriter writer = new BinaryWriter(stream))
  335. {
  336. writer.WriteStruct(header);
  337. ControllerDataRequest request = new ControllerDataRequest()
  338. {
  339. Type = MessageType.Data,
  340. Slot = (byte)slot,
  341. SubscriberType = SubscriberType.Slot
  342. };
  343. writer.WriteStruct(request);
  344. header.Length = (ushort)(stream.Length - 16);
  345. writer.Seek(6, SeekOrigin.Begin);
  346. writer.Write(header.Length);
  347. header.Crc32 = Crc32Algorithm.Compute(stream.ToArray());
  348. writer.Seek(8, SeekOrigin.Begin);
  349. writer.Write(header.Crc32);
  350. byte[] data = stream.ToArray();
  351. Send(data, clientId);
  352. }
  353. }
  354. private Header GenerateHeader(int clientId)
  355. {
  356. Header header = new Header()
  357. {
  358. Id = (uint)clientId,
  359. MagicString = Magic,
  360. Version = Version,
  361. Length = 0,
  362. Crc32 = 0
  363. };
  364. return header;
  365. }
  366. public void Dispose()
  367. {
  368. _active = false;
  369. CloseClients();
  370. }
  371. }
  372. }