"""Read-only Human68k FAT12 extraction from the supplied Buster XDF.""" from pathlib import Path import hashlib,json ROOT=Path(__file__).resolve().parent SRC=ROOT.parent.parent/'SharpX68000'/'buster' OUT=ROOT/'extracted' OUT.mkdir(exist_ok=True) sources=[] for p in SRC.iterdir(): if p.is_file(): sources.append(dict(name=p.name,bytes=p.stat().st_size,sha256=hashlib.sha256(p.read_bytes()).hexdigest())) (ROOT/'source-hashes.json').write_text(json.dumps(sources,indent=2)+'\n') b=(ROOT/'intake'/'buster.xdf').read_bytes() bps=int.from_bytes(b[0x0b:0x0d],'little'); spc=b[0x0d]; fats=b[0x10] reserved=int.from_bytes(b[0x0e:0x10],'little'); roots=int.from_bytes(b[0x11:0x13],'little'); spf=int.from_bytes(b[0x16:0x18],'little') assert bps==1024 and spc and fats and roots fat=reserved*bps; root=(reserved+fats*spf)*bps; data=root+((roots*32+bps-1)//bps)*bps def chain(c): seen=set(); out=bytearray() while 2<=c<0xff8: if c in seen: raise ValueError('cyclic FAT') seen.add(c); off=data+(c-2)*spc*bps if off+spc*bps>len(b): raise ValueError('FAT out of bounds') out+=b[off:off+spc*bps] q=fat+c*3//2; word=int.from_bytes(b[q:q+2],'little'); c=(word>>4 if c&1 else word&0xfff) return bytes(out) inventory=[] def directory(raw,prefix=''): for off in range(0,len(raw),32): e=raw[off:off+32] if not e or e[0]==0:break if e[0]==0xe5 or e[11]&8:continue stem=e[:8].rstrip(b' ')+e[12:22].split(b'\0')[0].rstrip(b' ') ext=e[8:11].rstrip(b' '); name=(stem+(b'.'+ext if ext else b'')).decode('cp932',errors='replace') if name in ('.','..'):continue if '/' in name or '\\' in name or '..' in name:raise ValueError('unsafe name') rel=prefix+name; c=int.from_bytes(e[26:28],'little'); n=int.from_bytes(e[28:32],'little') if e[11]&16: directory(chain(c),rel+'/');continue content=chain(c)[:n] if len(content)!=n:raise ValueError('short chain') dest=OUT/rel;dest.parent.mkdir(parents=True,exist_ok=True);dest.write_bytes(content) inventory.append(dict(path=rel,bytes=n,sha256=hashlib.sha256(content).hexdigest(),cluster=c,head=content[:32].hex())) directory(b[root:root+roots*32]) (ROOT/'disk-inventory.json').write_text(json.dumps(inventory,indent=2,ensure_ascii=False)+'\n',encoding='utf8') for e in inventory:print(f"{e['bytes']:8} {e['path']} {e['head']}")