From 46703811bc5c900c48b4aa7200dd8869b811f49e Mon Sep 17 00:00:00 2001 From: ram/haidar Date: Fri, 15 May 2026 07:46:39 +0700 Subject: [PATCH] feat(autopatch): support fallback binary patterns - Allows comma-separated binary alternatives so patching can try fallback byte patterns - Validates each search/replacement alternative pair before applying patches - Unhandled failures now surface outside URLError handling - Target: Windows Autopatch Tool --- win/tools/autopatch/autopatch.py | 51 ++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/win/tools/autopatch/autopatch.py b/win/tools/autopatch/autopatch.py index 3c290ae9..39521085 100755 --- a/win/tools/autopatch/autopatch.py +++ b/win/tools/autopatch/autopatch.py @@ -7,6 +7,7 @@ import subprocess import sys import tempfile +import urllib.error import urllib.request import xml.etree.ElementTree as ET from binascii import unhexlify @@ -53,17 +54,19 @@ def parse_args(): parser.add_argument("-S", "--search", nargs="+", default=[ - "8BF04533FF85C0", - "8985ECFBFFFF85C08B85DCFBFFFF7504", + "8BF04533FF85C0,8BF085C0750549892FEB", + "8985ECFBFFFF85C08B85DCFBFFFF7504,89450885C08B450C7504", ], - help="representation of search pattern(s) binary string") + help="representation of search pattern(s) binary string. " + "Separate fallback alternatives with commas") parser.add_argument("-R", "--replacement", nargs="+", default=[ - "33C08BF04533FF", - "31C08985ECFBFFFF8B85DCFBFFFF7504", + "33C08BF04533FF,33C08BF0750549892FEB", + "31C08985ECFBFFFF8B85DCFBFFFF7504,33C08945088B450C7504", ], - help="representation of replacement(s) binary string") + help="representation of replacement(s) binary string. " + "Fallback alternatives must match search order") parser.add_argument("-o", "--stdout", action="store_true", help="output into stdout") @@ -143,6 +146,10 @@ def extract_single_file(archive, filename, *, sevenzip="7z"): return result +def parse_binary_alternatives(value): + return [unhexlify(item) for item in value.split(',')] + + def make_patch(archive, *, arch_tgt, search, @@ -163,11 +170,19 @@ def make_patch(archive, *, f = fo.read() else: f = expand(tgt, sevenzip=sevenzip) - offset = f.find(search) - if offset == -1: + matches = [] + for pattern, patched_pattern in zip(search, replacement): + offset = f.find(pattern) + if offset == -1: + continue + if f[offset + len(pattern):].find(pattern) != -1: + raise MultipleOccurencesException("Multiple occurences of pattern found!") + matches.append((offset, pattern, patched_pattern)) + if not matches: raise PatternNotFoundException("Pattern not found.") - if f[offset + len(search):].find(search) != -1: - raise MultipleOccurencesException("Multiple occurences of pattern found!") + if len(matches) > 1: + raise MultipleOccurencesException("Multiple fallback patterns found!") + offset, search, replacement = matches[0] del f print("Pattern found @ %016X" % (offset,), file=sys.stderr) @@ -183,8 +198,10 @@ def identify_driver(archive, *, sevenzip="7z"): manifest = extract_single_file(archive, "setup.cfg", sevenzip=sevenzip) root = ET.fromstring(manifest) version = root.attrib['version'] - product_type = root.find('./properties/string[@name="ProductType"]') \ - .attrib['value'] + product_type_element = root.find('./properties/string[@name="ProductType"]') + if product_type_element is None: + raise UnknownPlatformException("Can't find ProductType in setup.cfg") + product_type = product_type_element.attrib['value'] return version, product_type @@ -197,9 +214,11 @@ def format_patch(diff, filename): def patch_flow(installer_file, search, replacement, target, target_name, patch_name, *, tempdir, direct=False, stdout=False, sevenzip="7z"): - search = unhexlify(search) - replacement = unhexlify(replacement) - assert len(search) == len(replacement), "len() of search and replacement is not equal" + search = parse_binary_alternatives(search) + replacement = parse_binary_alternatives(replacement) + assert len(search) == len(replacement), "count of search and replacement alternatives is not equal" + for search_item, replacement_item in zip(search, replacement): + assert len(search_item) == len(replacement_item), "len() of search and replacement is not equal" # Check if installer file exists or try to download @@ -230,7 +249,7 @@ def patch_flow(installer_file, search, replacement, target, target_name, patch_n else: print(f"Using downloaded file in '{file_path}'") installer_file = file_path - except (urllib.error.URLError, Exception) as e: + except urllib.error.URLError as e: raise InstallerNotFoundException(f"Failed to download the file: {e}") except Exception as e: raise InstallerNotFoundException(f"An error occurred during download: {str(e)}")