Something went wrong. Try again.
Native control, inspection, UI automation and evidence capture for a running Witcher 3 process.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114"""Read-only, file-based proof of the standard on-foot physical RB mapping.
This is not the native runtime input map. The caller must independently excludeBoat, Horse, Swimming and other unsupported runtime contexts before dispatch."""import hashlibimport re
from . import support
MAX_BINDINGS_BYTES = 2 * 1024 * 1024CONTEXTS = ('BASE_Items', 'Combat', 'Exploration', 'ThrowHold', 'ThrowHoldChain')BUTTON = 'IK_Pad_RightShoulder'_GUIDANCE = ( 'Do not retry input or recapture a hash in this session. Close the owned ' 'session safely, check input.settings against the supported standard on-foot ' 'bindings, then cold-start a new session to capture its bindings hash. ' 'This helper never edits configuration or guesses defaults.')
def _reject(code, detail): raise ValueError(f'crossbow_binding_{code}: {detail} {_GUIDANCE}')
def _read_bindings(): path = support.manage.DOCS / 'input.settings' try: with path.open('rb') as stream: data = stream.read(MAX_BINDINGS_BYTES + 1) except OSError as exc: _reject('unreadable', f'Cannot read {path}: {exc}.') if len(data) > MAX_BINDINGS_BYTES: _reject('oversized', f'{path} exceeds the 2 MiB input.settings limit.') return path, data
def capture_bindings_hash(): """Capture raw bytes only; None means no usable bounded file was readable.""" try: _, data = _read_bindings() except ValueError: return None return hashlib.sha256(data).hexdigest()
def _binding_fields(row, context, line_number): match = re.fullmatch(r'IK_Pad_RightShoulder\s*=\s*\(([^()]*)\)', row, re.I) if not match: _reject('malformed', f'Malformed {BUTTON} row in [{context}] at line {line_number}.') fields = {} for item in match[1].split(','): field = re.fullmatch(r'\s*([A-Za-z]+)\s*=\s*([A-Za-z0-9_.]+)\s*', item) if not field or field[1].lower() in fields: _reject('malformed', f'Malformed/duplicate binding field in [{context}] at line {line_number}.') fields[field[1].lower()] = field[2] return fields
def _verify_mapping(data): try: text = data.decode('utf-8-sig') except UnicodeDecodeError: _reject('malformed', 'input.settings is not valid UTF-8 (optional BOM supported).') context = None seen_contexts = set() bindings = {name: [] for name in CONTEXTS} for number, raw in enumerate(text.splitlines(), 1): row = raw.strip() if not row or row.startswith((';', '#')): continue if row.startswith('['): header = re.fullmatch(r'\[\s*([A-Za-z0-9_]+)\s*\]', row) if not header: _reject('malformed', f'Malformed context header at line {number}.') context = header[1] if context in bindings: if context in seen_contexts: _reject('unsupported', f'Duplicate supported context [{context}] at line {number}.') seen_contexts.add(context) continue if context in bindings and re.match(r'IK_Pad_RightShoulder\b', row, re.I): fields = _binding_fields(row, context, number) if fields == {'action': 'ThrowItem'}: kind = 'edge' elif fields == {'action': 'ThrowItemHold', 'state': 'Duration', 'idletime': '0.2'}: kind = 'hold' else: _reject('unsupported', f'Unsupported/remapped {BUTTON} binding in [{context}] at line {number}.') if kind in bindings[context]: _reject('unsupported', f'Duplicate {BUTTON} {kind} row in [{context}] at line {number}.') bindings[context].append(kind) for name, rows in bindings.items(): if set(rows) != {'edge', 'hold'}: _reject('unsupported', f'[{name}] requires exactly one ThrowItem edge (no State) and ' 'one ThrowItemHold State=Duration, IdleTime=0.2 right-shoulder row.')
def verify_bindings(expected_hash): """Verify the session-captured bytes and a narrow, explicit mapping contract.""" if not isinstance(expected_hash, str) or not re.fullmatch(r'[0-9a-f]{64}', expected_hash): _reject('missing_receipt', 'A session-captured lowercase SHA256 bindings hash is required.') path, data = _read_bindings() digest = hashlib.sha256(data).hexdigest() if digest != expected_hash: _reject('changed', f'{path} differs from the session-captured SHA256.') _verify_mapping(data) return dict( file=str(path), sha256=digest, button=BUTTON, xinput_button_mask=512, action='ThrowItem', hold_action='ThrowItemHold', scope='file-based standard on-foot mapping only; not the native full runtime map', )