1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
#!/usr/bin/env python3
"""netboot - bring up real networking (TUN + NAT) and launch sl0pboy with your
own arguments, link port bridged to the TUN over a unix socket.
Unlike the headless `tunbridge`, this keeps the LCD + on-screen keyboard, so you
get a fully interactive, networked Game Boy: the console is the OSK, the link
port is the network - no sharing, no confusion.
sudo tools/netboot --sixel --chrome gbos.gb
sudo tools/netboot gbos.gb
Then, on the Game Boy: press SELECT for the OSK and type e.g.
wget example.com
ping 1.1.1.1
nslookup sl0p.foo
The GB is 10.0.0.2; the host/gateway is 10.0.0.1. Ctrl-C (or quit the emu) to
tear everything down. Requires /dev/net/tun and root.
"""
import os, sys, fcntl, struct, subprocess, socket, threading, atexit
HOME = os.path.expanduser("~" + (os.environ.get("SUDO_USER") or ""))
EMU = os.path.join(HOME, "dev/gbc/build/sl0pboy")
SOCKPATH = "/tmp/sl0pboy-net.sock"
IFACE, GW_IP, GB_NET, MTU = "gbtun0", "10.0.0.1", "10.0.0.0/24", 256
END, ESC, ESC_END, ESC_ESC = 0xC0, 0xDB, 0xDC, 0xDD
TUNSETIFF, IFF_TUN, IFF_NO_PI = 0x400454ca, 0x0001, 0x1000
def slip_encode(pkt):
o = bytearray([END])
for c in pkt:
if c == END: o += bytes([ESC, ESC_END])
elif c == ESC: o += bytes([ESC, ESC_ESC])
else: o.append(c)
o.append(END)
return bytes(o)
def sh(cmd):
subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def wan_iface():
try:
return subprocess.check_output("ip route show default | awk '{print $5; exit}'",
shell=True).decode().strip() or "eth0"
except Exception:
return "eth0"
def main():
if os.geteuid() != 0:
sys.exit("netboot: must run as root (sudo)")
args = sys.argv[1:]
if not args:
sys.exit("usage: sudo netboot [emulator options] <rom>")
rom, opts = os.path.abspath(args[-1]), args[:-1]
if not os.path.exists(EMU):
sys.exit("netboot: emulator not found at " + EMU)
tun = os.open("/dev/net/tun", os.O_RDWR)
fcntl.ioctl(tun, TUNSETIFF, struct.pack("16sH", IFACE.encode(), IFF_TUN | IFF_NO_PI))
wan = wan_iface()
sh("ip addr add %s/24 dev %s" % (GW_IP, IFACE))
sh("ip link set %s mtu %d up" % (IFACE, MTU))
sh("sysctl -w net.ipv4.ip_forward=1")
sh("iptables -t nat -C POSTROUTING -s %s -o %s -j MASQUERADE || "
"iptables -t nat -A POSTROUTING -s %s -o %s -j MASQUERADE" % (GB_NET, wan, GB_NET, wan))
sh("iptables -C FORWARD -i %s -j ACCEPT || iptables -I FORWARD -i %s -j ACCEPT" % (IFACE, IFACE))
sh("iptables -C FORWARD -o %s -j ACCEPT || iptables -I FORWARD -o %s -j ACCEPT" % (IFACE, IFACE))
def cleanup():
sh("iptables -t nat -D POSTROUTING -s %s -o %s -j MASQUERADE" % (GB_NET, wan))
sh("iptables -D FORWARD -i %s -j ACCEPT" % IFACE)
sh("iptables -D FORWARD -o %s -j ACCEPT" % IFACE)
try: os.unlink(SOCKPATH)
except Exception: pass
atexit.register(cleanup)
try: os.unlink(SOCKPATH)
except Exception: pass
srv = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
srv.bind(SOCKPATH); srv.listen(1)
sys.stderr.write("[netboot] %s up %s/24, NAT via %s. GB=10.0.0.2. "
"OSK (SELECT) -> wget example.com / ping 1.1.1.1\n" % (IFACE, GW_IP, wan))
emu = subprocess.Popen([EMU] + opts + ["--serial-sock", SOCKPATH, rom])
srv.settimeout(10)
try:
conn, _ = srv.accept()
except socket.timeout:
sys.stderr.write("[netboot] emulator never connected the link socket\n")
emu.kill(); return
conn.setblocking(True)
def sock_to_tun(): # Game Boy -> host
buf, in_frame, esc = bytearray(), False, False
while True:
try: chunk = conn.recv(2048)
except Exception: return
if not chunk: return
for c in chunk:
if c == END:
if in_frame and buf:
try: os.write(tun, bytes(buf))
except Exception: pass
buf, in_frame, esc = bytearray(), False, False
else:
buf, in_frame, esc = bytearray(), True, False
elif in_frame:
if esc: c = {ESC_END: END, ESC_ESC: ESC}.get(c, c); esc = False; buf.append(c)
elif c == ESC: esc = True
else: buf.append(c)
# non-frame bytes are the console mirror; it's on the LCD, ignore
def tun_to_sock(): # host -> Game Boy
while True:
try: pkt = os.read(tun, 2048)
except Exception: return
if not pkt: return
if len(pkt) < 20 or pkt[16:20] != b"\x0a\x00\x00\x02":
continue # only unicast to 10.0.0.2 (drop mcast noise)
try: conn.sendall(slip_encode(pkt))
except Exception: return
threading.Thread(target=sock_to_tun, daemon=True).start()
threading.Thread(target=tun_to_sock, daemon=True).start()
try:
emu.wait()
except KeyboardInterrupt:
emu.terminate()
if __name__ == "__main__":
main()
|