mirror of
https://github.com/project-slippi/Nintendont.git
synced 2025-10-06 00:22:40 +02:00
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:
35
kernel/net.c
35
kernel/net.c
@@ -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
163
scripts/slippi-net-test
Executable 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()
|
Reference in New Issue
Block a user