1import argparse
2import struct
3
4def read_entry(f) -> dict:
5    name = struct.unpack_from("12s", f.read(12))[0]
6    uid = struct.unpack_from(">I", f.read(4))[0]
7    gid = struct.unpack_from(">H", f.read(2))[0]
8    is_file = struct.unpack_from("?", f.read(1))[0]
9    modes = struct.unpack_from("BBB", f.read(3))
10    attr = struct.unpack_from("B", f.read(2))[0]
11    x3 = struct.unpack_from(">I", f.read(4))[0]
12    num_children = struct.unpack_from(">I", f.read(4))[0]
13
14    children = []
15    for i in range(num_children):
16        children.append(read_entry(f))
17
18    return {
19        "name": name,
20        "uid": uid,
21        "gid": gid,
22        "is_file": is_file,
23        "modes": modes,
24        "attr": attr,
25        "x3": x3,
26        "children": children,
27    }
28
29COLOR_RESET = "\x1b[0;00m"
30BOLD = "\x1b[0;37m"
31COLOR_BLUE = "\x1b[1;34m"
32COLOR_GREEN = "\x1b[0;32m"
33
34def print_entry(entry, indent) -> None:
35    mode_str = {0: "--", 1: "r-", 2: "-w", 3: "rw"}
36
37    sp = ' ' * indent
38    color = BOLD if entry["is_file"] else COLOR_BLUE
39
40    owner = f"{COLOR_GREEN}{entry['uid']:04x}{COLOR_RESET}:{entry['gid']:04x}"
41    attrs = f"{''.join(mode_str[mode] for mode in entry['modes'])}"
42    other_attrs = f"{entry['attr']} {entry['x3']}"
43
44    print(f"{sp}{color}{entry['name'].decode()}{COLOR_RESET} [{owner} {attrs} {other_attrs}]")
45    for child in entry["children"]:
46        print_entry(child, indent + 2)
47
48def main() -> None:
49    parser = argparse.ArgumentParser(description="Prints a FST in a tree-like format.")
50    parser.add_argument("file")
51    args = parser.parse_args()
52
53    with open(args.file, "rb") as f:
54        root = read_entry(f)
55
56    print_entry(root, 0)
57
58main()
59