#!/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)