2018-02-27 22:11:58 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Threading.Tasks ;
using System.Numerics ;
using System.Threading ;
using System.Runtime.InteropServices ;
using System.Timers ;
2018-03-15 17:14:54 +00:00
2018-02-27 22:11:58 +00:00
using System.Net.NetworkInformation ;
using System.Diagnostics ;
2018-03-15 17:14:54 +00:00
using static BetterJoyForCemu . HIDapi ;
using Nefarius.ViGEm.Client ;
using Nefarius.ViGEm.Client.Targets ;
using System.Net ;
using System.Configuration ;
2018-05-02 18:57:47 +01:00
using System.Net.Http ;
using System.IO ;
using System.Windows.Forms ;
using System.ServiceProcess ;
2018-03-15 17:14:54 +00:00
2018-02-27 22:11:58 +00:00
namespace BetterJoyForCemu {
2018-07-16 22:03:51 +03:00
public class JoyconManager {
public bool EnableIMU = true ;
public bool EnableLocalize = false ;
private const ushort vendor_id = 0x57e ;
private const ushort vendor_id_ = 0x057e ;
private const ushort product_l = 0x2006 ;
private const ushort product_r = 0x2007 ;
private const ushort product_pro = 0x2009 ;
public List < Joycon > j ; // Array of all connected Joy-Cons
static JoyconManager instance ;
public MainForm form ;
2018-08-18 20:52:34 +03:00
System . Timers . Timer controllerCheck ;
bool useHIDG = Boolean . Parse ( ConfigurationManager . AppSettings [ "UseHIDG" ] ) ;
2018-07-27 09:54:08 +03:00
2018-07-16 22:03:51 +03:00
public static JoyconManager Instance {
get { return instance ; }
}
public void Awake ( ) {
instance = this ;
j = new List < Joycon > ( ) ;
HIDapi . hid_init ( ) ;
2018-08-18 20:52:34 +03:00
}
2018-07-16 22:03:51 +03:00
2018-08-18 20:52:34 +03:00
public void Start ( ) {
controllerCheck = new System . Timers . Timer ( 2000 ) ; // check every 2 seconds
controllerCheck . Elapsed + = CheckForNewControllersTime ;
controllerCheck . Start ( ) ;
}
2018-07-16 22:03:51 +03:00
2018-08-18 20:52:34 +03:00
bool ControllerAlreadyAdded ( string path ) {
foreach ( Joycon v in j )
if ( v . path = = path )
return true ;
return false ;
}
void CleanUp ( ) { // removes dropped controllers from list
List < Joycon > rem = new List < Joycon > ( ) ;
for ( int i = 0 ; i < j . Count ; i + + ) {
Joycon v = j [ i ] ;
if ( v . state = = Joycon . state_ . DROPPED ) {
2018-08-23 15:48:19 +03:00
if ( v . other ! = null )
v . other . other = null ; // The other of the other is the joycon itself
2018-08-18 20:52:34 +03:00
v . Detach ( ) ; rem . Add ( v ) ;
2018-08-22 12:52:41 +03:00
foreach ( Button b in form . con ) {
if ( b . Enabled & b . Tag = = v ) {
b . Invoke ( new MethodInvoker ( delegate {
2019-02-02 15:52:23 +00:00
b . BackColor = System . Drawing . Color . FromArgb ( 0x00 , System . Drawing . SystemColors . Control ) ;
2018-08-22 12:52:41 +03:00
b . Enabled = false ;
2018-08-23 15:48:19 +03:00
b . BackgroundImage = Properties . Resources . cross ;
2018-08-22 12:52:41 +03:00
} ) ) ;
break ;
}
}
2018-08-18 20:52:34 +03:00
form . AppendTextBox ( "Removed dropped controller to list. Can be reconnected.\r\n" ) ;
2018-07-16 22:03:51 +03:00
}
}
2018-08-18 20:52:34 +03:00
foreach ( Joycon v in rem )
j . Remove ( v ) ;
}
void CheckForNewControllersTime ( Object source , ElapsedEventArgs e ) {
if ( Config . Value ( "ProgressiveScan" ) ) {
CheckForNewControllers ( ) ;
}
}
public void CheckForNewControllers ( ) {
CleanUp ( ) ;
// move all code for initializing devices here and well as the initial code from Start()
bool isLeft = false ;
IntPtr ptr = HIDapi . hid_enumerate ( vendor_id , 0x0 ) ;
IntPtr top_ptr = ptr ;
hid_device_info enumerate ; // Add device to list
2018-08-23 15:48:19 +03:00
bool foundNew = false ;
2018-07-16 22:03:51 +03:00
while ( ptr ! = IntPtr . Zero ) {
enumerate = ( hid_device_info ) Marshal . PtrToStructure ( ptr , typeof ( hid_device_info ) ) ;
2018-08-18 20:52:34 +03:00
if ( ( enumerate . product_id = = product_l | | enumerate . product_id = = product_r | | enumerate . product_id = = product_pro ) & & ! ControllerAlreadyAdded ( enumerate . path ) ) {
switch ( enumerate . product_id ) {
case product_l :
isLeft = true ;
form . AppendTextBox ( "Left Joy-Con connected.\r\n" ) ; break ;
case product_r :
isLeft = false ;
form . AppendTextBox ( "Right Joy-Con connected.\r\n" ) ; break ;
case product_pro :
isLeft = true ;
form . AppendTextBox ( "Pro controller connected.\r\n" ) ; break ;
default :
form . AppendTextBox ( "Non Joy-Con Nintendo input device skipped.\r\n" ) ; break ;
2018-07-16 22:03:51 +03:00
}
// Add controller to block-list for HidGuardian
2018-07-27 09:54:08 +03:00
if ( useHIDG ) {
HttpWebRequest request = ( HttpWebRequest ) WebRequest . Create ( @"http://localhost:26762/api/v1/hidguardian/affected/add/" ) ;
string postData = @"hwids=HID\" + enumerate . path . Split ( '#' ) [ 1 ] . ToUpper ( ) ;
var data = Encoding . UTF8 . GetBytes ( postData ) ;
request . Method = "POST" ;
request . ContentType = "application/x-www-form-urlencoded; charset=UTF-8" ;
request . ContentLength = data . Length ;
using ( var stream = request . GetRequestStream ( ) )
stream . Write ( data , 0 , data . Length ) ;
try {
var response = ( HttpWebResponse ) request . GetResponse ( ) ;
var responseString = new StreamReader ( response . GetResponseStream ( ) ) . ReadToEnd ( ) ;
2018-08-18 20:52:34 +03:00
} catch {
form . AppendTextBox ( "Unable to add controller to block-list.\r\n" ) ;
2018-07-27 09:54:08 +03:00
}
2018-08-18 20:52:34 +03:00
} else { // Remove affected devices from list
try {
HttpWebResponse r1 = ( HttpWebResponse ) WebRequest . Create ( @"http://localhost:26762/api/v1/hidguardian/affected/purge/" ) . GetResponse ( ) ;
} catch { }
2018-07-16 22:03:51 +03:00
}
// -------------------- //
IntPtr handle = HIDapi . hid_open_path ( enumerate . path ) ;
try {
HIDapi . hid_set_nonblocking ( handle , 1 ) ;
2018-08-18 20:52:34 +03:00
} catch {
form . AppendTextBox ( "Unable to open path to device - are you using the correct (64 vs 32-bit) version for your PC?\r\n" ) ;
2018-07-16 22:03:51 +03:00
break ;
}
2018-08-18 20:52:34 +03:00
j . Add ( new Joycon ( handle , EnableIMU , EnableLocalize & EnableIMU , 0.05f , isLeft , enumerate . path , j . Count , enumerate . product_id = = product_pro , enumerate . serial_number = = "000000000001" ) ) ;
2018-07-16 22:03:51 +03:00
2018-08-23 15:48:19 +03:00
foundNew = true ;
2018-07-16 22:03:51 +03:00
j . Last ( ) . form = form ;
2018-08-22 12:52:41 +03:00
if ( j . Count < 5 ) {
2018-08-23 15:48:19 +03:00
int ii = - 1 ;
2018-08-22 12:52:41 +03:00
foreach ( Button v in form . con ) {
2018-08-23 15:48:19 +03:00
ii + + ;
2018-08-22 12:52:41 +03:00
if ( ! v . Enabled ) {
System . Drawing . Bitmap temp ;
switch ( enumerate . product_id ) {
case ( product_l ) :
2018-08-23 15:48:19 +03:00
temp = Properties . Resources . jc_left_s ; break ;
2018-08-22 12:52:41 +03:00
case ( product_r ) :
2018-08-23 15:48:19 +03:00
temp = Properties . Resources . jc_right_s ; break ;
2018-08-22 12:52:41 +03:00
case ( product_pro ) :
temp = Properties . Resources . pro ; break ;
default :
temp = Properties . Resources . cross ; break ;
}
v . Invoke ( new MethodInvoker ( delegate {
v . Tag = j . Last ( ) ; // assign controller to button
v . Enabled = true ;
v . Click + = new EventHandler ( form . conBtnClick ) ;
v . BackgroundImage = temp ;
} ) ) ;
2018-08-23 15:48:19 +03:00
form . loc [ ii ] . Invoke ( new MethodInvoker ( delegate {
form . loc [ ii ] . Tag = v ;
form . loc [ ii ] . Click + = new EventHandler ( form . locBtnClick ) ;
} ) ) ;
2018-08-22 12:52:41 +03:00
break ;
}
}
}
2018-07-16 22:03:51 +03:00
byte [ ] mac = new byte [ 6 ] ;
for ( int n = 0 ; n < 6 ; n + + )
mac [ n ] = byte . Parse ( enumerate . serial_number . Substring ( n * 2 , 2 ) , System . Globalization . NumberStyles . HexNumber ) ;
j [ j . Count - 1 ] . PadMacAddress = new PhysicalAddress ( mac ) ;
}
2018-08-18 20:52:34 +03:00
2018-07-16 22:03:51 +03:00
ptr = enumerate . next ;
}
2019-02-02 15:19:03 +00:00
if ( foundNew ) { // attempt to auto join-up joycons on connection
2018-07-16 22:03:51 +03:00
Joycon temp = null ;
foreach ( Joycon v in j ) {
if ( ! v . isPro ) {
if ( temp = = null )
temp = v ;
2019-02-02 15:52:23 +00:00
else if ( temp . isLeft ! = v . isLeft & & v . other = = null ) {
2018-07-16 22:03:51 +03:00
temp . other = v ;
v . other = temp ;
2019-04-19 01:20:12 -03:00
//Set both Joycon LEDs to the one with the lowest ID
byte led = temp . LED < = v . LED ? temp . LED : v . LED ;
temp . LED = led ;
v . LED = led ;
temp . SetLED ( led ) ;
v . SetLED ( led ) ;
2018-07-16 22:03:51 +03:00
temp . xin . Dispose ( ) ;
temp . xin = null ;
2018-08-23 15:48:19 +03:00
2019-02-02 15:52:23 +00:00
foreach ( Button b in form . con )
if ( b . Tag = = v | | b . Tag = = temp ) {
Joycon tt = ( b . Tag = = v ) ? v : ( b . Tag = = temp ) ? temp : v ;
b . BackgroundImage = tt . isLeft ? Properties . Resources . jc_left : Properties . Resources . jc_right ;
}
temp = null ; // repeat
}
2018-07-16 22:03:51 +03:00
}
2019-02-02 15:19:03 +00:00
}
2018-08-18 20:52:34 +03:00
}
2018-07-16 22:03:51 +03:00
HIDapi . hid_free_enumeration ( top_ptr ) ;
2018-08-18 20:52:34 +03:00
foreach ( Joycon jc in j ) { // Connect device straight away
if ( jc . state = = Joycon . state_ . NOT_ATTACHED ) {
if ( jc . xin ! = null )
jc . xin . Connect ( ) ;
2018-07-16 22:03:51 +03:00
2018-08-18 20:52:34 +03:00
jc . Attach ( leds_ : jc . LED ) ;
jc . Begin ( ) ;
}
2018-07-16 22:03:51 +03:00
}
}
public void Update ( ) {
for ( int i = 0 ; i < j . Count ; + + i )
j [ i ] . Update ( ) ;
}
public void OnApplicationQuit ( ) {
2018-08-14 10:42:00 +03:00
foreach ( Joycon v in j ) {
v . Detach ( ) ;
2018-07-16 22:03:51 +03:00
2018-08-14 10:42:00 +03:00
if ( v . xin ! = null ) {
v . xin . Disconnect ( ) ;
v . xin . Dispose ( ) ;
2018-07-16 22:03:51 +03:00
}
}
2018-08-18 20:52:34 +03:00
controllerCheck . Stop ( ) ;
HIDapi . hid_exit ( ) ;
2018-07-16 22:03:51 +03:00
}
}
// Custom timer class because system timers have a limit of 15.6ms
class HighResTimer {
double interval = 0 ;
double frequency = 0 ;
Thread thread ;
public delegate void ActionDelegate ( ) ;
ActionDelegate func ;
bool run = false ;
public HighResTimer ( double f , ActionDelegate a ) {
frequency = f ;
interval = 1.0 / f ;
func = a ;
}
public void Start ( ) {
run = true ;
thread = new Thread ( new ThreadStart ( Run ) ) ;
2018-08-14 10:42:00 +03:00
thread . IsBackground = true ;
2018-07-16 22:03:51 +03:00
thread . Start ( ) ;
}
void Run ( ) {
while ( run ) {
func ( ) ;
int timeToSleep = ( int ) ( interval * 1000 ) ;
Thread . Sleep ( timeToSleep ) ;
}
}
public void Stop ( ) {
run = false ;
}
}
class Program {
public static PhysicalAddress btMAC = new PhysicalAddress ( new byte [ ] { 0 , 0 , 0 , 0 , 0 , 0 } ) ;
public static UdpServer server ;
2018-08-18 20:52:34 +03:00
static double pollsPerSecond = 120.0 ;
2018-07-16 22:03:51 +03:00
public static ViGEmClient emClient ;
private static readonly HttpClient client = new HttpClient ( ) ;
2018-08-23 15:48:19 +03:00
public static JoyconManager mgr ;
2018-07-16 22:03:51 +03:00
static HighResTimer timer ;
static string pid ;
static MainForm form ;
2018-08-18 14:21:20 +03:00
static bool useHIDG = Boolean . Parse ( ConfigurationManager . AppSettings [ "UseHIDG" ] ) ;
2018-07-27 09:54:08 +03:00
2018-07-16 22:03:51 +03:00
public static void Start ( ) {
pid = Process . GetCurrentProcess ( ) . Id . ToString ( ) ; // get current process id for HidCerberus.Srv
2018-07-27 09:54:08 +03:00
if ( useHIDG ) {
try {
var HidCerberusService = new ServiceController ( "HidCerberus Service" ) ;
if ( HidCerberusService . Status = = ServiceControllerStatus . Stopped ) {
form . console . Text + = "HidGuardian was stopped. Starting...\r\n" ;
try {
HidCerberusService . Start ( ) ;
} catch ( Exception e ) {
form . console . Text + = "Unable to start HidGuardian - everything should work fine without it, but if you need it, run the app again as an admin.\r\n" ;
}
}
} catch ( Exception e ) {
form . console . Text + = "Unable to start HidGuardian - everything should work fine without it, but if you need it, install it properly as admin.\r\n" ;
}
2018-07-16 22:03:51 +03:00
2018-07-27 09:54:08 +03:00
HttpWebResponse response ;
2018-08-18 14:21:20 +03:00
if ( Boolean . Parse ( ConfigurationManager . AppSettings [ "PurgeWhitelist" ] ) ) {
2018-07-16 22:03:51 +03:00
try {
2018-07-27 09:54:08 +03:00
response = ( HttpWebResponse ) WebRequest . Create ( @"http://localhost:26762/api/v1/hidguardian/whitelist/purge/" ) . GetResponse ( ) ; // remove all programs allowed to see controller
2018-07-16 22:03:51 +03:00
} catch ( Exception e ) {
2018-07-27 09:54:08 +03:00
form . console . Text + = "Unable to purge whitelist.\r\n" ;
2018-07-16 22:03:51 +03:00
}
}
try {
2018-07-27 09:54:08 +03:00
response = ( HttpWebResponse ) WebRequest . Create ( @"http://localhost:26762/api/v1/hidguardian/whitelist/add/" + pid ) . GetResponse ( ) ; // add BetterJoyForCemu to allowed processes
2018-07-16 22:03:51 +03:00
} catch ( Exception e ) {
2018-07-27 09:54:08 +03:00
form . console . Text + = "Unable to add program to whitelist.\r\n" ;
2018-07-16 22:03:51 +03:00
}
2018-07-27 09:54:08 +03:00
} else {
form . console . Text + = "HidGuardian is disabled.\r\n" ;
2018-07-16 22:03:51 +03:00
}
emClient = new ViGEmClient ( ) ; // Manages emulated XInput
foreach ( NetworkInterface nic in NetworkInterface . GetAllNetworkInterfaces ( ) ) {
// Get local BT host MAC
if ( nic . NetworkInterfaceType ! = NetworkInterfaceType . FastEthernetFx & & nic . NetworkInterfaceType ! = NetworkInterfaceType . Wireless80211 ) {
if ( nic . Name . Split ( ) [ 0 ] = = "Bluetooth" ) {
btMAC = nic . GetPhysicalAddress ( ) ;
}
}
}
mgr = new JoyconManager ( ) ;
mgr . form = form ;
mgr . Awake ( ) ;
2018-08-18 20:52:34 +03:00
mgr . CheckForNewControllers ( ) ;
2018-07-16 22:03:51 +03:00
mgr . Start ( ) ;
server = new UdpServer ( mgr . j ) ;
server . form = form ;
2018-08-18 14:21:20 +03:00
server . Start ( IPAddress . Parse ( ConfigurationManager . AppSettings [ "IP" ] ) , Int32 . Parse ( ConfigurationManager . AppSettings [ "Port" ] ) ) ;
2018-07-16 22:03:51 +03:00
timer = new HighResTimer ( pollsPerSecond , new HighResTimer . ActionDelegate ( mgr . Update ) ) ;
timer . Start ( ) ;
form . console . Text + = "All systems go\r\n" ;
}
public static void Stop ( ) {
try {
2018-08-18 14:21:20 +03:00
HttpWebResponse response = ( HttpWebResponse ) WebRequest . Create ( @"http://localhost:26762/api/v1/hidguardian/whitelist/remove/" + pid ) . GetResponse ( ) ;
2018-07-16 22:03:51 +03:00
} catch ( Exception e ) {
form . console . Text + = "Unable to remove program from whitelist.\r\n" ;
}
2019-01-19 14:47:08 +00:00
if ( Boolean . Parse ( ConfigurationManager . AppSettings [ "PurgeAffectedDevices" ] ) ) {
try {
HttpWebResponse r1 = ( HttpWebResponse ) WebRequest . Create ( @"http://localhost:26762/api/v1/hidguardian/affected/purge/" ) . GetResponse ( ) ;
} catch { }
}
2018-07-16 22:03:51 +03:00
server . Stop ( ) ;
timer . Stop ( ) ;
mgr . OnApplicationQuit ( ) ;
2018-08-18 14:21:20 +03:00
form . console . Text + = "" ;
2018-07-16 22:03:51 +03:00
}
static void Main ( string [ ] args ) {
Application . EnableVisualStyles ( ) ;
Application . SetCompatibleTextRenderingDefault ( false ) ;
form = new MainForm ( ) ;
Application . Run ( form ) ;
}
}
}