#!/usr/bin/env python3
"""disc_map.py —— 全盘可读性扫描：按块试读，输出可读/不可读区间分布

用法: sudo python3 disc_map.py [--end N] [--step 52] [--speed 4] [--max-bad N]
输出: 每 xx 段一行进度 + 末尾"可读区间 / 不可读区间"汇总 + 首个可读扇区的原始字节
"""
import argparse, os, sys, time
sys.path.insert(0, "/home/zyw/Downloads/vcd-rip")
import sgio

CDROM_SELECT_SPEED = 0x5322

ap = argparse.ArgumentParser()
ap.add_argument("--dev", default="/dev/sr0")
ap.add_argument("--start", type=int, default=0)
ap.add_argument("--end", type=int, default=0, help="0=用设备容量")
ap.add_argument("--step", type=int, default=52)
ap.add_argument("--speed", type=int, default=4, help="读取速度（降速利于救数据）")
ap.add_argument("--retries", type=int, default=2)
a = ap.parse_args()

fd = os.open(a.dev, os.O_RDONLY | os.O_NONBLOCK)
if a.speed:
    r = sgio.libc.ioctl(fd, CDROM_SELECT_SPEED, a.speed)
    print(f"[speed] 设为 {a.speed}x -> {'ok' if r == 0 else '失败'}")

# 容量
data, st, *_ = sgio.sg_io(fd, [0x25, 0, 0, 0, 0, 0, 0, 0, 0], 8, sgio.SG_DXFER_FROM_DEV)
cap = 0
if st == 0 and data and len(data) == 8:
    import struct
    cap = struct.unpack(">II", data)[0] + 1
end = a.end or cap
print(f"[range] 扫描 LBA {a.start}..{end}（{end-a.start} 扇区 ≈ {(end-a.start)*2352/1e6:.1f} MB raw），步长 {a.step}")

runs = []          # [(start_lba, n_ok, n_bad)]
cur_ok = None
bad_total = 0
t0 = time.time()
lba = a.start
while lba < end:
    cur = min(a.step, end - lba)
    ok = False
    for _ in range(a.retries):
        try:
            d, info = sgio.read_cd(fd, lba, cur, flags=0xF8)
            if info["status"] == 0 and d and info["got"] >= cur * 2352:
                ok = True; break
        except OSError:
            pass
        time.sleep(0.1)
    if ok:
        if cur_ok is None: cur_ok = [lba, 0]
        cur_ok[1] += cur
    else:
        bad_total += cur
        if cur_ok is not None:
            runs.append(("OK", cur_ok[0], cur_ok[1])); cur_ok = None
    lba += cur
    if ((lba - a.start) // a.step) % 100 == 0:
        el = time.time() - t0
        print(f"  {100*(lba-a.start)/(end-a.start):5.1f}%  LBA {lba}/{end}  坏扇区累计 {bad_total}  "
              f"{el/60:.1f}min", flush=True)
if cur_ok is not None:
    runs.append(("OK", cur_ok[0], cur_ok[1]))

print("\n=== 可读区间 ===")
for kind, s, n in runs:
    print(f"  LBA {s:>7} .. {s+n-1:>7}  ({n} 扇区, {n*2352/1e6:.1f} MB)")
print(f"\n坏扇区总数 ≈ {bad_total} / {end-a.start} ({100*bad_total/max(1,end-a.start):.1f}%)")

# 取第一个可读扇区看内容类型
if runs:
    s = runs[0][1]
    d, info = sgio.read_cd(fd, s, 1, flags=0xF8)
    if d:
        mode = d[15]; form2 = bool(d[18] & 0x20) if len(d) > 18 else False
        print(f"\n首个可读扇区 LBA {s}: mode={mode} form2={form2} 前16字节={d[:16].hex()}")
        print(f"  ISO9660 PVD 位置应为 LBA 16；若首可读扇区 > 16，则文件系统区不可读")
os.close(fd)
