Files
CydandClaude Fable 5 68ca9b6f15 VDB: honor the VGA-DAC pixel-mask (RP drives it; BT left it 0xFF); park Mac mirror
RP experiment finding: RP runs on the BT pod emulator (shared MUNGA stack) but
its cockpit VDB heads mis-decode. Root causes captured live (VDB_PALDUMP +
vpxresp log): (1) framebuffer identical to BT (mode 0x111, 640x480); (2) RP
loads pal0 (BT's dynamic color-radar palette) as ALL ZEROS -> radar head black;
(3) RP actively drives the VGA-DAC pixel-mask register that BT leaves at 0xFF,
and pal_draw ignored it -> likely the MFD garble.

Device change: pixel-mask writes now log their VALUE on change
("# VDB pixel-mask[gN] = 0xXX"); pal_draw ANDs the palette index with the
group mask under VDB_APPLYMASK=1 (default off = BT-identical); masks default to
0xFF in vdb_reset so an un-driven group still decodes. Built clean under MINGW64.

Also: Mac Console 4.10 retired from regular use (host .NET console is the daily
driver); tap2_mirror.py PARKED with revive notes for console A/B only. The
SendToRxAdapters registry stays set (serves the .NET host console).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 14:53:22 -05:00

140 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""[PARKED 2026-07-10] The SheepShaver/Mac Console 4.10 is retired from
regular use -- the modern host-side .NET TeslaConsole is the daily driver
(it talks to the pod directly via the bridge IP + SendToRxAdapters; no
mirror needed). Keep this tool for the ONE case it exists for: running the
1996 Mac console ALONGSIDE the .NET console for A/B testing. To revive: start
a pod, then `pythonw net-tools/tap2_mirror.py`, and bring up SheepShaver +
Console 4.10 (see NET-NOTES.md "BOTH CONSOLES AT ONCE"). Verified working
2026-07-09 (Console 4.10 ran a full mission through it). Constants at top
(pod NE2000 MAC, TAP2 GUID) may need refreshing if the adapters change.
----------------------------------------------------------------------------
Pod->Mac mirror for the SendToRx era: with Npcap's SendToRxAdapters set
on the Network Bridge (so the host .NET console hears the pod), the pod's
injected frames no longer reach the TAP members -- SheepShaver goes deaf.
Every other path still works. This daemon closes the one missing hop:
capture pod-sourced frames destined for the Mac (or broadcast) on the
bridge, and write them into TAP2's free application side; the Windows
bridge then forwards them member->member to TAP1 where SheepShaver reads
them. Result: BOTH consoles (Mac 4.10 + host .NET) work at once.
A short-lived frame-hash dedup breaks the reinjection loop (the TAP2 copy
is re-captured on the bridge; identical bytes within the window = skip).
SECOND DIRECTION (required!): writing pod-sourced frames into TAP2 teaches
the Windows bridge that the pod's MAC lives on TAP2 -- from then on the
Mac's UNICAST frames to the pod are steered to TAP2's app side instead of
up the miniport where the pod's pcap listens. So a reader thread drains
TAP2 and re-injects those frames on the bridge (under SendToRxAdapters the
injection is RX-indicated up, exactly where the pod hears it). Frames the
pod itself sourced are never re-injected (loop guard).
Run with any CPython (ctypes only). Stop with Ctrl+C / kill.
"""
import ctypes, os, struct, sys, threading, time, zlib
from ctypes import wintypes as wt
BRIDGE = rb"\Device\NPF_{5DB5521D-2D56-40E8-9E3D-3B36C9EE7C8F}"
TAP2_PATH = r"\\.\Global\{C78EA2D1-6120-4944-A949-786B1B963CB5}.tap"
POD_MAC = bytes.fromhex("ce3d72673869") # DOSBox NE2000 (stable across runs)
MAC_MAC = bytes.fromhex("525400e73ef0") # SheepShaver (etherpermanentaddress)
BCAST = b"\xff" * 6
os.add_dll_directory(r"C:\Windows\System32\Npcap")
pc = ctypes.WinDLL(r"C:\Windows\System32\Npcap\wpcap.dll")
pc.pcap_open_live.restype = ctypes.c_void_p
pc.pcap_open_live.argtypes = [ctypes.c_char_p] + [ctypes.c_int]*3 + [ctypes.c_char_p]
pc.pcap_next_ex.restype = ctypes.c_int
pc.pcap_next_ex.argtypes = [ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p),
ctypes.POINTER(ctypes.c_void_p)]
pc.pcap_setnonblock.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_char_p]
class PktHdr(ctypes.Structure):
_fields_ = [("s", ctypes.c_long), ("u", ctypes.c_long),
("cl", ctypes.c_uint), ("l", ctypes.c_uint)]
k32 = ctypes.WinDLL("kernel32", use_last_error=True)
GENERIC_RW = 0xC0000000
OPEN_EXISTING = 3
FILE_ATTRIBUTE_SYSTEM = 0x4
tap = k32.CreateFileW(TAP2_PATH, GENERIC_RW, 0, None, OPEN_EXISTING,
FILE_ATTRIBUTE_SYSTEM, None)
if tap == wt.HANDLE(-1).value:
sys.exit(f"cannot open TAP2 app side ({ctypes.get_last_error()}) -- is "
"another process attached?")
# TAP_IOCTL_SET_MEDIA_STATUS(1): belt & braces alongside the registry force
TAP_IOCTL_SET_MEDIA_STATUS = 0x22C018
status = ctypes.c_ulong(1)
ret = ctypes.c_ulong(0)
k32.DeviceIoControl(tap, TAP_IOCTL_SET_MEDIA_STATUS, ctypes.byref(status), 4,
ctypes.byref(status), 4, ctypes.byref(ret), None)
err = ctypes.create_string_buffer(256)
cap = pc.pcap_open_live(BRIDGE, 65535, 1, 20, err)
if not cap:
sys.exit(f"pcap open failed: {err.value.decode()}")
pc.pcap_setnonblock(cap, 1, err)
pc.pcap_sendpacket.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int]
# a second pcap handle for the reader thread's re-injections (thread safety)
inj = pc.pcap_open_live(BRIDGE, 65535, 0, 20, err)
if not inj:
sys.exit(f"pcap inject-handle open failed: {err.value.decode()}")
reinjected = [0]
def tap_reader():
"""Drain frames the bridge steers INTO TAP2 (unicasts to MACs it has
learned on this port -- i.e. the Mac's frames to the pod once the
mirror has been writing) and re-inject them on the bridge so the
pod's capture receives them. Never re-inject pod-sourced frames."""
buf = ctypes.create_string_buffer(2048)
n = ctypes.c_ulong(0)
while True:
ok = k32.ReadFile(tap, buf, 2048, ctypes.byref(n), None)
if not ok or n.value < 14:
time.sleep(0.002)
continue
frame = buf.raw[:n.value]
if frame[6:12] == POD_MAC:
continue
pc.pcap_sendpacket(inj, frame, len(frame))
reinjected[0] += 1
threading.Thread(target=tap_reader, daemon=True).start()
print(f"pod->Mac mirror up: bridge capture -> TAP2 app side "
f"(pod {POD_MAC.hex(':')}, mac {MAC_MAC.hex(':')}); "
f"TAP2 reader re-injects bridge-steered frames", flush=True)
recent = {} # frame crc32 -> expiry time (loop breaker)
hdr = ctypes.c_void_p(); dat = ctypes.c_void_p()
mirrored = dropped_dup = 0
last_report = time.time()
while True:
r = pc.pcap_next_ex(cap, ctypes.byref(hdr), ctypes.byref(dat))
now = time.time()
if r == 1:
ph = ctypes.cast(hdr, ctypes.POINTER(PktHdr)).contents
if ph.cl >= 14:
frame = ctypes.string_at(dat, ph.cl)
if frame[6:12] == POD_MAC and (frame[0:6] == MAC_MAC
or frame[0:6] == BCAST):
h = zlib.crc32(frame)
exp = recent.get(h)
if exp is not None and exp > now:
dropped_dup += 1
else:
recent[h] = now + 0.2
written = ctypes.c_ulong(0)
k32.WriteFile(tap, frame, len(frame),
ctypes.byref(written), None)
mirrored += 1
else:
time.sleep(0.005)
if now - last_report > 30:
last_report = now
for h in [k for k, v in recent.items() if v <= now]:
del recent[h]
print(f"mirrored={mirrored} dup_dropped={dropped_dup} "
f"reinjected={reinjected[0]}", flush=True)