mirror of
https://github.com/barberd/macmfsextract.git
synced 2024-07-26 13:28:56 +00:00
Simplified to handle file directory fragmentation better, with side effect of no longer needing the code to align to sector boundaries.
131 lines
4.2 KiB
Python
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()
|
|
|
|
|