91 lines
3.2 KiB
Python
91 lines
3.2 KiB
Python
#!/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)
|