#!/usr/bin/env python3 """gateway.py - host-side "link cable adapter" for gbos networking. Wraps the emulator, owns its link-port serial (stdio), speaks SLIP, and does the real network work the Game Boy can't (DNS/TCP/HTTP). Modes: echo - bounce every frame back (loopback test) http - treat each frame as a URL, HTTP GET it, stream the body back Reply frames are typed: b'D'+chunk for data, b'E' to end the stream. usage: tools/gateway.py [--mode echo|http] [--cmd "wget example.com"] [--rom PATH] """ import os, sys, subprocess, threading, time, argparse, urllib.request EMU = os.path.expanduser("~/dev/gbc/build/sl0pboy") END, ESC, ESC_END, ESC_ESC = 0xC0, 0xDB, 0xDC, 0xDD CHUNK = 200 LIMIT = 800 # cap the fetched body (link is ~1-32 KB/s) def slip_encode(payload): out = bytearray([END]) for c in payload: if c == END: out += bytes([ESC, ESC_END]) elif c == ESC: out += bytes([ESC, ESC_ESC]) else: out.append(c) out.append(END) return bytes(out) def http_fetch(payload): url = bytes(payload).decode("latin1", "replace").strip() if not url.startswith("http"): url = "http://" + url sys.stderr.write("\n[gw] GET %s\n" % url) try: req = urllib.request.Request(url, headers={"User-Agent": "gbos/0.1"}) with urllib.request.urlopen(req, timeout=8) as r: body = r.read(LIMIT) except Exception as e: body = ("ERR: " + str(e)).encode() sys.stderr.write("[gw] %d bytes\n" % len(body)) frames = [b"D" + body[i:i+CHUNK] for i in range(0, len(body), CHUNK)] frames.append(b"E") return frames def handle(payload, mode): if mode == "http": return http_fetch(payload) return [bytes(payload)] # echo def reader(out, inp, mode): buf, in_frame, esc = bytearray(), False, False while True: b = out.read(1) if not b: return c = b[0] if c == END: if in_frame and buf: for f in handle(buf, mode): inp.write(slip_encode(f)); inp.flush() 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) else: sys.stderr.write(chr(c) if 32 <= c < 127 else ("\n" if c == 10 else ".")) sys.stderr.flush() def main(): ap = argparse.ArgumentParser() ap.add_argument("--mode", default="echo", choices=["echo", "http"]) ap.add_argument("--cmd", default="necho") ap.add_argument("--rom", default=os.path.join(os.path.dirname(__file__), "..", "gbos.gb")) a = ap.parse_args() p = subprocess.Popen([EMU, "--headless", "--uncapped", a.rom], stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0) threading.Thread(target=reader, args=(p.stdout, p.stdin, a.mode), daemon=True).start() time.sleep(1.0) p.stdin.write((a.cmd + "\n").encode()); p.stdin.flush() time.sleep(4.0) p.stdin.write(b"exit\n"); p.stdin.flush() try: p.wait(timeout=3) except Exception: p.kill() if __name__ == "__main__": main()