net.c comments; track a slippi-net-test client script

slippi-net-test will stream some data into stdout on your terminal.
just a super minimal example of some client ingesting slippi data
with the current networking code.
This commit is contained in:
meta
2018-08-27 23:40:40 -05:00
parent aba878d1bb
commit dcc5156bef
2 changed files with 184 additions and 14 deletions

View File

@@ -1,16 +1,20 @@
/* Mostly fixing up FIX94's net.c from the bbatest branch (ha-ha).
*
* This prints an endless stream of 0xdeadbeefs to my terminal, wee!
* You can test this with something like `nc <Wii IP> 666 | xxd`.
/* Mostly fixing up FIX94's net.c from the bbatest branch (ha-ha). This should
* be good start to exposing networking/socket primitives to other code in the
* Nintendont kernel (ie. Slippi).
*
* I think that for now it's reasonable to keep /dev/net/ip/top's file
* descriptor in some global state in this file so that we don't need to
* pass it via arguments when implementing the socket operations (so that
* the interfaces will look just like libogc's.
* descriptor in some global state in this file so that we don't need to pass
* it via arguments when implementing the socket operations (so that the
* interfaces will look just like libogc's). In some end state, we would
* expect the caller to keep track of this. This code is from the Slippi fork,
* so the assumption is that we've only got one thread dealing with that fd.
*
* Also need to be weary of the fact that Ioctls expect pointer arguments to
* be 32-byte aligned.
* See scripts/slippi-net-test for an example of dealing with streaming data
* from the network.
*
* Recall that, when issuing ioctls, all pointer arguments need to be 32-byte
* aligned (or at least, this is what the doctor tells me).
*
*/
@@ -61,6 +65,7 @@ s32 socket(s32 fd, u32 domain, u32 type, u32 protocol)
return res;
}
/* Close a socket */
s32 close(s32 fd, s32 socket)
{
s32 res;
@@ -74,6 +79,7 @@ s32 close(s32 fd, s32 socket)
return res;
}
/* Bind a name to some socket */
s32 bind(s32 fd, s32 socket, struct sockaddr *name)//, socklen_t address_len)
{
s32 res;
@@ -96,6 +102,7 @@ s32 bind(s32 fd, s32 socket, struct sockaddr *name)//, socklen_t address_len)
return res;
}
/* Listen for connections on a socket */
s32 listen(s32 fd, s32 socket, u32 backlog)
{
s32 res;
@@ -114,7 +121,7 @@ s32 listen(s32 fd, s32 socket, u32 backlog)
return res;
}
// I think ioctl writes into addr - might be a problem if it isn't aligned?
/* Accept some connection on a socket */
s32 accept(s32 fd, s32 socket)
{
s32 res;
@@ -136,6 +143,7 @@ s32 accept(s32 fd, s32 socket)
return res;
}
/* Emit a message on a socket */
s32 sendto(s32 fd, s32 socket, void *data, s32 len, u32 flags)
{
s32 res;
@@ -181,8 +189,7 @@ s32 sendto(s32 fd, s32 socket, void *data, s32 len, u32 flags)
}
/* For now, just assume that we provide an aligned address and
* disregard having to allocate heap to do memcpy things */
/* Recieve a message from some socket */
s32 recvfrom(s32 fd, s32 socket, void *mem, s32 len, u32 flags)
{
s32 res;
@@ -210,7 +217,8 @@ s32 recvfrom(s32 fd, s32 socket, void *mem, s32 len, u32 flags)
/* Connect a socket to some remote host. Pretty sure this
* assumes IPv4, speaking TCP/IP */
s32 connect(s32 fd, s32 socket, struct address *addr)
{
s32 res;
@@ -245,7 +253,6 @@ s32 top_fd __attribute__((aligned(32)));
static u8 message[0x100] __attribute__((aligned(32)));
u32 net_handler(void *arg)
{
s32 res;

163
scripts/slippi-net-test Executable file
View File

@@ -0,0 +1,163 @@
#!/usr/bin/python3
""" slippi-net-test
Naive, very hacky example code for connecting to the net thread listener PoC
and streaming some Slippi data into your terminal on stdout. Only implementing
some very basic parsing of Slippi messages here. This will probably break as
the networking code is updated.
A beautiful, safe, performant, Windows-compatible implementation has been left
as an excercise to the reader :^)
"""
from struct import pack, unpack
from sys import argv
import socket
import select
import errno
import sys
import os
if (len(argv)) < 3:
print("usage: slippi-net-test <IP Address> <port>")
exit(0)
ADDR = str(argv[1])
PORT = int(argv[2])
# These are the message types and lengths supported by this particular example.
# In reality, a Slippi client should self identify commands by emitting the
# 0x35 command to us [of length ((3*num_commands)+1)].
EVENT_PAYLOADS = 0x35
GAME_START = 0x36
PRE_FRAME = 0x37
POST_FRAME = 0x38
GAME_END = 0x39
cmd = { GAME_START: 0x160,
PRE_FRAME: 0x3b,
POST_FRAME: 0x25,
GAME_END: 0x1,
}
glob_off = 0
glob_frame = 0
glob_state = {0x0: 0, 0x1: 0, 0x2: 0, 0x3: 0}
glob_buf = bytes()
# Naive parsing of Slippi messages. Does some work on a global buffer. We're
# basically just relying on a lot of shared state here and the fact that
# there'll probably be some time in-between the file descriptor having some
# data that we can read off the wire.
def process():
global glob_off
global glob_frame
global glob_state
global glob_buf
# If we don't have enough data, this function will just return and let the
# main loop call recv() again to expand the stream
while (glob_off < len(glob_buf)):
# EVENT_PAYLOADS
if glob_buf[glob_off] == 0x35:
#print("[*] Event payload message")
glob_off += 1
payload_size = unpack(">b", glob_buf[glob_off:glob_off+1])[0]
glob_off += 1
num_commands = (payload_size-1) // 3
for i in range(0, num_commands):
command = unpack(">b", glob_buf[glob_off:glob_off+1])[0]
glob_off += 1
command_size = unpack(">H", glob_buf[glob_off:glob_off+2])[0]
glob_off += 2
#print("supported command {}, size {}".format(hex(command), hex(command_size)))
continue
# GAME_START
if glob_buf[glob_off] == 0x36:
blk = glob_buf[glob_off:glob_off+cmd[0x36]]
try:
version = hex(unpack(">L", blk[0x1:0x5])[0])
stage = hex(unpack(">H", blk[0x13:0x15])[0])
except:
break
glob_off += 1
glob_off += cmd[0x36]
continue
# PRE_FRAME
if glob_buf[glob_off] == 0x37:
blk = glob_buf[glob_off:glob_off+cmd[0x37]]
try:
frame = unpack(">i", blk[0x1:0x5])[0]
player_idx = unpack(">b", blk[0x5:0x6])[0]
state = unpack(">H", blk[0xb:0xd])[0]
glob_frame = frame
glob_state[player_idx] = state
except:
break
glob_off += 1
glob_off += cmd[0x37]
continue
# POST_FRAME
if glob_buf[glob_off] == 0x38:
blk = glob_buf[glob_off:glob_off+cmd[0x38]]
try:
frame = unpack(">i", blk[0x1:0x5])[0]
player_idx = unpack(">b", blk[0x5:0x6])[0]
state = unpack(">H", blk[0x8:0xa])[0]
glob_frame = frame
glob_state[player_idx] = state
except:
break
glob_off += 1
glob_off += cmd[0x38]
continue
# GAME_END
if glob_buf[glob_off] == 0x39:
glob_off += 1
glob_off += cmd[0x39]
continue
# Otherwise, we're SOL
else:
print("stuck in buffer at glob_off={}".format(hex(glob_off)))
exit(-1)
# Set up a socket and connect to it. This seems sufficient for a hacky PoC.
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.connect((ADDR, PORT))
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.setblocking(0)
inputs = [server]
outputs = []
# Eh, I think using select() should work alright like this. Otherwise I guess
# Python will just spin forever and turn one of your CPU cores into slag
while True:
print("frame={}, state={}".format(glob_frame, glob_state), end='\r')
readable, writable, exceptional = select.select(inputs, outputs, inputs)
for s in readable:
try:
buf = s.recv(0x5b4*3)
except socket.error as e:
if (e.args[0] == errno.EWOULDBLOCK):
process()
continue
if (len(buf) != 0):
glob_buf += buf
process()
process()
server.close()