Last active
April 28, 2019 18:55
-
-
Save y-ack/2d3337ca37371a5428c8f63b429008e8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[CmdletBinding()] | |
Param( | |
[uint32]$first_packet = 0, | |
[int]$port = 26760, | |
[System.Net.IPAddress]$address = [System.Net.IPAddress]::Parse("127.0.0.1"), | |
[bool]$listen_thread = $false | |
) | |
$packetcount = $first_packet | |
#keeps track of how many more frames a packet should be sent for | |
$activePackets = @(0,0,0) | |
$OnUdpData = @" | |
using System.Net.Sockets; | |
public enum PacketType { | |
Version = 0x00100000, | |
PortInfo = 0x00100001, | |
PadData = 0x00100002 | |
} | |
public enum Magic { | |
CLIENT_MAGIC = 0x43555344, | |
SERVER_MAGIC = 0x53555344 | |
} | |
public class Header { | |
public Magic magic {get; set;} | |
public System.UInt16 protocol {get; set;} //protocol version | |
public System.UInt16 payload_len {get; set;} | |
public System.UInt32 crc {get; set;} | |
public System.UInt32 id {get; set;} | |
public PacketType type {get; set;} | |
public Header() {} | |
public Header(byte[] data) { | |
this.magic = (Magic)System.BitConverter.ToUInt32(data,0); | |
this.protocol = System.BitConverter.ToUInt16(data,4); | |
this.payload_len = System.BitConverter.ToUInt16(data,6); | |
this.crc = System.BitConverter.ToUInt32(data,8); | |
this.id = System.BitConverter.ToUInt32(data,12); | |
this.type = (PacketType)System.BitConverter.ToUInt32(data,16); | |
} | |
public byte[] toBytes() { | |
byte[] packet = new byte[20]; | |
System.Buffer.BlockCopy(System.BitConverter.GetBytes((int)this.magic), 0, packet, 0, 4); | |
System.Buffer.BlockCopy(System.BitConverter.GetBytes(this.protocol), 0, packet, 4, 2); | |
System.Buffer.BlockCopy(System.BitConverter.GetBytes(this.payload_len),0, packet, 6, 2); | |
System.Buffer.BlockCopy(System.BitConverter.GetBytes(this.crc), 0, packet, 8, 4); | |
System.Buffer.BlockCopy(System.BitConverter.GetBytes(this.id), 0, packet, 12, 4); | |
System.Buffer.BlockCopy(System.BitConverter.GetBytes((int)this.type), 0, packet, 16, 4); | |
return packet; | |
} | |
} | |
public class UdpCrap { | |
public static byte[] lastMessage {get; set;} | |
public UdpCrap() { | |
UdpCrap.lastMessage = new byte[20]; | |
} | |
public static void OnUdpData(System.IAsyncResult result) { | |
System.Net.Sockets.UdpClient socket = result.AsyncState as System.Net.Sockets.UdpClient; | |
System.Net.IPEndPoint source = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("127.0.0.1"), 26760); | |
byte[] message = socket.EndReceive(result, ref source); | |
lastMessage = message; | |
Header header = new Header(message); | |
socket.BeginReceive(new System.AsyncCallback(OnUdpData), socket); | |
} | |
public static void StartReceiving() { | |
System.Net.IPEndPoint endpoint = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("127.0.0.1"), 26760); | |
System.Net.Sockets.UdpClient socket = new System.Net.Sockets.UdpClient(); | |
socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); | |
socket.Client.Bind(endpoint); | |
System.Console.WriteLine(endpoint); | |
socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); | |
socket.BeginReceive(new System.AsyncCallback(OnUdpData), socket); | |
} | |
} | |
"@ | |
Add-Type -TypeDefinition $OnUdpData | |
# essentially constants | |
$ResponseHeader = [Header]::new() | |
$ResponseHeader.magic = [Magic]::SERVER_MAGIC | |
$ResponseHeader.protocol = 1001 | |
$ResponseHeader.id = 14522 | |
[uint16]$version = 1001 | |
[uint32]$MAX_PORTS = 4 | |
$endpoint = [System.Net.IPEndPoint]::new($address, $port) | |
$ssocket = [System.Net.Sockets.UdpClient]::new() | |
$ssocket.Client.SetSocketOption([System.Net.Sockets.SocketOptionLevel]::Socket, | |
[System.Net.Sockets.SocketOptionName]::ReuseAddress, | |
$true) | |
<# | |
enum PacketType { | |
Version = 0x00100000 | |
PortInfo = 0x00100001 | |
PadData = 0x00100002 | |
} | |
enum Magic { | |
CLIENT_MAGIC = 0x43555344 | |
SERVER_MAGIC = 0x53555344 | |
} | |
class Header { | |
[Magic]$magic | |
[uint16]$protocol #protocol version | |
[uint16]$payload_len | |
[uint32]$crc | |
[uint32]$id | |
[PacketType]$type | |
Header() {} | |
Header([byte[]]$data) { | |
$this.magic = [System.BitConverter]::ToUInt32($data,0) | |
$this.protocol = [System.BitConverter]::ToUInt16($data,4) | |
$this.payload_len = [System.BitConverter]::ToUInt16($data,6) | |
$this.crc = [System.BitConverter]::ToUInt32($data,8) | |
$this.id = [System.BitConverter]::ToUInt32($data,12) | |
$this.type = [PacketType][System.BitConverter]::ToUInt32($data,16) | |
} | |
[byte[]]toBytes() { | |
[byte[]]$packet = [byte[]]::new(0) | |
$packet += [System.BitConverter]::GetBytes($this.magic.value__) | |
$packet += [System.BitConverter]::GetBytes($this.protocol) | |
$packet += [System.BitConverter]::GetBytes($this.payload_len) | |
$packet += [System.BitConverter]::GetBytes($this.crc) | |
$packet += [System.BitConverter]::GetBytes($this.id) | |
$packet += [System.BitConverter]::GetBytes($this.type.value__) | |
return $packet | |
} | |
} | |
#> | |
enum Flags { | |
AllPorts | |
Id | |
Mac | |
} | |
class PortInfoRequest { | |
[uint32]$pad_count #number of ports to request data for | |
[byte[]]$port = [byte[]]::new($MAX_PORTS) | |
PortInfoRequest() {} | |
PortInfoRequest([byte[]]$requestinfo) { | |
$this.pad_count = [System.BitConverter]::ToUInt32($requestinfo,0) | |
$this.port = $requestinfo[4..7] | |
} | |
} | |
class PadDataRequest { | |
[Flags]$flags | |
[byte]$port_id | |
[byte[]]$mac = @(0,0,0,0,0,0) | |
PadDataRequest() {} | |
PadDataRequest([byte[]]$requestinfo) { | |
$this.flags = $requestinfo[0] | |
$this.port_id = $requestinfo[1] | |
$this.mac = $requestinfo[2..7] | |
} | |
} | |
class PortInfoResponse { | |
[byte]$id = 1 | |
[byte]$state = 1 | |
[byte]$model = 145 | |
[byte]$connection_type = 0 | |
[byte[]]$MacAddress = @(0,0,0,0,0,0) | |
[byte]$battery = 100 | |
[byte]$is_pad_active = 1 | |
[byte[]]toBytes() { | |
[byte[]]$packet = [byte[]]::new(0) | |
$packet += $this.id | |
$packet += $this.state | |
$packet += $this.model | |
$packet += $this.connection_type | |
$packet += $this.MacAddress | |
$packet += $this.battery | |
$packet += $this.is_pad_active | |
return $packet | |
} | |
} | |
class PadDataResponse { | |
#PadData | |
[PortInfoResponse]$PortInfo = [PortInfoResponse]::new() | |
#region unimportant | |
[uint32]$packet_count = 0 | |
[uint16]$d_button = 0 #Share,L3,R3,Options,Up,Right,Down,Left,L2,R2,L1,R1,Tri,O,X,Sq | |
[byte]$home_button | |
[byte]$touch_hard_press #click | |
[byte]$left_stick_x = 127 | |
[byte]$left_stick_y = 127 | |
[byte]$right_stick_x = 127 | |
[byte]$right_stick_y = 127 | |
# analog_buttons | |
[byte]$button8 | |
[byte]$button7 | |
[byte]$button6 | |
[byte]$button5 | |
[byte]$button12 | |
[byte]$button11 | |
[byte]$button10 | |
[byte]$button9 | |
[byte]$button16 | |
[byte]$button15 | |
[byte]$button14 | |
[byte]$button13 | |
#endregion | |
# TouchPad | |
#(important part) | |
[byte]$touch1_is_active = 0 | |
[byte]$touch1_id = 0 | |
[uint16]$touch1_x | |
[uint16]$touch1_y | |
[byte]$touch2_is_active = 0 | |
[byte]$touch2_id = 0 | |
[uint16]$touch2_x | |
[uint16]$touch2_y | |
[uint64]$motion_timestamp | |
# Accelerometer | |
[float]$accel_x | |
[float]$accel_y | |
[float]$accel_z | |
# Gyroscope | |
[float]$gyro_pitch | |
[float]$gyro_yaw | |
[float]$gyro_roll | |
[byte[]]toBytes() { | |
[byte[]]$packet = [byte[]]::new(0) | |
$packet += $this.PortInfo.toBytes() | |
$packet += [System.BitConverter]::GetBytes($this.packet_count) | |
$packet += [System.BitConverter]::GetBytes($this.d_button) | |
#region paddata packet construction | |
$packet += $this.home_button | |
$packet += $this.touch_hard_press | |
$packet += $this.left_stick_x | |
$packet += $this.left_stick_y | |
$packet += $this.right_stick_x | |
$packet += $this.right_stick_y | |
$packet += $this.button8 | |
$packet += $this.button7 | |
$packet += $this.button6 | |
$packet += $this.button5 | |
$packet += $this.button12 | |
$packet += $this.button11 | |
$packet += $this.button10 | |
$packet += $this.button9 | |
$packet += $this.button16 | |
$packet += $this.button15 | |
$packet += $this.button14 | |
$packet += $this.button13 | |
$packet += $this.touch1_is_active | |
$packet += $this.touch1_id | |
$packet += [System.BitConverter]::GetBytes($this.touch1_x) | |
$packet += [System.BitConverter]::GetBytes($this.touch1_y) | |
$packet += $this.touch2_is_active | |
$packet += $this.touch2_id | |
$packet += [System.BitConverter]::GetBytes($this.touch2_x) | |
$packet += [System.BitConverter]::GetBytes($this.touch2_y) | |
$packet += [System.BitConverter]::GetBytes($this.motion_timestamp) | |
$packet += [System.BitConverter]::GetBytes($this.accel_x) | |
$packet += [System.BitConverter]::GetBytes($this.accel_y) | |
$packet += [System.BitConverter]::GetBytes($this.accel_z) | |
$packet += [System.BitConverter]::GetBytes($this.gyro_pitch) | |
$packet += [System.BitConverter]::GetBytes($this.gyro_yaw) | |
$packet += [System.BitConverter]::GetBytes($this.gyro_roll) | |
#endregion | |
return $packet | |
} | |
} | |
#region CRC32 | |
# reference: | |
# https://raw.githubusercontent.com/randomouscrap98/MonolithicExtensions/82eb31311ee6e829305ea2305cd3742431461553/MonolithicExtensions.Portable/HashServices.cs | |
function ReverseBits([uint32]$dword) { | |
$dword = (($dword -band [uint32]"0x55555555") -shl 1) -bor (($dword -shr 1) -band [uint32]"0x55555555") | |
$dword = (($dword -band [uint32]"0x33333333") -shl 2) -bor (($dword -shr 2) -band [uint32]"0x33333333") | |
$dword = (($dword -band [uint32]"0xf0f0f0f") -shl 4) -bor (($dword -shr 4) -band [uint32]"0xf0f0f0f") | |
$dword = ($dword -shl 24) -bor (($dword -band [uint32]"0xff00") -shl 8) -bor (($dword -shr 8) -band [uint32]"0xff00") -bor ($dword -shr 24) | |
return $dword | |
} | |
function checksum32 ([byte[]]$bytes) { | |
[uint32]$polynomial = 0x4c11db7 | |
[uint32]$init = [uint32]"0xffffffff" | |
[uint32]$crc = $init | |
for ([int]$b = 0; $b -lt $bytes.length; $b++) { | |
[uint32]$currentByte = ReverseBits($bytes[$b]) | |
for ([int]$i = 0; $i -le 7; $i++) { | |
if ((($crc -bxor $currentByte) -band [uint32]"0x80000000") -gt 0) { | |
$crc = ($crc -shl 1) -bxor $polynomial | |
} else { | |
$crc = $crc -shl 1 | |
} | |
$currentByte = $currentByte -shl 1 | |
} | |
} | |
return ReverseBits(-bnot $crc) | |
} | |
#endregion | |
#region keymap | |
$keymap = [System.Collections.Generic.Dictionary[String,[uint16[]]]]::new() | |
$keymap.Add('!',@(30,70)) | |
$keymap.Add('"',@(55,70)) | |
$keymap.Add('#',@(80,70)) | |
$keymap.Add('$',@(105,70)) | |
$keymap.Add('%',@(130,70)) | |
$keymap.Add('&',@(155,70)) | |
$keymap.Add('(',@(180,70)) | |
$keymap.Add(')',@(205,70)) | |
$keymap.Add('-',@(230,70)) | |
$keymap.Add('+',@(255,70)) | |
$keymap.Add('=',@(280,70)) | |
$keymap.Add('1',@(14,95)) | |
$keymap.Add('2',@(39,95)) | |
$keymap.Add('3',@(64,95)) | |
$keymap.Add('4',@(89,95)) | |
$keymap.Add('5',@(114,95)) | |
$keymap.Add('6',@(139,95)) | |
$keymap.Add('7',@(164,95)) | |
$keymap.Add('8',@(189,95)) | |
$keymap.Add('9',@(214,95)) | |
$keymap.Add('0',@(239,95)) | |
$keymap.Add('[',@(264,95)) | |
$keymap.Add(']',@(289,95)) | |
$keymap.Add('Q',@(30,120)) | |
$keymap.Add('W',@(55,120)) | |
$keymap.Add('E',@(80,120)) | |
$keymap.Add('R',@(105,120)) | |
$keymap.Add('T',@(130,120)) | |
$keymap.Add('Y',@(155,120)) | |
$keymap.Add('U',@(180,120)) | |
$keymap.Add('I',@(205,120)) | |
$keymap.Add('O',@(230,120)) | |
$keymap.Add('P',@(255,120)) | |
$keymap.Add('@',@(280,120)) | |
$keymap.Add('*',@(305,120)) | |
$keymap.Add('|',@(14,148)) | |
$keymap.Add('A',@(39,148)) | |
$keymap.Add('S',@(64,148)) | |
$keymap.Add('D',@(89,148)) | |
$keymap.Add('F',@(114,148)) | |
$keymap.Add('G',@(139,148)) | |
$keymap.Add('H',@(164,148)) | |
$keymap.Add('J',@(189,148)) | |
$keymap.Add('K',@(214,148)) | |
$keymap.Add('L',@(239,148)) | |
$keymap.Add(';',@(264,148)) | |
$keymap.Add(':',@(289,148)) | |
$keymap.Add('?',@(30,174)) | |
$keymap.Add('Z',@(55,174)) | |
$keymap.Add('X',@(80,174)) | |
$keymap.Add('C',@(105,174)) | |
$keymap.Add('V',@(130,174)) | |
$keymap.Add('B',@(155,174)) | |
$keymap.Add('N',@(180,174)) | |
$keymap.Add('M',@(205,174)) | |
$keymap.Add(',',@(230,174)) | |
$keymap.Add('.',@(255,174)) | |
$keymap.Add("'",@(280,174)) | |
#endregion | |
function Receive-Request { | |
[CmdletBinding()] | |
Param() | |
#while ($true) { | |
$activePackets = @(0,0,0) | |
$message = [byte[]]::new(160) | |
#$ssocket.Connect($endpoint) | |
write-host $endpoint | |
$ssocket.Client.Bind($endpoint) | Out-Null | |
$ssocket.Client.ReceiveFrom($message, [ref]$endpoint) | Out-Null | |
write-host $endpoint | |
$ssocket.Close() | Out-Null | |
#$ssocket.close() | |
$header = [Header]::new($message) | |
if ($VerbosePreference -ne "SilentlyContinue") { | |
$header | Format-Table | |
} | |
switch ($header.type) { | |
Version {} | |
PortInfo { | |
# Well, we can read the PortInfo request for recordkeeping, | |
# But I think it can be ignored pretty safely. | |
# We assume it asks for controller 0, and it's not meaningful | |
# to have multiple keyboards anyway. | |
$PortInfo = [PortInfoRequest]::new($message[16..(16+$header.payload_len)]) | |
if ($VerbosePreference -ne "SilentlyContinue") { | |
$PortInfo | Format-Table | |
} | |
$activePackets[1] = 3 | |
} | |
PadData { | |
$PadRequest = [PadDataRequest]::new($message[16..(16+$header.payload_len)]) | |
if ($VerbosePreference -ne "SilentlyContinue") { | |
$PadRequest | Format-Table | |
} | |
# sad magic number because the enum has special values | |
$activePackets[2] = 3 | |
} | |
} | |
return $activePackets | |
#} | |
} | |
function Send-PortInfo { | |
[CmdletBinding()]Param() | |
$PortInfoResponse = [PortInfoResponse]::new() | |
[byte[]]$response = [byte[]]::new(0) | |
$ResponseHeader.type = [PacketType]::PortInfo | |
# +4 comes from length of [PacketType], which "isn't part of the header" | |
# Ok, whatever you say, I guess. | |
$ResponseHeader.payload_len = $PortInfoResponse.toBytes().Length + 4 | |
# zero the crc field, calculate checksum for packet, then set crc field | |
$ResponseHeader.crc = 0 | |
$response = $ResponseHeader.toBytes() + $PortInfoResponse.toBytes() | |
$ResponseHeader.crc = checksum32($response) | |
# and reconstruct packet | |
$response = $ResponseHeader.toBytes() + $PortInfoResponse.toBytes() | |
if ($VerbosePreference -ne "SilentlyContinue") { | |
#$ResponseHeader | Format-Table | |
$PortInfoResponse | Format-Table | |
} | |
$ssocket.Send($response, $response.Length, $endpoint) | Out-Null | |
} | |
function Send-PadData { | |
[CmdletBinding()] | |
Param([bool]$touch_active = 0,[uint16[]]$touch_coords = @(0,0)) | |
[byte[]]$response = [byte[]]::new(0) | |
$ResponseHeader.type = [PacketType]::PadData | |
$PadDataResponse = [PadDataResponse]::new() | |
$PadDataResponse.packet_count = $packetcount++ | |
$PadDataResponse.touch1_is_active = $touch_active | |
$PadDataResponse.touch1_x = $touch_coords[0] | |
$PadDataResponse.touch1_y = $touch_coords[1] | |
$PadDataBytes = $PadDataResponse.toBytes() | |
$ResponseHeader.payload_len = $PadDataBytes.Length + 4 | |
$ResponseHeader.crc = 0 | |
$response = $ResponseHeader.toBytes() + $PadDataBytes | |
$ResponseHeader.crc = checksum32($response) | |
$response = $ResponseHeader.toBytes() + $PadDataBytes | |
$ssocket.Send($response, $response.Length, $endpoint) | Out-Null | |
if ($VerbosePreference -ne "SilentlyContinue") { | |
$PadDataResponse | Select-Object packet_count,d_button,touch1_is_active,touch1_x,touch1_y | Format-Table | |
} | |
} | |
function Start-SmileBASICKeyboardServer { | |
[CmdletBinding()] | |
Param() | |
[System.Management.Automation.Job]$background | |
try { | |
$background = Start-Job -FilePath $PSCommandPath -ArgumentList @($first_packet,$port,$address,$true) | |
while($true) { | |
if ($background.State -eq "Completed") { | |
$newPackets = Receive-Job $background | |
# ignoring version requests for now | |
$activePackets[1] = [Math]::max($activePackets[1], $newPackets[1]) | |
$activePackets[2] = [Math]::max($activePackets[2], $newPackets[2]) | |
if ($VerbosePreference -ne "SilentlyContinue") { | |
"Updated out-packet information: $activePackets" | |
} | |
Stop-Job $background | |
$background = Start-Job -FilePath $PSCommandPath -ArgumentList @($first_packet,$port,$address,$true) | |
} | |
# PortInfo Packets Requested | |
if ($activePackets[1]) { | |
$activePackets[1]-- | |
Send-PortInfo | |
} | |
# PadData Packets Requested | |
if ($activePackets[2]) { | |
$activePackets[2]-- | |
# If the user is holding Left Control, send calibration data | |
if ([console]::KeyAvailable -and ([console]::ReadKey().Modifiers -eq "Control")) { | |
Send-PadData $true @(0,0) | |
Start-Sleep -Seconds 1 | |
Send-PadData $true @(320,240) | |
} else { | |
$coords = @(0,0) | |
if ([console]::KeyAvailable -and $keymap[[System.Console]::ReadKey().KeyChar]) { | |
$coords = $keymap[[System.Console]::ReadKey().KeyChar] | |
} | |
Send-PadData ($true -or [console]::KeyAvailable) $coords | |
$packetcount++ | |
Send-PadData $false $coords | |
$packetcount++ | |
} | |
} | |
# https://github.com/citra-emu/citra-nightly/blob/c2b514cb971907e7a7b1e46eda39c2bf90b94a36/src/citra_qt/configuration/configure_input.cpp#L384 | |
# but there's a joystick thing that maybe sleeps for 10 | |
Start-Sleep -milliseconds 200 | |
} | |
} catch { | |
"$($Error[0])" | |
} finally { Get-Job | Stop-Job | Remove-Job } | |
} | |
function Mini { | |
#[System.Net.Sockets.UdpClient]$socket = [System.Net.Sockets.UdpClient]::new($endpoint) | |
[UdpCrap]::StartReceiving() | |
while ($true) { | |
Write-Host ([UdpCrap]::lastMessage) | |
Send-PortInfo | |
Send-PadData $false (0,0) | |
Start-Sleep -Milliseconds 200 | |
} | |
} | |
Mini | |
pause | |
if ($listen_thread) { | |
return Receive-Request | |
} else { | |
Start-SmileBASICKeyboardServer | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment