Client.cs 14 KB

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