import struct

CHUNK    = 128*512
LV_START = 5120000*512
BSIZE    = 4096
IPG      = 8192

def read_virt(virt_byte, length):
    result = bytearray(length)
    pos = virt_byte
    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_byte
            if chunk_idx != 4:
                phys = LV_START + group*4*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)

# Cache GDT
print('Loading full GDT...')
NUM_GROUPS = 35728
gdt_data   = read_virt(BSIZE, NUM_GROUPS*64)
print(f'GDT loaded: {len(gdt_data)} bytes')

def get_inode_table_block(group):
    entry    = gdt_data[group*64:(group+1)*64]
    it_lo    = struct.unpack_from('<I', entry,  8)[0]
    it_hi    = struct.unpack_from('<I', entry, 40)[0]
    return (it_hi<<32)|it_lo

def read_inode(inode_num):
    group    = (inode_num-1)//IPG
    idx      = (inode_num-1)%IPG
    it_block = get_inode_table_block(group)
    return read_virt(it_block*BSIZE + idx*256, 256)

def get_extents(inode_data):
    blocks = []
    if struct.unpack_from('<H', inode_data, 40)[0] != 0xf30a:
        return blocks
    entries = struct.unpack_from('<H', inode_data, 42)[0]
    depth   = struct.unpack_from('<H', inode_data, 46)[0]
    if depth == 0:
        for i in range(min(entries,4)):
            off    = 52+i*12
            ee_len = struct.unpack_from('<H', inode_data, off+4)[0]
            ee_hi  = struct.unpack_from('<H', inode_data, off+6)[0]
            ee_lo  = struct.unpack_from('<I', inode_data, off+8)[0]
            start  = (ee_hi<<32)|ee_lo
            if ee_len > 1024: continue
            for b in range(min(ee_len,8)):
                blocks.append(start+b)
    return blocks

def list_dir(inode_num):
    inode_data = read_inode(inode_num)
    mode = struct.unpack_from('<H', inode_data, 0)[0]
    if (mode&0xf000) != 0x4000:
        return []
    entries = []
    for blk in get_extents(inode_data):
        blk_data = read_virt(blk*BSIZE, BSIZE)
        off = 0
        while off < BSIZE-8:
            ino     = struct.unpack_from('<I', blk_data, off)[0]
            rec_len = struct.unpack_from('<H', blk_data, off+4)[0]
            name_len= blk_data[off+6]
            ftype   = blk_data[off+7]
            if rec_len < 8: break
            if ino > 0 and name_len > 0:
                name = blk_data[off+8:off+8+name_len].decode('utf-8',errors='replace')
                if name not in ('.','..'): 
                    entries.append((ino, name, ftype))
            off += rec_len
    return entries

# Walk tree from /var
print()
print('=== /var (inode 1310721) ===')
for ino, name, ftype in list_dir(1310721):
    tname = {1:'file',2:'dir',7:'link'}.get(ftype,'?')
    print(f'  {tname:4s} inode={ino:10d} {name!r}')

print()
for ino, name, ftype in list_dir(1310721):
    if name == 'lib':
        print(f'=== /var/lib (inode {ino}) ===')
        for i2,n2,f2 in list_dir(ino):
            tname = {1:'file',2:'dir',7:'link'}.get(f2,'?')
            print(f'  {tname:4s} inode={i2:10d} {n2!r}')
        
        for i2,n2,f2 in list_dir(ino):
            if n2 == 'pterodactyl':
                print(f'\n=== /var/lib/pterodactyl (inode {i2}) ===')
                for i3,n3,f3 in list_dir(i2):
                    tname = {1:'file',2:'dir',7:'link'}.get(f3,'?')
                    print(f'  {tname:4s} inode={i3:10d} {n3!r}')
