#!/usr/bin/env python3 # viceDriver.py - launch x64sc headless with the remote text monitor, run a list of monitor # command "stages" (each stage = commands to run when the monitor becomes active), capture output. import socket, subprocess, sys, time, os, shlex SCRATCH = os.path.dirname(os.path.abspath(__file__)) def pickFreePort(): s = socket.socket() s.bind(("127.0.0.1", 0)) port = s.getsockname()[1] s.close() return port PORT = pickFreePort() def recvUntilPrompt(sock, timeout): # The monitor prompt looks like "(C:$xxxx) " or "(8:$xxxx) "; wait for it. sock.settimeout(0.5) buf = b"" deadline = time.time() + timeout while time.time() < deadline: try: chunk = sock.recv(65536) if not chunk: break buf += chunk tail = buf[-40:] if b") " in tail and (b"(C:$" in tail or b"(8:$" in tail): break except socket.timeout: pass return buf.decode("latin-1") def runStages(disk, stages, extraArgs, totalTimeout): cmd = ["xvfb-run", "-a", "x64sc", "-remotemonitor", "-remotemonitoraddress", f"127.0.0.1:{PORT}", "-drive8truedrive", "-drive8type", "1541", "-sounddev", "dummy", "-warp", "-jamaction", "2", "-autostart-warp", "-model", "ntsc", "-chdir", SCRATCH] + extraArgs + ["-autostart", disk] print("launch:", " ".join(shlex.quote(c) for c in cmd), flush=True) proc = subprocess.Popen(cmd, stdout=open(f"{SCRATCH}/vice_stdout.txt", "w"), stderr=subprocess.STDOUT) sock = None for _ in range(100): if os.path.exists(f"{SCRATCH}/vice_stdout.txt") and "bind() failed" in open(f"{SCRATCH}/vice_stdout.txt").read(): proc.kill() raise SystemExit("monitor port collision") try: sock = socket.create_connection(("127.0.0.1", PORT), timeout=1) break except OSError: time.sleep(0.3) if sock is None: proc.kill() raise SystemExit("could not connect to monitor") log = [] print(recvUntilPrompt(sock, 10), flush=True) for stageName, waitSecs, cmds in stages: print(f"=== stage: {stageName}", flush=True) for c in cmds: sock.sendall((c + "\n").encode()) out = recvUntilPrompt(sock, 30) print(f">>> {c}\n{out}", flush=True) log.append((c, out)) # last command of each stage is expected to resume emulation ("x" / "g"); wait for next break if waitSecs: out = recvUntilPrompt(sock, waitSecs) print(f"--- resumed; monitor re-entered with:\n{out}", flush=True) try: sock.sendall(b"quit\n") except OSError: pass time.sleep(1) proc.kill() proc.wait() return log if __name__ == "__main__": disk = os.path.abspath(sys.argv[1]) stages = [ ("setup", 240, ["break 0800", "x"]), ("dump at 0800", 0, [ "r", "bank ram", f"bsave \"{SCRATCH}/ram_at_0800.bin\" 0 0000 ffff", "bank cpu", "m 0000 00ff", "dev 8:", "r", f"bsave \"{SCRATCH}/drive_at_0800.bin\" 0 0000 07ff", "dev c:", "x" ]), ] runStages(disk, stages, [], 300)