2023-03-12 02:24:11 +00:00
using Ryujinx.Common ;
2020-09-29 22:32:42 +01:00
using Ryujinx.Common.Configuration.Hid ;
2021-04-14 11:28:43 +01:00
using Ryujinx.Common.Configuration.Hid.Controller ;
using Ryujinx.Common.Configuration.Hid.Controller.Motion ;
2020-09-29 22:32:42 +01:00
using Ryujinx.Common.Logging ;
2021-05-16 16:12:14 +01:00
using Ryujinx.Input.HLE ;
2021-04-14 11:28:43 +01:00
using Ryujinx.Input.Motion.CemuHook.Protocol ;
2020-09-29 22:32:42 +01:00
using System ;
using System.Collections.Generic ;
using System.IO ;
2023-03-12 02:24:11 +00:00
using System.IO.Hashing ;
2020-09-29 22:32:42 +01:00
using System.Net ;
using System.Net.Sockets ;
using System.Numerics ;
using System.Threading.Tasks ;
2021-04-14 11:28:43 +01:00
namespace Ryujinx.Input.Motion.CemuHook
2020-09-29 22:32:42 +01:00
{
public class Client : IDisposable
{
public const uint Magic = 0x43555344 ; // DSUC
public const ushort Version = 1001 ;
private bool _active ;
private readonly Dictionary < int , IPEndPoint > _hosts ;
private readonly Dictionary < int , Dictionary < int , MotionInput > > _motionData ;
private readonly Dictionary < int , UdpClient > _clients ;
2022-02-13 13:50:07 +00:00
private readonly bool [ ] _clientErrorStatus = new bool [ Enum . GetValues < PlayerIndex > ( ) . Length ] ;
private readonly long [ ] _clientRetryTimer = new long [ Enum . GetValues < PlayerIndex > ( ) . Length ] ;
2021-05-16 16:12:14 +01:00
private NpadManager _npadManager ;
2020-09-29 22:32:42 +01:00
2021-05-16 16:12:14 +01:00
public Client ( NpadManager npadManager )
2020-09-29 22:32:42 +01:00
{
2021-05-16 16:12:14 +01:00
_npadManager = npadManager ;
_hosts = new Dictionary < int , IPEndPoint > ( ) ;
_motionData = new Dictionary < int , Dictionary < int , MotionInput > > ( ) ;
_clients = new Dictionary < int , UdpClient > ( ) ;
2020-09-29 22:32:42 +01:00
CloseClients ( ) ;
}
public void CloseClients ( )
{
_active = false ;
lock ( _clients )
{
foreach ( var client in _clients )
{
try
{
client . Value ? . Dispose ( ) ;
}
2020-11-27 17:57:20 +00:00
catch ( SocketException socketException )
2020-09-29 22:32:42 +01:00
{
2020-11-27 17:57:20 +00:00
Logger . Warning ? . PrintMsg ( LogClass . Hid , $"Unable to dispose motion client. Error: {socketException.ErrorCode}" ) ;
2020-09-29 22:32:42 +01:00
}
}
_hosts . Clear ( ) ;
_clients . Clear ( ) ;
_motionData . Clear ( ) ;
}
}
public void RegisterClient ( int player , string host , int port )
{
2020-10-28 19:52:07 +00:00
if ( _clients . ContainsKey ( player ) | | ! CanConnect ( player ) )
2020-09-29 22:32:42 +01:00
{
return ;
}
2020-10-28 19:52:07 +00:00
lock ( _clients )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
if ( _clients . ContainsKey ( player ) | | ! CanConnect ( player ) )
{
return ;
}
UdpClient client = null ;
try
2020-09-29 22:32:42 +01:00
{
IPEndPoint endPoint = new IPEndPoint ( IPAddress . Parse ( host ) , port ) ;
2020-10-28 19:52:07 +00:00
client = new UdpClient ( host , port ) ;
2020-09-29 22:32:42 +01:00
_clients . Add ( player , client ) ;
_hosts . Add ( player , endPoint ) ;
_active = true ;
Task . Run ( ( ) = >
{
ReceiveLoop ( player ) ;
} ) ;
}
2020-11-27 17:57:20 +00:00
catch ( FormatException formatException )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
if ( ! _clientErrorStatus [ player ] )
{
2020-11-27 17:57:20 +00:00
Logger . Warning ? . PrintMsg ( LogClass . Hid , $"Unable to connect to motion source at {host}:{port}. Error: {formatException.Message}" ) ;
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
_clientErrorStatus [ player ] = true ;
}
2020-09-29 22:32:42 +01:00
}
2020-11-27 17:57:20 +00:00
catch ( SocketException socketException )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
if ( ! _clientErrorStatus [ player ] )
{
2020-11-27 17:57:20 +00:00
Logger . Warning ? . PrintMsg ( LogClass . Hid , $"Unable to connect to motion source at {host}:{port}. Error: {socketException.ErrorCode}" ) ;
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
_clientErrorStatus [ player ] = true ;
}
RemoveClient ( player ) ;
client ? . Dispose ( ) ;
SetRetryTimer ( player ) ;
}
2020-11-27 17:57:20 +00:00
catch ( Exception exception )
2020-10-28 19:52:07 +00:00
{
2020-11-27 17:57:20 +00:00
Logger . Warning ? . PrintMsg ( LogClass . Hid , $"Unable to register motion client. Error: {exception.Message}" ) ;
2020-09-29 22:32:42 +01:00
_clientErrorStatus [ player ] = true ;
2020-10-28 19:52:07 +00:00
RemoveClient ( player ) ;
client ? . Dispose ( ) ;
SetRetryTimer ( player ) ;
2020-09-29 22:32:42 +01:00
}
}
}
public bool TryGetData ( int player , int slot , out MotionInput input )
{
lock ( _motionData )
{
if ( _motionData . ContainsKey ( player ) )
{
2020-10-28 19:52:07 +00:00
if ( _motionData [ player ] . TryGetValue ( slot , out input ) )
{
return true ;
}
2020-09-29 22:32:42 +01:00
}
}
input = null ;
return false ;
}
2020-10-28 19:52:07 +00:00
private void RemoveClient ( int clientId )
{
_clients ? . Remove ( clientId ) ;
_hosts ? . Remove ( clientId ) ;
}
2020-09-29 22:32:42 +01:00
private void Send ( byte [ ] data , int clientId )
{
if ( _clients . TryGetValue ( clientId , out UdpClient _client ) )
{
if ( _client ! = null & & _client . Client ! = null & & _client . Client . Connected )
{
try
{
_client ? . Send ( data , data . Length ) ;
}
2020-11-27 17:57:20 +00:00
catch ( SocketException socketException )
2020-09-29 22:32:42 +01:00
{
if ( ! _clientErrorStatus [ clientId ] )
{
2020-11-27 17:57:20 +00:00
Logger . Warning ? . PrintMsg ( LogClass . Hid , $"Unable to send data request to motion source at {_client.Client.RemoteEndPoint}. Error: {socketException.ErrorCode}" ) ;
2020-09-29 22:32:42 +01:00
}
_clientErrorStatus [ clientId ] = true ;
2020-10-28 19:52:07 +00:00
RemoveClient ( clientId ) ;
_client ? . Dispose ( ) ;
SetRetryTimer ( clientId ) ;
}
2020-11-27 17:57:20 +00:00
catch ( ObjectDisposedException )
2020-10-28 19:52:07 +00:00
{
_clientErrorStatus [ clientId ] = true ;
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
RemoveClient ( clientId ) ;
2020-09-29 22:32:42 +01:00
_client ? . Dispose ( ) ;
2020-10-28 19:52:07 +00:00
SetRetryTimer ( clientId ) ;
2020-09-29 22:32:42 +01:00
}
}
}
}
2020-10-28 19:52:07 +00:00
private byte [ ] Receive ( int clientId , int timeout = 0 )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
if ( _hosts . TryGetValue ( clientId , out IPEndPoint endPoint ) & & _clients . TryGetValue ( clientId , out UdpClient _client ) )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
if ( _client ! = null & & _client . Client ! = null & & _client . Client . Connected )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
_client . Client . ReceiveTimeout = timeout ;
var result = _client ? . Receive ( ref endPoint ) ;
if ( result . Length > 0 )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
_clientErrorStatus [ clientId ] = false ;
}
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
return result ;
}
}
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
throw new Exception ( $"Client {clientId} is not registered." ) ;
}
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
private void SetRetryTimer ( int clientId )
{
var elapsedMs = PerformanceCounter . ElapsedMilliseconds ;
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
_clientRetryTimer [ clientId ] = elapsedMs ;
}
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
private void ResetRetryTimer ( int clientId )
{
_clientRetryTimer [ clientId ] = 0 ;
}
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
private bool CanConnect ( int clientId )
{
2020-11-27 17:57:20 +00:00
return _clientRetryTimer [ clientId ] = = 0 | | PerformanceCounter . ElapsedMilliseconds - 5000 > _clientRetryTimer [ clientId ] ;
2020-09-29 22:32:42 +01:00
}
public void ReceiveLoop ( int clientId )
{
2020-10-28 19:52:07 +00:00
if ( _hosts . TryGetValue ( clientId , out IPEndPoint endPoint ) & & _clients . TryGetValue ( clientId , out UdpClient _client ) )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
if ( _client ! = null & & _client . Client ! = null & & _client . Client . Connected )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
try
{
while ( _active )
{
byte [ ] data = Receive ( clientId ) ;
if ( data . Length = = 0 )
{
continue ;
}
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
Task . Run ( ( ) = > HandleResponse ( data , clientId ) ) ;
}
}
2020-11-27 17:57:20 +00:00
catch ( SocketException socketException )
2020-10-28 19:52:07 +00:00
{
if ( ! _clientErrorStatus [ clientId ] )
{
2020-11-27 17:57:20 +00:00
Logger . Warning ? . PrintMsg ( LogClass . Hid , $"Unable to receive data from motion source at {endPoint}. Error: {socketException.ErrorCode}" ) ;
2020-10-28 19:52:07 +00:00
}
_clientErrorStatus [ clientId ] = true ;
RemoveClient ( clientId ) ;
_client ? . Dispose ( ) ;
SetRetryTimer ( clientId ) ;
}
catch ( ObjectDisposedException )
{
_clientErrorStatus [ clientId ] = true ;
RemoveClient ( clientId ) ;
_client ? . Dispose ( ) ;
SetRetryTimer ( clientId ) ;
}
}
2020-09-29 22:32:42 +01:00
}
}
2020-10-28 19:52:07 +00:00
public void HandleResponse ( byte [ ] data , int clientId )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
ResetRetryTimer ( clientId ) ;
2020-09-29 22:32:42 +01:00
MessageType type = ( MessageType ) BitConverter . ToUInt32 ( data . AsSpan ( ) . Slice ( 16 , 4 ) ) ;
2020-11-27 17:57:20 +00:00
data = data . AsSpan ( ) [ 16. . ] . ToArray ( ) ;
2020-10-28 19:52:07 +00:00
2020-11-27 17:57:20 +00:00
using MemoryStream stream = new MemoryStream ( data ) ;
using BinaryReader reader = new BinaryReader ( stream ) ;
2020-10-28 19:52:07 +00:00
switch ( type )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
case MessageType . Protocol :
break ;
case MessageType . Info :
ControllerInfoResponse contollerInfo = reader . ReadStruct < ControllerInfoResponse > ( ) ;
break ;
case MessageType . Data :
ControllerDataResponse inputData = reader . ReadStruct < ControllerDataResponse > ( ) ;
Vector3 accelerometer = new Vector3 ( )
2020-09-29 22:32:42 +01:00
{
2020-10-28 19:52:07 +00:00
X = - inputData . AccelerometerX ,
Y = inputData . AccelerometerZ ,
Z = - inputData . AccelerometerY
} ;
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
Vector3 gyroscrope = new Vector3 ( )
{
X = inputData . GyroscopePitch ,
Y = inputData . GyroscopeRoll ,
Z = - inputData . GyroscopeYaw
} ;
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
ulong timestamp = inputData . MotionTimestamp ;
2020-09-29 22:32:42 +01:00
2021-05-16 16:12:14 +01:00
InputConfig config = _npadManager . GetPlayerInputConfigByIndex ( clientId ) ;
2020-09-29 22:32:42 +01:00
2020-10-28 19:52:07 +00:00
lock ( _motionData )
{
2021-04-14 11:28:43 +01:00
// Sanity check the configuration state and remove client if needed if needed.
if ( config is StandardControllerInputConfig controllerConfig & &
controllerConfig . Motion . EnableMotion & &
controllerConfig . Motion . MotionBackend = = MotionInputBackendType . CemuHook & &
controllerConfig . Motion is CemuHookMotionConfigController cemuHookConfig )
2020-10-28 19:52:07 +00:00
{
2021-04-14 11:28:43 +01:00
int slot = inputData . Shared . Slot ;
if ( _motionData . ContainsKey ( clientId ) )
2020-09-29 22:32:42 +01:00
{
2021-04-14 11:28:43 +01:00
if ( _motionData [ clientId ] . ContainsKey ( slot ) )
{
MotionInput previousData = _motionData [ clientId ] [ slot ] ;
previousData . Update ( accelerometer , gyroscrope , timestamp , cemuHookConfig . Sensitivity , ( float ) cemuHookConfig . GyroDeadzone ) ;
}
else
{
MotionInput input = new MotionInput ( ) ;
2020-10-28 19:52:07 +00:00
2021-04-14 11:28:43 +01:00
input . Update ( accelerometer , gyroscrope , timestamp , cemuHookConfig . Sensitivity , ( float ) cemuHookConfig . GyroDeadzone ) ;
_motionData [ clientId ] . Add ( slot , input ) ;
}
2020-09-29 22:32:42 +01:00
}
2020-10-28 19:52:07 +00:00
else
{
MotionInput input = new MotionInput ( ) ;
2020-11-27 17:57:20 +00:00
2021-04-14 11:28:43 +01:00
input . Update ( accelerometer , gyroscrope , timestamp , cemuHookConfig . Sensitivity , ( float ) cemuHookConfig . GyroDeadzone ) ;
2020-11-27 17:57:20 +00:00
2021-04-14 11:28:43 +01:00
_motionData . Add ( clientId , new Dictionary < int , MotionInput > ( ) { { slot , input } } ) ;
2020-10-28 19:52:07 +00:00
}
}
else
{
2021-04-14 11:28:43 +01:00
RemoveClient ( clientId ) ;
2020-10-28 19:52:07 +00:00
}
2020-09-29 22:32:42 +01:00
}
2020-10-28 19:52:07 +00:00
break ;
2020-09-29 22:32:42 +01:00
}
}
public void RequestInfo ( int clientId , int slot )
{
if ( ! _active )
{
return ;
}
Header header = GenerateHeader ( clientId ) ;
2020-11-27 17:57:20 +00:00
using ( MemoryStream stream = new MemoryStream ( ) )
using ( BinaryWriter writer = new BinaryWriter ( stream ) )
2020-09-29 22:32:42 +01:00
{
2020-11-27 17:57:20 +00:00
writer . WriteStruct ( header ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
ControllerInfoRequest request = new ControllerInfoRequest ( )
{
Type = MessageType . Info ,
PortsCount = 4
} ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
request . PortIndices [ 0 ] = ( byte ) slot ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
writer . WriteStruct ( request ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
header . Length = ( ushort ) ( stream . Length - 16 ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
writer . Seek ( 6 , SeekOrigin . Begin ) ;
writer . Write ( header . Length ) ;
2020-09-29 22:32:42 +01:00
2023-03-12 02:24:11 +00:00
Crc32 . Hash ( stream . ToArray ( ) , header . Crc32 . AsSpan ( ) ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
writer . Seek ( 8 , SeekOrigin . Begin ) ;
2023-03-12 02:24:11 +00:00
writer . Write ( header . Crc32 . AsSpan ( ) ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
byte [ ] data = stream . ToArray ( ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
Send ( data , clientId ) ;
2020-09-29 22:32:42 +01:00
}
}
2021-01-08 08:14:13 +00:00
public unsafe void RequestData ( int clientId , int slot )
2020-09-29 22:32:42 +01:00
{
if ( ! _active )
{
return ;
}
Header header = GenerateHeader ( clientId ) ;
2020-11-27 17:57:20 +00:00
using ( MemoryStream stream = new MemoryStream ( ) )
using ( BinaryWriter writer = new BinaryWriter ( stream ) )
2020-09-29 22:32:42 +01:00
{
2020-11-27 17:57:20 +00:00
writer . WriteStruct ( header ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
ControllerDataRequest request = new ControllerDataRequest ( )
{
Type = MessageType . Data ,
Slot = ( byte ) slot ,
SubscriberType = SubscriberType . Slot
} ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
writer . WriteStruct ( request ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
header . Length = ( ushort ) ( stream . Length - 16 ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
writer . Seek ( 6 , SeekOrigin . Begin ) ;
writer . Write ( header . Length ) ;
2020-09-29 22:32:42 +01:00
2023-03-12 02:24:11 +00:00
Crc32 . Hash ( stream . ToArray ( ) , header . Crc32 . AsSpan ( ) ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
writer . Seek ( 8 , SeekOrigin . Begin ) ;
2023-03-12 02:24:11 +00:00
writer . Write ( header . Crc32 . AsSpan ( ) ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
byte [ ] data = stream . ToArray ( ) ;
2020-09-29 22:32:42 +01:00
2020-11-27 17:57:20 +00:00
Send ( data , clientId ) ;
2020-09-29 22:32:42 +01:00
}
}
private Header GenerateHeader ( int clientId )
{
Header header = new Header ( )
{
2021-01-08 08:14:13 +00:00
Id = ( uint ) clientId ,
2020-09-29 22:32:42 +01:00
MagicString = Magic ,
Version = Version ,
2023-03-12 02:24:11 +00:00
Length = 0
2020-09-29 22:32:42 +01:00
} ;
return header ;
}
public void Dispose ( )
{
_active = false ;
CloseClients ( ) ;
}
}
2021-01-08 08:14:13 +00:00
}