#!/usr/bin/env python3
"""
Build merged GDT using corrected 5-chunk translation formula.

With the old formula (group*4*CHUNK), chunk_idx=4 blocks were skipped and
every address past group 0 was wrong. The correct formula is group*5*CHUNK
for all chunk indices 0-4.

Since all chunks are now readable, we use the primary GDT for everything and
fall back to the backup (group 1) only when a primary entry reads as all-zeros
(indicating it genuinely landed in a damaged sector).
"""
import struct

CHUNK      = 128 * 512     # 64 KB
LV_START   = 5120000 * 512
BSIZE      = 4096
BPG        = 32768
GDT_ENTRY  = 64
NUM_GROUPS = 35728


def raw_read(virt_offset, length):
    """Correct PERC H710 translation — 5-chunk stride, all chunks readable."""
    result = bytearray(length)
    pos = virt_offset
    remaining = length
    with open('/dev/md0', 'rb') as f:
        while remaining > 0:
            group     = pos // (5 * CHUNK)
            in_group  = pos % (5 * CHUNK)
            chunk_idx = in_group // CHUNK
            intra     = in_group % CHUNK
            seg_len   = min(CHUNK - intra, remaining)
            dst_off   = pos - virt_offset
            phys = LV_START + group * 5 * CHUNK + chunk_idx * CHUNK + intra
            f.seek(phys)
            data = f.read(seg_len)
            result[dst_off:dst_off + len(data)] = data
            pos       += seg_len
            remaining -= seg_len
    return bytes(result)


print('Building merged GDT (v2 — corrected 5-chunk formula)...')
primary_start = BSIZE                  # block 1
backup_start  = (1 * BPG + 1) * BSIZE # group 1 backup SB location

primary_gdt = raw_read(primary_start, NUM_GROUPS * GDT_ENTRY)
backup_gdt  = raw_read(backup_start,  NUM_GROUPS * GDT_ENTRY)

merged       = bytearray(NUM_GROUPS * GDT_ENTRY)
primary_used = 0
backup_used  = 0
zero_both    = 0

for g in range(NUM_GROUPS):
    src_off = g * GDT_ENTRY
    prim_e  = primary_gdt[src_off:src_off + GDT_ENTRY]
    back_e  = backup_gdt[src_off:src_off + GDT_ENTRY]

    if any(prim_e):
        merged[src_off:src_off + GDT_ENTRY] = prim_e
        primary_used += 1
    elif any(back_e):
        merged[src_off:src_off + GDT_ENTRY] = back_e
        backup_used += 1
    else:
        zero_both += 1

print(f'  From primary GDT : {primary_used}')
print(f'  From backup GDT  : {backup_used}')
print(f'  Both zero (lost) : {zero_both}')

if zero_both > 0:
    print(f'  WARNING: {zero_both} group descriptors could not be recovered')

print()
print('Sample entries:')
for g in [0, 1, 13, 100, 1000, 35000, 35727]:
    e   = merged[g * GDT_ENTRY:(g + 1) * GDT_ENTRY]
    bb  = struct.unpack_from('<I', e,  0)[0]
    ib  = struct.unpack_from('<I', e,  4)[0]
    it  = struct.unpack_from('<I', e,  8)[0]
    cs  = struct.unpack_from('<H', e, 30)[0]
    src = 'primary' if any(primary_gdt[g*GDT_ENTRY:(g+1)*GDT_ENTRY]) else 'backup'
    print(f'  Group {g:6d}: bb={bb:10d} ib={ib:10d} it={it:10d} '
          f'csum=0x{cs:04x}  [{src}]')

with open('/tmp/merged_gdt.bin', 'wb') as f:
    f.write(merged)
print(f'\nSaved /tmp/merged_gdt.bin ({len(merged) // 1024} KB)')
