#!/usr/bin/env python3 import sys, os, struct def parse_appledouble(ad_path): """ Open an AppleDouble file and return (file_type, creator_code). """ # Auxiliary function to decode 8 bytes → (ft, cr) def decode_codes(data): ft_bytes, cr_bytes = data[:4], data[4:8] # decode ASCII, trim NULs ft = ft_bytes.decode("ascii", errors="ignore").replace("\x00", "") cr = cr_bytes.decode("ascii", errors="ignore").replace("\x00", "") # validate 4 alphanumeric characters if len(ft) == 4 and ft.isalnum() and len(cr) == 4 and cr.isalnum(): return ft, cr return "", "" with open(ad_path, "rb") as f: # skip to number of entries (big‑endian uint16 at offset 24) f.seek(24) num_entries, = struct.unpack(">H", f.read(2)) # find offsets and lengths for entries 9 and 2 off9 = len9 = off2 = len2 = None f.seek(26) for _ in range(num_entries): entry_id, entry_off, entry_len = struct.unpack(">III", f.read(12)) if entry_id == 9: off9, len9 = entry_off, entry_len elif entry_id == 2: off2, len2 = entry_off, entry_len # 1) Try Finder Info (entry 9) if off9 is not None and len9 >= 8: f.seek(off9) data = f.read(8) ft, cr = decode_codes(data) if ft and cr: return ft, cr # 2) Fallback to Resource Fork (entry 2) if off2 is not None and len2 >= 16: # read mapOffset (big‑endian uint32 at off2+4) f.seek(off2 + 4) map_off, = struct.unpack(">I", f.read(4)) # embedded Finder Info is at off2 + mapOffset + 16 f.seek(off2 + map_off + 16) data = f.read(8) ft, cr = decode_codes(data) if ft and cr: return ft, cr # if all else fails, return empty strings return "", "" def main(): if len(sys.argv) < 2: print("Usage: get_type_creator.py [...]", file=sys.stderr) sys.exit(1) for datafile in sys.argv[1:]: dirpath, base = os.path.split(datafile) apple = os.path.join(dirpath, "@eaDir", f"{base}@SynoResource") if not os.path.isfile(apple): # print empty fields if no AppleDouble # print(f"{datafile}\t\t") continue ft, cr = parse_appledouble(apple) # only print if both codes are non‐empty if ft and cr: print(f"{datafile}\t{ft}\t{cr}") if __name__ == "__main__": main()