aboutsummaryrefslogtreecommitdiffstats
path: root/tools/gateway.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gateway.py')
-rw-r--r--tools/gateway.py69
1 files changed, 69 insertions, 0 deletions
diff --git a/tools/gateway.py b/tools/gateway.py
new file mode 100644
index 0000000..81a70af
--- /dev/null
+++ b/tools/gateway.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+"""gateway.py - a host-side "link cable adapter" for gbos networking.
+
+Wraps the emulator, owns its link-port serial (stdio), and speaks SLIP. This
+milestone just ECHOES every frame back (loopback), so `necho` on gbos can prove
+a framed round-trip. Console (ASCII) bytes on the same channel are printed.
+
+usage: tools/gateway.py [rom]
+"""
+import os, sys, subprocess, threading, time
+
+EMU = os.path.expanduser("~/dev/gbc/build/sl0pboy")
+ROM = sys.argv[1] if len(sys.argv) > 1 else os.path.join(os.path.dirname(__file__), "..", "gbos.gb")
+
+END, ESC, ESC_END, ESC_ESC = 0xC0, 0xDB, 0xDC, 0xDD
+
+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 handle_frame(payload):
+ """The 'network'. For now: echo. (Later: DNS/TCP/HTTP/IRC.)"""
+ sys.stderr.write("\n[gw] frame in : %r\n" % bytes(payload))
+ sys.stderr.write("[gw] frame out: %r (echo)\n" % bytes(payload))
+ return bytes(payload)
+
+def reader(out, inp):
+ 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:
+ reply = handle_frame(buf)
+ inp.write(slip_encode(reply)); 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():
+ p = subprocess.Popen([EMU, "--headless", "--uncapped", ROM],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0)
+ threading.Thread(target=reader, args=(p.stdout, p.stdin), daemon=True).start()
+ time.sleep(1.0) # boot to the shell
+ p.stdin.write(b"necho\n"); p.stdin.flush()
+ time.sleep(2.0) # let the framed round-trip happen
+ p.stdin.write(b"exit\n"); p.stdin.flush()
+ try: p.wait(timeout=3)
+ except Exception: p.kill()
+
+if __name__ == "__main__":
+ main()