Skip to content

Instantly share code, notes, and snippets.

@y-ack
Last active April 28, 2019 18:55
Show Gist options
  • Save y-ack/2d3337ca37371a5428c8f63b429008e8 to your computer and use it in GitHub Desktop.
Save y-ack/2d3337ca37371a5428c8f63b429008e8 to your computer and use it in GitHub Desktop.
[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