Something went wrong. Try again.
Native control, inspection, UI automation and evidence capture for a running Witcher 3 process.
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697"""Shared paths, ownership checks, and typed arguments. No console transport."""import datetimeimport hashlibimport jsonimport shutilimport subprocessimport sysfrom pathlib import PathROOT=Path(__file__).resolve().parent.parentfrom . import game_paths as manage # Compatibility name for existing Python clients.STATE_ROOT=manage.STATE_ROOTARTIFACTS=STATE_ROOT/'artifacts'COMMANDS={'hello','snapshot','pause','resume','timescale','teleport','spawn','cleanup','events','saves','load','exit'}
def run(*args, timeout=10): p = subprocess.run(args, capture_output=True, text=True, timeout=timeout) if p.returncode: raise RuntimeError(f'{args[0]} failed: {p.stderr.strip()[:1000]}') return p.stdout.strip()
def now(): return datetime.datetime.now(datetime.timezone.utc).isoformat()
def atomic_json(path, value): temp = path.with_suffix('.tmp') temp.write_text(json.dumps(value, indent=2) + '\n') temp.chmod(0o600) temp.replace(path)
def process_identity(pid): p = Path('/proc') / str(pid) try: argv = (p / 'cmdline').read_bytes().split(b'\0')[0].decode(errors='replace') if argv.replace('\\', '/').rsplit('/', 1)[-1].lower() != 'witcher3.exe': return None start = (p / 'stat').read_text().rsplit(')', 1)[1].split()[19] return {'pid': int(pid), 'start': start, 'executable': argv} except (OSError, IndexError): return None
def process(): found = [identity for p in Path('/proc').iterdir() if p.name.isdigit() if (identity := process_identity(p.name))] if len(found) != 1: raise RuntimeError(f'Expected one Witcher process, found {len(found)}') return found[0]
def window(identity, compiler=False): if process_identity(identity['pid']) != identity: raise RuntimeError('Game process identity changed') result = subprocess.run(['xdotool', 'search', '--pid', str(identity['pid'])], capture_output=True, text=True) found = [] for wid in result.stdout.split(): title = run('xdotool', 'getwindowname', wid) wanted = 'Script Compilation Errors' in title if compiler else title == 'The Witcher 3' if wanted: found.append(wid) if len(found) != 1: raise RuntimeError(f'Expected one matching game window, found {len(found)}') return found[0]
def validate(command, args): # Compatibility entry point; the CLI and bridge now share one constraint table. from .arguments import validate as validate_arguments if command not in COMMANDS: raise ValueError('Unsupported bridge command') validate_arguments(command, args)
def record(state, kind, data): folder = ARTIFACTS / state['session'] folder.mkdir(parents=True, exist_ok=True) with (folder / 'actions.jsonl').open('a') as f: f.write(json.dumps({'at': now(), 'kind': kind, 'data': data}) + '\n')
def bridge_source(root=None): root = root or ROOT return '\n'.join((root/'scripts'/name).read_text() for name in ( 'bridge.ws.in','observations.ws.in','encounters.ws.in','request_algorithm.ws.in','crossbow.ws.in','overlay.ws.in'))
def deploy(): manage.ensure_game_closed() receipt_path = STATE_ROOT / 'deployment.json' receipt = json.loads(receipt_path.read_text()) target = Path(receipt['mod_path']) if manage.digest(target) != receipt['mod_sha256']: raise RuntimeError('Installed bridge has changed; refusing overwrite') source = bridge_source() build = hashlib.sha256(source.encode()).hexdigest()[:16] previous = ARTIFACTS / ('bridge-' + receipt['build'] + '.ws') if not previous.exists(): shutil.copy2(target, previous) target.write_text(source.replace('@@BUILD@@', build)) receipt.update(build=build, mod_sha256=manage.digest(target)) atomic_json(receipt_path, receipt) return {'ok': True, 'build': build, 'compilation': 'unverified'}