#!/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, 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 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()