IUserLocalCommunicationService.cs 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154
  1. using LibHac.Ns;
  2. using Ryujinx.Common;
  3. using Ryujinx.Common.Configuration.Multiplayer;
  4. using Ryujinx.Common.Logging;
  5. using Ryujinx.Common.Memory;
  6. using Ryujinx.Common.Utilities;
  7. using Ryujinx.Cpu;
  8. using Ryujinx.HLE.HOS.Ipc;
  9. using Ryujinx.HLE.HOS.Kernel.Threading;
  10. using Ryujinx.HLE.HOS.Services.Ldn.Types;
  11. using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm;
  12. using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnRyu;
  13. using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
  14. using Ryujinx.Horizon.Common;
  15. using Ryujinx.Memory;
  16. using System;
  17. using System.IO;
  18. using System.Net;
  19. using System.Net.NetworkInformation;
  20. using System.Runtime.InteropServices;
  21. namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
  22. {
  23. class IUserLocalCommunicationService : IpcService, IDisposable
  24. {
  25. public static string DefaultLanPlayHost = "ryuldn.vudjun.com";
  26. public static short LanPlayPort = 30456;
  27. public INetworkClient NetworkClient { get; private set; }
  28. private const int NifmRequestID = 90;
  29. private const string DefaultIPAddress = "127.0.0.1";
  30. private const string DefaultSubnetMask = "255.255.255.0";
  31. private const bool IsDevelopment = false;
  32. private readonly KEvent _stateChangeEvent;
  33. private int _stateChangeEventHandle;
  34. private NetworkState _state;
  35. private DisconnectReason _disconnectReason;
  36. private ResultCode _nifmResultCode;
  37. private AccessPoint _accessPoint;
  38. private Station _station;
  39. public IUserLocalCommunicationService(ServiceCtx context)
  40. {
  41. _stateChangeEvent = new KEvent(context.Device.System.KernelContext);
  42. _state = NetworkState.None;
  43. _disconnectReason = DisconnectReason.None;
  44. }
  45. private ushort CheckDevelopmentChannel(ushort channel)
  46. {
  47. return (ushort)(!IsDevelopment ? 0 : channel);
  48. }
  49. private SecurityMode CheckDevelopmentSecurityMode(SecurityMode securityMode)
  50. {
  51. return !IsDevelopment ? SecurityMode.Retail : securityMode;
  52. }
  53. private bool CheckLocalCommunicationIdPermission(ServiceCtx context, ulong localCommunicationIdChecked)
  54. {
  55. // TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
  56. ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
  57. foreach (var localCommunicationId in controlProperty.LocalCommunicationId.ItemsRo)
  58. {
  59. if (localCommunicationId == localCommunicationIdChecked)
  60. {
  61. return true;
  62. }
  63. }
  64. return false;
  65. }
  66. [CommandCmif(0)]
  67. // GetState() -> s32 state
  68. public ResultCode GetState(ServiceCtx context)
  69. {
  70. if (_nifmResultCode != ResultCode.Success)
  71. {
  72. context.ResponseData.Write((int)NetworkState.Error);
  73. return ResultCode.Success;
  74. }
  75. // NOTE: Returns ResultCode.InvalidArgument if _state is null, doesn't occur in our case.
  76. context.ResponseData.Write((int)_state);
  77. return ResultCode.Success;
  78. }
  79. public void SetState()
  80. {
  81. _stateChangeEvent.WritableEvent.Signal();
  82. }
  83. public void SetState(NetworkState state)
  84. {
  85. _state = state;
  86. SetState();
  87. }
  88. [CommandCmif(1)]
  89. // GetNetworkInfo() -> buffer<network_info<0x480>, 0x1a>
  90. public ResultCode GetNetworkInfo(ServiceCtx context)
  91. {
  92. ulong bufferPosition = context.Request.RecvListBuff[0].Position;
  93. MemoryHelper.FillWithZeros(context.Memory, bufferPosition, 0x480);
  94. if (_nifmResultCode != ResultCode.Success)
  95. {
  96. return _nifmResultCode;
  97. }
  98. ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
  99. if (resultCode != ResultCode.Success)
  100. {
  101. return resultCode;
  102. }
  103. ulong infoSize = MemoryHelper.Write(context.Memory, bufferPosition, networkInfo);
  104. context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(infoSize);
  105. return ResultCode.Success;
  106. }
  107. private ResultCode GetNetworkInfoImpl(out NetworkInfo networkInfo)
  108. {
  109. if (_state == NetworkState.StationConnected)
  110. {
  111. networkInfo = _station.NetworkInfo;
  112. }
  113. else if (_state == NetworkState.AccessPointCreated)
  114. {
  115. networkInfo = _accessPoint.NetworkInfo;
  116. }
  117. else
  118. {
  119. networkInfo = new NetworkInfo();
  120. return ResultCode.InvalidState;
  121. }
  122. return ResultCode.Success;
  123. }
  124. private NodeLatestUpdate[] GetNodeLatestUpdateImpl(int count)
  125. {
  126. if (_state == NetworkState.StationConnected)
  127. {
  128. return _station.LatestUpdates.ConsumeLatestUpdate(count);
  129. }
  130. else if (_state == NetworkState.AccessPointCreated)
  131. {
  132. return _accessPoint.LatestUpdates.ConsumeLatestUpdate(count);
  133. }
  134. else
  135. {
  136. return Array.Empty<NodeLatestUpdate>();
  137. }
  138. }
  139. [CommandCmif(2)]
  140. // GetIpv4Address() -> (u32 ip_address, u32 subnet_mask)
  141. public ResultCode GetIpv4Address(ServiceCtx context)
  142. {
  143. if (_nifmResultCode != ResultCode.Success)
  144. {
  145. return _nifmResultCode;
  146. }
  147. // NOTE: Return ResultCode.InvalidArgument if ip_address and subnet_mask are null, doesn't occur in our case.
  148. if (_state == NetworkState.AccessPointCreated || _state == NetworkState.StationConnected)
  149. {
  150. ProxyConfig config = _state switch
  151. {
  152. NetworkState.AccessPointCreated => _accessPoint.Config,
  153. NetworkState.StationConnected => _station.Config,
  154. _ => default
  155. };
  156. if (config.ProxyIp == 0)
  157. {
  158. (_, UnicastIPAddressInformation unicastAddress) = NetworkHelpers.GetLocalInterface(context.Device.Configuration.MultiplayerLanInterfaceId);
  159. if (unicastAddress == null)
  160. {
  161. context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultIPAddress));
  162. context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(DefaultSubnetMask));
  163. }
  164. else
  165. {
  166. Logger.Info?.Print(LogClass.ServiceLdn, $"Console's LDN IP is \"{unicastAddress.Address}\".");
  167. context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.Address));
  168. context.ResponseData.Write(NetworkHelpers.ConvertIpv4Address(unicastAddress.IPv4Mask));
  169. }
  170. }
  171. else
  172. {
  173. Logger.Info?.Print(LogClass.ServiceLdn, $"LDN obtained proxy IP.");
  174. context.ResponseData.Write(config.ProxyIp);
  175. context.ResponseData.Write(config.ProxySubnetMask);
  176. }
  177. }
  178. else
  179. {
  180. return ResultCode.InvalidArgument;
  181. }
  182. return ResultCode.Success;
  183. }
  184. [CommandCmif(3)]
  185. // GetDisconnectReason() -> u16 disconnect_reason
  186. public ResultCode GetDisconnectReason(ServiceCtx context)
  187. {
  188. // NOTE: Returns ResultCode.InvalidArgument if _disconnectReason is null, doesn't occur in our case.
  189. context.ResponseData.Write((short)_disconnectReason);
  190. return ResultCode.Success;
  191. }
  192. public void SetDisconnectReason(DisconnectReason reason)
  193. {
  194. if (_state != NetworkState.Initialized)
  195. {
  196. _disconnectReason = reason;
  197. SetState(NetworkState.Initialized);
  198. }
  199. }
  200. [CommandCmif(4)]
  201. // GetSecurityParameter() -> bytes<0x20, 1> security_parameter
  202. public ResultCode GetSecurityParameter(ServiceCtx context)
  203. {
  204. if (_nifmResultCode != ResultCode.Success)
  205. {
  206. return _nifmResultCode;
  207. }
  208. ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
  209. if (resultCode != ResultCode.Success)
  210. {
  211. return resultCode;
  212. }
  213. SecurityParameter securityParameter = new()
  214. {
  215. Data = new Array16<byte>(),
  216. SessionId = networkInfo.NetworkId.SessionId,
  217. };
  218. context.ResponseData.WriteStruct(securityParameter);
  219. return ResultCode.Success;
  220. }
  221. [CommandCmif(5)]
  222. // GetNetworkConfig() -> bytes<0x20, 8> network_config
  223. public ResultCode GetNetworkConfig(ServiceCtx context)
  224. {
  225. if (_nifmResultCode != ResultCode.Success)
  226. {
  227. return _nifmResultCode;
  228. }
  229. ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
  230. if (resultCode != ResultCode.Success)
  231. {
  232. return resultCode;
  233. }
  234. NetworkConfig networkConfig = new()
  235. {
  236. IntentId = networkInfo.NetworkId.IntentId,
  237. Channel = networkInfo.Common.Channel,
  238. NodeCountMax = networkInfo.Ldn.NodeCountMax,
  239. LocalCommunicationVersion = networkInfo.Ldn.Nodes[0].LocalCommunicationVersion,
  240. Reserved2 = new Array10<byte>(),
  241. };
  242. context.ResponseData.WriteStruct(networkConfig);
  243. return ResultCode.Success;
  244. }
  245. [CommandCmif(100)]
  246. // AttachStateChangeEvent() -> handle<copy>
  247. public ResultCode AttachStateChangeEvent(ServiceCtx context)
  248. {
  249. if (_stateChangeEventHandle == 0 && context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != Result.Success)
  250. {
  251. throw new InvalidOperationException("Out of handles!");
  252. }
  253. context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle);
  254. // Returns ResultCode.InvalidArgument if handle is null, doesn't occur in our case since we already throw an Exception.
  255. return ResultCode.Success;
  256. }
  257. [CommandCmif(101)]
  258. // GetNetworkInfoLatestUpdate() -> (buffer<network_info<0x480>, 0x1a>, buffer<node_latest_update, 0xa>)
  259. public ResultCode GetNetworkInfoLatestUpdate(ServiceCtx context)
  260. {
  261. ulong bufferPosition = context.Request.RecvListBuff[0].Position;
  262. MemoryHelper.FillWithZeros(context.Memory, bufferPosition, 0x480);
  263. if (_nifmResultCode != ResultCode.Success)
  264. {
  265. return _nifmResultCode;
  266. }
  267. ResultCode resultCode = GetNetworkInfoImpl(out NetworkInfo networkInfo);
  268. if (resultCode != ResultCode.Success)
  269. {
  270. return resultCode;
  271. }
  272. ulong outputPosition = context.Request.RecvListBuff[0].Position;
  273. ulong outputSize = context.Request.RecvListBuff[0].Size;
  274. ulong latestUpdateSize = (ulong)Marshal.SizeOf<NodeLatestUpdate>();
  275. int count = (int)(outputSize / latestUpdateSize);
  276. NodeLatestUpdate[] latestUpdate = GetNodeLatestUpdateImpl(count);
  277. MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
  278. foreach (NodeLatestUpdate node in latestUpdate)
  279. {
  280. MemoryHelper.Write(context.Memory, outputPosition, node);
  281. outputPosition += latestUpdateSize;
  282. }
  283. ulong infoSize = MemoryHelper.Write(context.Memory, bufferPosition, networkInfo);
  284. context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(infoSize);
  285. return ResultCode.Success;
  286. }
  287. [CommandCmif(102)]
  288. // Scan(u16 channel, bytes<0x60, 8> scan_filter) -> (u16 count, buffer<network_info, 0x22>)
  289. public ResultCode Scan(ServiceCtx context)
  290. {
  291. return ScanImpl(context);
  292. }
  293. [CommandCmif(103)]
  294. // ScanPrivate(u16 channel, bytes<0x60, 8> scan_filter) -> (u16 count, buffer<network_info, 0x22>)
  295. public ResultCode ScanPrivate(ServiceCtx context)
  296. {
  297. return ScanImpl(context, true);
  298. }
  299. private ResultCode ScanImpl(ServiceCtx context, bool isPrivate = false)
  300. {
  301. ushort channel = (ushort)context.RequestData.ReadUInt64();
  302. ScanFilter scanFilter = context.RequestData.ReadStruct<ScanFilter>();
  303. (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x22(0);
  304. if (_nifmResultCode != ResultCode.Success)
  305. {
  306. return _nifmResultCode;
  307. }
  308. if (!isPrivate)
  309. {
  310. channel = CheckDevelopmentChannel(channel);
  311. }
  312. ResultCode resultCode = ResultCode.InvalidArgument;
  313. if (bufferSize != 0)
  314. {
  315. if (bufferPosition != 0)
  316. {
  317. ScanFilterFlag scanFilterFlag = scanFilter.Flag;
  318. if (!scanFilterFlag.HasFlag(ScanFilterFlag.NetworkType) || scanFilter.NetworkType <= NetworkType.All)
  319. {
  320. if (scanFilterFlag.HasFlag(ScanFilterFlag.Ssid))
  321. {
  322. if (scanFilter.Ssid.Length <= 31)
  323. {
  324. return resultCode;
  325. }
  326. }
  327. if (!scanFilterFlag.HasFlag(ScanFilterFlag.MacAddress))
  328. {
  329. if (scanFilterFlag > ScanFilterFlag.All)
  330. {
  331. return resultCode;
  332. }
  333. if (_state - 3 >= NetworkState.AccessPoint)
  334. {
  335. resultCode = ResultCode.InvalidState;
  336. }
  337. else
  338. {
  339. if (scanFilter.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
  340. {
  341. // TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
  342. ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
  343. scanFilter.NetworkId.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0];
  344. }
  345. resultCode = ScanInternal(context.Memory, channel, scanFilter, bufferPosition, bufferSize, out ulong counter);
  346. context.ResponseData.Write(counter);
  347. }
  348. }
  349. else
  350. {
  351. throw new NotSupportedException();
  352. }
  353. }
  354. }
  355. }
  356. return resultCode;
  357. }
  358. private ResultCode ScanInternal(IVirtualMemoryManager memory, ushort channel, ScanFilter scanFilter, ulong bufferPosition, ulong bufferSize, out ulong counter)
  359. {
  360. ulong networkInfoSize = (ulong)Marshal.SizeOf(typeof(NetworkInfo));
  361. ulong maxGames = bufferSize / networkInfoSize;
  362. MemoryHelper.FillWithZeros(memory, bufferPosition, (int)bufferSize);
  363. NetworkInfo[] availableGames = NetworkClient.Scan(channel, scanFilter);
  364. counter = 0;
  365. foreach (NetworkInfo networkInfo in availableGames)
  366. {
  367. MemoryHelper.Write(memory, bufferPosition + (networkInfoSize * counter), networkInfo);
  368. if (++counter >= maxGames)
  369. {
  370. break;
  371. }
  372. }
  373. return ResultCode.Success;
  374. }
  375. [CommandCmif(104)] // 5.0.0+
  376. // SetWirelessControllerRestriction(u32 wireless_controller_restriction)
  377. public ResultCode SetWirelessControllerRestriction(ServiceCtx context)
  378. {
  379. // NOTE: Return ResultCode.InvalidArgument if an internal IPAddress is null, doesn't occur in our case.
  380. uint wirelessControllerRestriction = context.RequestData.ReadUInt32();
  381. if (wirelessControllerRestriction > 1)
  382. {
  383. return ResultCode.InvalidArgument;
  384. }
  385. if (_state != NetworkState.Initialized)
  386. {
  387. return ResultCode.InvalidState;
  388. }
  389. // NOTE: WirelessControllerRestriction value is used for the btm service in SetWlanMode call.
  390. // Since we use our own implementation we can do nothing here.
  391. return ResultCode.Success;
  392. }
  393. [CommandCmif(200)]
  394. // OpenAccessPoint()
  395. public ResultCode OpenAccessPoint(ServiceCtx context)
  396. {
  397. if (_nifmResultCode != ResultCode.Success)
  398. {
  399. return _nifmResultCode;
  400. }
  401. if (_state != NetworkState.Initialized)
  402. {
  403. return ResultCode.InvalidState;
  404. }
  405. CloseStation();
  406. SetState(NetworkState.AccessPoint);
  407. _accessPoint = new AccessPoint(this);
  408. // NOTE: Calls nifm service and return related result codes.
  409. // Since we use our own implementation we can return ResultCode.Success.
  410. return ResultCode.Success;
  411. }
  412. [CommandCmif(201)]
  413. // CloseAccessPoint()
  414. public ResultCode CloseAccessPoint(ServiceCtx context)
  415. {
  416. if (_nifmResultCode != ResultCode.Success)
  417. {
  418. return _nifmResultCode;
  419. }
  420. if (_state == NetworkState.AccessPoint || _state == NetworkState.AccessPointCreated)
  421. {
  422. DestroyNetworkImpl(DisconnectReason.DestroyedByUser);
  423. }
  424. else
  425. {
  426. return ResultCode.InvalidState;
  427. }
  428. SetState(NetworkState.Initialized);
  429. return ResultCode.Success;
  430. }
  431. private void CloseAccessPoint()
  432. {
  433. _accessPoint?.Dispose();
  434. _accessPoint = null;
  435. }
  436. [CommandCmif(202)]
  437. // CreateNetwork(bytes<0x44, 2> security_config, bytes<0x30, 1> user_config, bytes<0x20, 8> network_config)
  438. public ResultCode CreateNetwork(ServiceCtx context)
  439. {
  440. return CreateNetworkImpl(context);
  441. }
  442. [CommandCmif(203)]
  443. // CreateNetworkPrivate(bytes<0x44, 2> security_config, bytes<0x20, 1> security_parameter, bytes<0x30, 1>, bytes<0x20, 8> network_config, buffer<unknown, 9> address_entry, int count)
  444. public ResultCode CreateNetworkPrivate(ServiceCtx context)
  445. {
  446. return CreateNetworkImpl(context, true);
  447. }
  448. public ResultCode CreateNetworkImpl(ServiceCtx context, bool isPrivate = false)
  449. {
  450. SecurityConfig securityConfig = context.RequestData.ReadStruct<SecurityConfig>();
  451. SecurityParameter securityParameter = isPrivate ? context.RequestData.ReadStruct<SecurityParameter>() : new SecurityParameter();
  452. UserConfig userConfig = context.RequestData.ReadStruct<UserConfig>();
  453. context.RequestData.BaseStream.Seek(4, SeekOrigin.Current); // Alignment?
  454. NetworkConfig networkConfig = context.RequestData.ReadStruct<NetworkConfig>();
  455. if (networkConfig.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
  456. {
  457. // TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
  458. ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
  459. networkConfig.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0];
  460. }
  461. bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId);
  462. if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
  463. {
  464. return ResultCode.InvalidObject;
  465. }
  466. if (_nifmResultCode != ResultCode.Success)
  467. {
  468. return _nifmResultCode;
  469. }
  470. networkConfig.Channel = CheckDevelopmentChannel(networkConfig.Channel);
  471. securityConfig.SecurityMode = CheckDevelopmentSecurityMode(securityConfig.SecurityMode);
  472. if (networkConfig.NodeCountMax <= LdnConst.NodeCountMax)
  473. {
  474. if ((((ulong)networkConfig.LocalCommunicationVersion) & 0x80000000) == 0)
  475. {
  476. if (securityConfig.SecurityMode <= SecurityMode.Retail)
  477. {
  478. if (securityConfig.Passphrase.Length <= LdnConst.PassphraseLengthMax)
  479. {
  480. if (_state == NetworkState.AccessPoint)
  481. {
  482. if (isPrivate)
  483. {
  484. ulong bufferPosition = context.Request.PtrBuff[0].Position;
  485. ulong bufferSize = context.Request.PtrBuff[0].Size;
  486. byte[] addressListBytes = new byte[bufferSize];
  487. context.Memory.Read(bufferPosition, addressListBytes);
  488. AddressList addressList = MemoryMarshal.Cast<byte, AddressList>(addressListBytes)[0];
  489. _accessPoint.CreateNetworkPrivate(securityConfig, securityParameter, userConfig, networkConfig, addressList);
  490. }
  491. else
  492. {
  493. _accessPoint.CreateNetwork(securityConfig, userConfig, networkConfig);
  494. }
  495. return ResultCode.Success;
  496. }
  497. else
  498. {
  499. return ResultCode.InvalidState;
  500. }
  501. }
  502. }
  503. }
  504. }
  505. return ResultCode.InvalidArgument;
  506. }
  507. [CommandCmif(204)]
  508. // DestroyNetwork()
  509. public ResultCode DestroyNetwork(ServiceCtx context)
  510. {
  511. return DestroyNetworkImpl(DisconnectReason.DestroyedByUser);
  512. }
  513. private ResultCode DestroyNetworkImpl(DisconnectReason disconnectReason)
  514. {
  515. if (_nifmResultCode != ResultCode.Success)
  516. {
  517. return _nifmResultCode;
  518. }
  519. if (disconnectReason - 3 <= DisconnectReason.DisconnectedByUser)
  520. {
  521. if (_state == NetworkState.AccessPointCreated)
  522. {
  523. CloseAccessPoint();
  524. SetState(NetworkState.AccessPoint);
  525. return ResultCode.Success;
  526. }
  527. CloseAccessPoint();
  528. return ResultCode.InvalidState;
  529. }
  530. return ResultCode.InvalidArgument;
  531. }
  532. [CommandCmif(205)]
  533. // Reject(u32 node_id)
  534. public ResultCode Reject(ServiceCtx context)
  535. {
  536. uint nodeId = context.RequestData.ReadUInt32();
  537. return RejectImpl(DisconnectReason.Rejected, nodeId);
  538. }
  539. private ResultCode RejectImpl(DisconnectReason disconnectReason, uint nodeId)
  540. {
  541. if (_nifmResultCode != ResultCode.Success)
  542. {
  543. return _nifmResultCode;
  544. }
  545. if (_state != NetworkState.AccessPointCreated)
  546. {
  547. return ResultCode.InvalidState; // Must be network host to reject nodes.
  548. }
  549. return NetworkClient.Reject(disconnectReason, nodeId);
  550. }
  551. [CommandCmif(206)]
  552. // SetAdvertiseData(buffer<advertise_data, 0x21>)
  553. public ResultCode SetAdvertiseData(ServiceCtx context)
  554. {
  555. (ulong bufferPosition, ulong bufferSize) = context.Request.GetBufferType0x21(0);
  556. if (_nifmResultCode != ResultCode.Success)
  557. {
  558. return _nifmResultCode;
  559. }
  560. if (bufferSize == 0 || bufferSize > LdnConst.AdvertiseDataSizeMax)
  561. {
  562. return ResultCode.InvalidArgument;
  563. }
  564. if (_state == NetworkState.AccessPoint || _state == NetworkState.AccessPointCreated)
  565. {
  566. byte[] advertiseData = new byte[bufferSize];
  567. context.Memory.Read(bufferPosition, advertiseData);
  568. return _accessPoint.SetAdvertiseData(advertiseData);
  569. }
  570. else
  571. {
  572. return ResultCode.InvalidState;
  573. }
  574. }
  575. [CommandCmif(207)]
  576. // SetStationAcceptPolicy(u8 accept_policy)
  577. public ResultCode SetStationAcceptPolicy(ServiceCtx context)
  578. {
  579. AcceptPolicy acceptPolicy = (AcceptPolicy)context.RequestData.ReadByte();
  580. if (_nifmResultCode != ResultCode.Success)
  581. {
  582. return _nifmResultCode;
  583. }
  584. if (acceptPolicy > AcceptPolicy.WhiteList)
  585. {
  586. return ResultCode.InvalidArgument;
  587. }
  588. if (_state == NetworkState.AccessPoint || _state == NetworkState.AccessPointCreated)
  589. {
  590. return _accessPoint.SetStationAcceptPolicy(acceptPolicy);
  591. }
  592. else
  593. {
  594. return ResultCode.InvalidState;
  595. }
  596. }
  597. [CommandCmif(208)]
  598. // AddAcceptFilterEntry(bytes<6, 1> mac_address)
  599. public ResultCode AddAcceptFilterEntry(ServiceCtx context)
  600. {
  601. if (_nifmResultCode != ResultCode.Success)
  602. {
  603. return _nifmResultCode;
  604. }
  605. // TODO
  606. return ResultCode.Success;
  607. }
  608. [CommandCmif(209)]
  609. // ClearAcceptFilter()
  610. public ResultCode ClearAcceptFilter(ServiceCtx context)
  611. {
  612. if (_nifmResultCode != ResultCode.Success)
  613. {
  614. return _nifmResultCode;
  615. }
  616. // TODO
  617. return ResultCode.Success;
  618. }
  619. [CommandCmif(300)]
  620. // OpenStation()
  621. public ResultCode OpenStation(ServiceCtx context)
  622. {
  623. if (_nifmResultCode != ResultCode.Success)
  624. {
  625. return _nifmResultCode;
  626. }
  627. if (_state != NetworkState.Initialized)
  628. {
  629. return ResultCode.InvalidState;
  630. }
  631. CloseAccessPoint();
  632. SetState(NetworkState.Station);
  633. _station?.Dispose();
  634. _station = new Station(this);
  635. // NOTE: Calls nifm service and returns related result codes.
  636. // Since we use our own implementation we can return ResultCode.Success.
  637. return ResultCode.Success;
  638. }
  639. [CommandCmif(301)]
  640. // CloseStation()
  641. public ResultCode CloseStation(ServiceCtx context)
  642. {
  643. if (_nifmResultCode != ResultCode.Success)
  644. {
  645. return _nifmResultCode;
  646. }
  647. if (_state == NetworkState.Station || _state == NetworkState.StationConnected)
  648. {
  649. DisconnectImpl(DisconnectReason.DisconnectedByUser);
  650. }
  651. else
  652. {
  653. return ResultCode.InvalidState;
  654. }
  655. SetState(NetworkState.Initialized);
  656. return ResultCode.Success;
  657. }
  658. private void CloseStation()
  659. {
  660. _station?.Dispose();
  661. _station = null;
  662. }
  663. [CommandCmif(302)]
  664. // Connect(bytes<0x44, 2> security_config, bytes<0x30, 1> user_config, u32 local_communication_version, u32 option_unknown, buffer<network_info<0x480>, 0x19>)
  665. public ResultCode Connect(ServiceCtx context)
  666. {
  667. return ConnectImpl(context);
  668. }
  669. [CommandCmif(303)]
  670. // ConnectPrivate(bytes<0x44, 2> security_config, bytes<0x20, 1> security_parameter, bytes<0x30, 1> user_config, u32 local_communication_version, u32 option_unknown, bytes<0x20, 8> network_config)
  671. public ResultCode ConnectPrivate(ServiceCtx context)
  672. {
  673. return ConnectImpl(context, true);
  674. }
  675. private ResultCode ConnectImpl(ServiceCtx context, bool isPrivate = false)
  676. {
  677. SecurityConfig securityConfig = context.RequestData.ReadStruct<SecurityConfig>();
  678. SecurityParameter securityParameter = isPrivate ? context.RequestData.ReadStruct<SecurityParameter>() : new SecurityParameter();
  679. UserConfig userConfig = context.RequestData.ReadStruct<UserConfig>();
  680. uint localCommunicationVersion = context.RequestData.ReadUInt32();
  681. uint optionUnknown = context.RequestData.ReadUInt32();
  682. NetworkConfig networkConfig = new();
  683. NetworkInfo networkInfo = new();
  684. if (isPrivate)
  685. {
  686. context.RequestData.ReadUInt32(); // Padding.
  687. networkConfig = context.RequestData.ReadStruct<NetworkConfig>();
  688. }
  689. else
  690. {
  691. ulong bufferPosition = context.Request.PtrBuff[0].Position;
  692. ulong bufferSize = context.Request.PtrBuff[0].Size;
  693. byte[] networkInfoBytes = new byte[bufferSize];
  694. context.Memory.Read(bufferPosition, networkInfoBytes);
  695. networkInfo = MemoryMarshal.Read<NetworkInfo>(networkInfoBytes);
  696. }
  697. if (networkInfo.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
  698. {
  699. // TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
  700. ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
  701. networkInfo.NetworkId.IntentId.LocalCommunicationId = (long)controlProperty.LocalCommunicationId[0];
  702. }
  703. bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkInfo.NetworkId.IntentId.LocalCommunicationId);
  704. if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
  705. {
  706. return ResultCode.InvalidObject;
  707. }
  708. if (_nifmResultCode != ResultCode.Success)
  709. {
  710. return _nifmResultCode;
  711. }
  712. securityConfig.SecurityMode = CheckDevelopmentSecurityMode(securityConfig.SecurityMode);
  713. ResultCode resultCode = ResultCode.InvalidArgument;
  714. if (securityConfig.SecurityMode - 1 <= SecurityMode.Debug)
  715. {
  716. if (optionUnknown <= 1 && (localCommunicationVersion >> 15) == 0 && securityConfig.PassphraseSize <= 64)
  717. {
  718. resultCode = ResultCode.VersionTooLow;
  719. if (localCommunicationVersion >= 0)
  720. {
  721. resultCode = ResultCode.VersionTooHigh;
  722. if (localCommunicationVersion <= short.MaxValue)
  723. {
  724. if (_state != NetworkState.Station)
  725. {
  726. resultCode = ResultCode.InvalidState;
  727. }
  728. else
  729. {
  730. if (isPrivate)
  731. {
  732. resultCode = _station.ConnectPrivate(securityConfig, securityParameter, userConfig, localCommunicationVersion, optionUnknown, networkConfig);
  733. }
  734. else
  735. {
  736. resultCode = _station.Connect(securityConfig, userConfig, localCommunicationVersion, optionUnknown, networkInfo);
  737. }
  738. }
  739. }
  740. }
  741. }
  742. }
  743. return resultCode;
  744. }
  745. [CommandCmif(304)]
  746. // Disconnect()
  747. public ResultCode Disconnect(ServiceCtx context)
  748. {
  749. return DisconnectImpl(DisconnectReason.DisconnectedByUser);
  750. }
  751. private ResultCode DisconnectImpl(DisconnectReason disconnectReason)
  752. {
  753. if (_nifmResultCode != ResultCode.Success)
  754. {
  755. return _nifmResultCode;
  756. }
  757. if (disconnectReason <= DisconnectReason.DisconnectedBySystem)
  758. {
  759. if (_state == NetworkState.StationConnected)
  760. {
  761. SetState(NetworkState.Station);
  762. CloseStation();
  763. _disconnectReason = disconnectReason;
  764. return ResultCode.Success;
  765. }
  766. CloseStation();
  767. return ResultCode.InvalidState;
  768. }
  769. return ResultCode.InvalidArgument;
  770. }
  771. [CommandCmif(400)]
  772. // InitializeOld(pid)
  773. public ResultCode InitializeOld(ServiceCtx context)
  774. {
  775. return InitializeImpl(context, context.Process.Pid, NifmRequestID);
  776. }
  777. [CommandCmif(401)]
  778. // Finalize()
  779. public ResultCode Finalize(ServiceCtx context)
  780. {
  781. if (_nifmResultCode != ResultCode.Success)
  782. {
  783. return _nifmResultCode;
  784. }
  785. // NOTE: Use true when its called in nn::ldn::detail::ISystemLocalCommunicationService
  786. ResultCode resultCode = FinalizeImpl(false);
  787. if (resultCode == ResultCode.Success)
  788. {
  789. SetDisconnectReason(DisconnectReason.None);
  790. }
  791. if (_stateChangeEventHandle != 0)
  792. {
  793. context.Process.HandleTable.CloseHandle(_stateChangeEventHandle);
  794. _stateChangeEventHandle = 0;
  795. }
  796. return resultCode;
  797. }
  798. private ResultCode FinalizeImpl(bool isCausedBySystem)
  799. {
  800. DisconnectReason disconnectReason;
  801. switch (_state)
  802. {
  803. case NetworkState.None:
  804. return ResultCode.Success;
  805. case NetworkState.AccessPoint:
  806. {
  807. CloseAccessPoint();
  808. break;
  809. }
  810. case NetworkState.AccessPointCreated:
  811. {
  812. if (isCausedBySystem)
  813. {
  814. disconnectReason = DisconnectReason.DestroyedBySystem;
  815. }
  816. else
  817. {
  818. disconnectReason = DisconnectReason.DestroyedByUser;
  819. }
  820. DestroyNetworkImpl(disconnectReason);
  821. break;
  822. }
  823. case NetworkState.Station:
  824. {
  825. CloseStation();
  826. break;
  827. }
  828. case NetworkState.StationConnected:
  829. {
  830. if (isCausedBySystem)
  831. {
  832. disconnectReason = DisconnectReason.DisconnectedBySystem;
  833. }
  834. else
  835. {
  836. disconnectReason = DisconnectReason.DisconnectedByUser;
  837. }
  838. DisconnectImpl(disconnectReason);
  839. break;
  840. }
  841. }
  842. SetState(NetworkState.None);
  843. NetworkClient?.Dispose();
  844. NetworkClient = null;
  845. return ResultCode.Success;
  846. }
  847. [CommandCmif(402)] // 7.0.0+
  848. // Initialize(u64 ip_addresses, pid)
  849. public ResultCode Initialize(ServiceCtx context)
  850. {
  851. _ = new IPAddress(context.RequestData.ReadUInt32());
  852. _ = new IPAddress(context.RequestData.ReadUInt32());
  853. // NOTE: It seems the guest can get ip_address and subnet_mask from nifm service and pass it through the initialize.
  854. // This calls InitializeImpl() twice: The first time with NIFM_REQUEST_ID, and if it fails, a second time with nifm_request_id = 1.
  855. return InitializeImpl(context, context.Process.Pid, NifmRequestID);
  856. }
  857. public ResultCode InitializeImpl(ServiceCtx context, ulong pid, int nifmRequestId)
  858. {
  859. ResultCode resultCode = ResultCode.InvalidArgument;
  860. if (nifmRequestId <= 255)
  861. {
  862. if (_state != NetworkState.Initialized)
  863. {
  864. // NOTE: Service calls nn::ldn::detail::NetworkInterfaceManager::NetworkInterfaceMonitor::Initialize() with nifmRequestId as argument,
  865. // then it stores the result code of it in a global variable. Since we use our own implementation, we can just check the connection
  866. // and return related error codes.
  867. if (System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
  868. {
  869. MultiplayerMode mode = context.Device.Configuration.MultiplayerMode;
  870. Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Initializing with multiplayer mode: {mode}");
  871. switch (mode)
  872. {
  873. case MultiplayerMode.LdnRyu:
  874. try
  875. {
  876. string ldnServer = context.Device.Configuration.MultiplayerLdnServer;
  877. if (string.IsNullOrEmpty(ldnServer))
  878. {
  879. ldnServer = DefaultLanPlayHost;
  880. }
  881. if (!IPAddress.TryParse(ldnServer, out IPAddress ipAddress))
  882. {
  883. ipAddress = Dns.GetHostEntry(ldnServer).AddressList[0];
  884. }
  885. NetworkClient = new LdnMasterProxyClient(ipAddress.ToString(), LanPlayPort, context.Device.Configuration);
  886. }
  887. catch (Exception ex)
  888. {
  889. Logger.Error?.Print(LogClass.ServiceLdn, "Could not locate LdnRyu server. Defaulting to stubbed wireless.");
  890. Logger.Error?.Print(LogClass.ServiceLdn, ex.Message);
  891. NetworkClient = new LdnDisabledClient();
  892. }
  893. break;
  894. case MultiplayerMode.LdnMitm:
  895. NetworkClient = new LdnMitmClient(context.Device.Configuration);
  896. break;
  897. case MultiplayerMode.Disabled:
  898. NetworkClient = new LdnDisabledClient();
  899. break;
  900. }
  901. // TODO: Call nn::arp::GetApplicationLaunchProperty here when implemented.
  902. NetworkClient.SetGameVersion(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion.Items.ToArray());
  903. resultCode = ResultCode.Success;
  904. _nifmResultCode = resultCode;
  905. SetState(NetworkState.Initialized);
  906. }
  907. else
  908. {
  909. // NOTE: Service returns different ResultCode here related to the nifm ResultCode.
  910. resultCode = ResultCode.DeviceDisabled;
  911. _nifmResultCode = resultCode;
  912. }
  913. }
  914. }
  915. return resultCode;
  916. }
  917. public void Dispose()
  918. {
  919. _station?.Dispose();
  920. _station = null;
  921. _accessPoint?.Dispose();
  922. _accessPoint = null;
  923. NetworkClient?.DisconnectAndStop();
  924. NetworkClient = null;
  925. }
  926. }
  927. }