import struct, os, sys

p = sys.argv[1]
f = open(p, "rb")
total = os.path.getsize(p)
f.seek(32 * 2048)
h = f.read(2048)
assert h[:20] == b"MICROSOFT*XBOX*MEDIA", h[:20]
root_sec, root_size = struct.unpack("<II", h[20:28])

ATTR_DIR = 0x10
files = 0
dirs = 0
bytes_total = 0
bad = []
top = []


def read_dir(sec, size, depth, path):
    global files, dirs, bytes_total
    if size == 0 or sec == 0:
        return
    f.seek(sec * 2048)
    d = f.read(size)
    seen = set()

    def walk(off):
        if off in seen or off * 4 + 14 > len(d):
            return
        seen.add(off)
        left, right, esec, esize, attr, nlen = struct.unpack(
            "<HHIIBB", d[off*4:off*4+14]
        )
        if left == 0xFFFF:
            return
        name = d[off*4+14:off*4+14+nlen].decode("latin1")
        isdir = bool(attr & ATTR_DIR)
        full = path + "/" + name
        if esec * 2048 + esize > total:
            bad.append(full)
        if isdir:
            globals()['dirs'] = globals()['dirs'] + 1
            read_dir(esec, esize, depth + 1, full)
        else:
            globals()['files'] = globals()['files'] + 1
            globals()['bytes_total'] = globals()['bytes_total'] + esize
        if depth == 0:
            top.append((name, isdir, esize))
        if left:
            walk(left)
        if right:
            walk(right)

    walk(0)


read_dir(root_sec, root_size, 0, "")
print("image      :", os.path.basename(p))
print("image size : %d bytes" % total)
print("dirs       :", dirs)
print("files      :", files)
print("data bytes : %d (%.2f GB)" % (bytes_total, bytes_total / 1e9))
print("out-of-range entries:", len(bad))
for b in bad[:10]:
    print("   !!", b)
print("\nroot entries:")
for name, isdir, size in sorted(top):
    print("   %-28s %s" % (name, "<DIR>" if isdir else "%d" % size))
