File size: 2,174 Bytes
bdfb884
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
"""
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