diff --git a/oc-patches/customize-bowden-length/README.md b/oc-patches/customize-bowden-length/README.md new file mode 100644 index 0000000..04f8f11 --- /dev/null +++ b/oc-patches/customize-bowden-length/README.md @@ -0,0 +1,17 @@ +# Bowden Tube Length Patch + +## Summary +Binary patching of 0x2c81f8 performed to update the bowden tube length from 700mm to any desired value + +--- + +## Verification +- [x] Confirm patched binary boots normally +- [x] Confirm print pauses correctly after filament runout sensor trips +- [x] Verify pause timing is nearly immediate after trip event + +--- + +## TODO +- [x] Write parameterized script to accept user-entered Bowden length values +~~- Generate and apply `bsdiff` at runtime~~ diff --git a/oc-patches/customize-bowden-length/patch.py b/oc-patches/customize-bowden-length/patch.py new file mode 100755 index 0000000..5f7dc17 --- /dev/null +++ b/oc-patches/customize-bowden-length/patch.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# - Only tested on 1.1.40 app binary + +import os +import re +import sys +import struct +import shutil +from pathlib import Path + +# ------------------------------------------------------------------- +BASE_VA = 0x00010000 +TARGET_VA = 0x02C81F8 +EXPECTED_BEFORE = bytes.fromhex("0000000000E08540") # original val for 700mm +DEFAULT_MM = 700 # may need to be changed in future hardware/firmware iterations +# ------------------------------------------------------------------- + +# rootcheck +if hasattr(os, "geteuid") and os.geteuid() != 0: + print("Error: please run as root.", file=sys.stderr) + sys.exit(1) + +# env/path +project_root = os.environ.get("REPOSITORY_ROOT") +squashfs_root = os.environ.get("SQUASHFS_ROOT") + +if not project_root or not squashfs_root: + print("Error: REPOSITORY_ROOT and SQUASHFS_ROOT must be set in the environment.", file=sys.stderr) + sys.exit(1) + +project_root = Path(project_root) +squashfs_root = Path(squashfs_root) + +# --- read BOWDEN_LENGTH_MM from patch_config and validate --- +cfg_file = project_root / "oc-patches" / "patch_config" + +try: + text = cfg_file.read_text(encoding="utf-8", errors="replace") + m = re.search(r'^BOWDEN_LENGTH_MM\s*=\s*([^\r\n#]+)', text, flags=re.MULTILINE) +except FileNotFoundError: + print("[INFO] Config not found; skipping patch.") + sys.exit(0) + +if not m: + print("[INFO] BOWDEN_LENGTH_MM not found; skipping patch.") + sys.exit(0) + +# sanitize +bowden_raw = m.group(1).strip() +if not re.fullmatch(r'\d+', bowden_raw): + print("[INFO] BOWDEN_LENGTH_MM invalid (non-integer); skipping patch.") + sys.exit(0) + +bowden_mm = int(bowden_raw, 10) +if not (10 <= bowden_mm <= 999): + print("[INFO] BOWDEN_LENGTH_MM invalid (needs integer 10–999); skipping patch.") + sys.exit(0) + +# get app +app_dir = squashfs_root / "app" +orig = app_dir / "app" +work = app_dir / "app-patch" + +if not orig.is_file(): + print(f"ERROR: target file not found: {orig}", file=sys.stderr) + sys.exit(1) + +shutil.copyfile(orig, work) + +# Hex setup +try: + data = bytearray(work.read_bytes()) +except Exception as e: + print(f"ERROR: failed to read working file: {e}", file=sys.stderr) + sys.exit(1) + +file_off = TARGET_VA - BASE_VA +if file_off < 0 or file_off + 8 > len(data): + print(f"ERROR: invalid offset 0x{file_off:X}", file=sys.stderr) + sys.exit(1) + +before = bytes(data[file_off:file_off+8]) + +# Confirm location +if before != EXPECTED_BEFORE: + print( + f"ERROR: pre-patch bytes mismatch at 0x{file_off:X}\n" + f" found {before.hex().upper()}\n" + f" expected {EXPECTED_BEFORE.hex().upper()}\n" + f" → skipping Bowden patch (bytes don't match, unsafe)", + file=sys.stderr + ) + work.unlink(missing_ok=True) + sys.exit(0) + +# write new double bytes +data[file_off:file_off+8] = struct.pack('