""" Disc scanner. Reads a source sector by sector into bytes. The source can be a raw optical drive (Windows: r"\\\\.\\D:", may need admin) or an .iso / image file (for testing). Unreadable sectors (scratches / disc rot) are recorded as BAD and zero-filled -- ddrescue-style -- rather than aborting the scan. Important: bad sectors are data you never read. RS self-healing (archive.py) protects the STORED copy against future corruption; it cannot invent sectors the drive could not read. To recover those, re-scan the disc and merge good sectors across attempts (`merge_scans`). """ from __future__ import annotations SECTOR = 2048 # CD-ROM Mode-1 user data sector size def scan_image(path: str, sector: int = SECTOR): """-> (data: bytes, bad_sectors: list[int]). Robust to unreadable sectors.""" data = bytearray() bad = [] with open(path, "rb", buffering=0) as f: idx = 0 while True: try: blk = f.read(sector) except OSError: bad.append(idx) data += bytes(sector) try: f.seek((idx + 1) * sector) except OSError: break idx += 1 continue if not blk: break data += blk # keep true length (last idx += 1 # sector may be partial) return bytes(data), bad def merge_scans(scans, sector: int = SECTOR): """Combine multiple (data, bad) scans of the same failing disc: a sector is good if ANY scan read it. Returns (merged_data, still_bad_sectors).""" n = max(len(d) // sector for d, _ in scans) merged = bytearray(n * sector) known = [False] * n for data, bad in scans: badset = set(bad) for s in range(len(data) // sector): if s not in badset and not known[s]: merged[s * sector:(s + 1) * sector] = data[s * sector:(s + 1) * sector] known[s] = True still_bad = [s for s in range(n) if not known[s]] return bytes(merged), still_bad