macmfsextract/mfsextract.py

131 lines
4.2 KiB
Python

#!/usr/bin/env python3
#Created by Don Barber, don@dgb3.net
#Pass the raw disk image on the command line as argument 1
#add verbose as argument 2 if you want some extra details
#MFS implementation details from https://www.macgui.com/news/article.php?t=482
#If the image is a diskcopy 4.2 image, extract the raw MFS image first via:
# dd if=INPUTFILE of=OUTPUTFILE bs=84 skip=1
#Idea for improvement: extract files as macbinary format instead of
#individual data and resource forks
import sys
import os
import math
filename=sys.argv[1]
file_size = os.path.getsize(filename)
fh=open(filename,"rb")
fh.seek(1026)
drCrDate = int.from_bytes(fh.read(4),'big')
drLsBkUp = int.from_bytes(fh.read(4),'big')
drAtrb=int.from_bytes(fh.read(2),'big')
drNmFls=int.from_bytes(fh.read(2),'big')
drDirSt=int.from_bytes(fh.read(2),'big')
drBlLen=int.from_bytes(fh.read(2),'big')
drNmAlBlks=int.from_bytes(fh.read(2),'big')
drAlBlkSiz=int.from_bytes(fh.read(4),'big')
drClpSiz=int.from_bytes(fh.read(4),'big')
drAlBlSt=int.from_bytes(fh.read(2),'big')
drNxtFNum=int.from_bytes(fh.read(4),'big')
drFreeBks=int.from_bytes(fh.read(2),'big')
drVNl=int.from_bytes(fh.read(1),'big')
drVN=fh.read(drVNl)
print("Volume Name:",drVN)
if 'verbose' in sys.argv:
print("Volume Create Datestamp:",drCrDate)
print("Volume Modify Datestamp:",drLsBkUp)
maplocation=0x440
def getmapentry(blocknum):
location=((blocknum-2)*12/8)
if location==math.ceil(location):
fh.seek(maplocation+int(location))
entry=(int.from_bytes(fh.read(2),'big') >> 4)
else:
fh.seek(maplocation+math.floor(location))
entry=(int.from_bytes(fh.read(2),'big') & 0xFFF )
return entry
def getfilecontents(block,length):
blocklist=[block]
while True:
block=getmapentry(block)
#print(block)
if block==1:
break
blocklist.append(block)
if block==0:
raise Exception("Unused Block")
if 'verbose' in sys.argv:
print("Blocklist:",blocklist)
contents=b''
for block in blocklist:
if 'verbose' in sys.argv:
print("Seeking to:",hex(drAlBlSt*512+(block-2)*drAlBlkSiz),"for block",block)
fh.seek(drAlBlSt*512+(block-2)*drAlBlkSiz)
data=fh.read(drAlBlkSiz)
contents+=data
return contents[:length]
fh.seek(drDirSt*512)
while True:
flFlgs=fh.read(1)
flType=int.from_bytes(fh.read(1),'big')
while flFlgs==b'\x00': #loop until next record found
flFlgs=fh.read(1)
flType=int.from_bytes(fh.read(1),'big')
if fh.tell()>=((drDirSt+drBlLen-1)*512): #we're past the end of the file directory, exit out
break
if fh.tell()>=((drDirSt+drBlLen-1)*512): #we're past the end of the file directory, exit out
break
if flFlgs!=b'\x00':
flUsrWds=fh.read(16)
flFlNum=int.from_bytes(fh.read(4),'big')
flStBlk=int.from_bytes(fh.read(2),'big')
flLgLen=int.from_bytes(fh.read(4),'big')
flPyLen=int.from_bytes(fh.read(4),'big')
flRStBlk=int.from_bytes(fh.read(2),'big')
flRLgLen=int.from_bytes(fh.read(4),'big')
flRPyLen=int.from_bytes(fh.read(4),'big')
flCrDat=int.from_bytes(fh.read(4),'big')
flMdDat=int.from_bytes(fh.read(4),'big')
flNaml=int.from_bytes(fh.read(1),'big')
flNam=fh.read(flNaml)
if (fh.tell()%2==1): #align to word boundary after reading name
fh.read(1)
if flFlNum!=0:
print(hex(fh.tell()),flFlNum,flNaml,flNam)
if 'verbose' in sys.argv:
print("Create Datestamp:",flCrDat)
print("Modify Datestamp:",flMdDat)
location=fh.tell()
oh=open(flNam+b".info","wb")
oh.write(flUsrWds)
oh.close()
if(flStBlk==0 and flRStBlk==0):
print("Error:",flNam,"has neither data nor resource fork")
if flStBlk!=0:
content=getfilecontents(flStBlk,flLgLen)
oh=open(flNam+b".data","wb")
oh.write(content)
oh.close()
if flRStBlk!=0:
content=getfilecontents(flRStBlk,flRLgLen)
oh=open(flNam+b".rsrc","wb")
oh.write(content)
oh.close()
fh.seek(location)
fh.close()