mirror of
https://github.com/elliotnunn/machfs.git
synced 2025-02-16 08:30:59 +00:00
hfsutils can mount my images
This commit is contained in:
parent
b681e95d5a
commit
1f2b60b3fe
161
thing.py
161
thing.py
@ -1,5 +1,4 @@
|
|||||||
import struct
|
import struct
|
||||||
import copy
|
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
|
|
||||||
@ -38,24 +37,16 @@ def _dump_btree_recs(buf, start):
|
|||||||
break
|
break
|
||||||
this_leaf = ndFLink
|
this_leaf = ndFLink
|
||||||
|
|
||||||
def _choose_alloc_size(total_logical_blocks):
|
def _pack_leaf_record(key, value): # works correctly
|
||||||
"""Guess a good number of physical blocks per allocation block"""
|
b = bytes([len(key)+1, 0, *key])
|
||||||
size = 1
|
|
||||||
while size * 65536 < 0.99 * total_logical_blocks:
|
|
||||||
size += 1
|
|
||||||
return size
|
|
||||||
|
|
||||||
def _pack_leaf_record(key, value):
|
|
||||||
b = bytes([len(key)]) + key
|
|
||||||
if len(b) & 1: b += bytes(1)
|
if len(b) & 1: b += bytes(1)
|
||||||
b += value
|
b += value
|
||||||
return b
|
return b
|
||||||
|
|
||||||
def _pack_index_record(key, pointer):
|
def _pack_index_record(key, pointer):
|
||||||
b = bytes([0x25]) + key
|
key += bytes(0x24 - len(key))
|
||||||
b += bytes(0x26 - len(b))
|
value = struct.pack('>L', pointer)
|
||||||
b += struct.pack('>L', pointer) # check this: are pointers meant to be 4 bytes?
|
return _pack_leaf_record(key, value)
|
||||||
return b
|
|
||||||
|
|
||||||
def _will_fit_in_leaf_node(keyvals):
|
def _will_fit_in_leaf_node(keyvals):
|
||||||
return len(keyvals) <= 2 # really must fix this!
|
return len(keyvals) <= 2 # really must fix this!
|
||||||
@ -126,11 +117,11 @@ def _mkbtree(records):
|
|||||||
biglist.reverse() # index nodes then leaf nodes
|
biglist.reverse() # index nodes then leaf nodes
|
||||||
|
|
||||||
# cool, now biglist is of course brilliant
|
# cool, now biglist is of course brilliant
|
||||||
# for i, level in enumerate(biglist, 1):
|
for i, level in enumerate(biglist, 1):
|
||||||
# print('LEVEL', i)
|
print('LEVEL', i)
|
||||||
# for node in level:
|
for node in level:
|
||||||
# print('(%d)' % len(node), *(rec[0] for rec in node))
|
print('(%d)' % len(node), *(rec[0] for rec in node))
|
||||||
# print()
|
print()
|
||||||
|
|
||||||
# Make space for a header node at element 0
|
# Make space for a header node at element 0
|
||||||
hnode = _Node()
|
hnode = _Node()
|
||||||
@ -143,7 +134,8 @@ def _mkbtree(records):
|
|||||||
|
|
||||||
for i, level in enumerate(biglist, 1):
|
for i, level in enumerate(biglist, 1):
|
||||||
for node in level:
|
for node in level:
|
||||||
firstkey = node[0][0]
|
if len(node) == 0: continue
|
||||||
|
firstkey, firstval = node[0]
|
||||||
spiderdict[i, firstkey] = len(nodelist)
|
spiderdict[i, firstkey] = len(nodelist)
|
||||||
|
|
||||||
newnode = _Node()
|
newnode = _Node()
|
||||||
@ -174,6 +166,7 @@ def _mkbtree(records):
|
|||||||
bits_covered = 2048
|
bits_covered = 2048
|
||||||
mapnodes = []
|
mapnodes = []
|
||||||
while bits_covered < len(nodelist):
|
while bits_covered < len(nodelist):
|
||||||
|
print('making map node!')
|
||||||
bits_covered += 3952 # bits in a max-sized record
|
bits_covered += 3952 # bits in a max-sized record
|
||||||
mapnode = _Node()
|
mapnode = _Node()
|
||||||
nodelist.append(mapnode)
|
nodelist.append(mapnode)
|
||||||
@ -187,12 +180,12 @@ def _mkbtree(records):
|
|||||||
for i, node in enumerate(nodelist):
|
for i, node in enumerate(nodelist):
|
||||||
node.ndBLink = most_recent.get(node.ndType, 0)
|
node.ndBLink = most_recent.get(node.ndType, 0)
|
||||||
most_recent[node.ndType] = i
|
most_recent[node.ndType] = i
|
||||||
bthLNode = most_recent[0xFF]
|
bthLNode = most_recent.get(0xFF, 0)
|
||||||
most_recent = {}
|
most_recent = {}
|
||||||
for i, node in reversed(list(enumerate(nodelist))):
|
for i, node in reversed(list(enumerate(nodelist))):
|
||||||
node.ndFLink = most_recent.get(node.ndType, 0)
|
node.ndFLink = most_recent.get(node.ndType, 0)
|
||||||
most_recent[node.ndType] = i
|
most_recent[node.ndType] = i
|
||||||
bthFNode = most_recent[0xFF]
|
bthFNode = most_recent.get(0xFF, 0)
|
||||||
|
|
||||||
# for n in nodelist:
|
# for n in nodelist:
|
||||||
# print(n.__dict__)
|
# print(n.__dict__)
|
||||||
@ -223,7 +216,6 @@ def _mkbtree(records):
|
|||||||
return b''.join(bytes(node) for node in nodelist)
|
return b''.join(bytes(node) for node in nodelist)
|
||||||
|
|
||||||
def _catrec_sorter(b):
|
def _catrec_sorter(b):
|
||||||
return b # must fix this later on
|
|
||||||
order = [
|
order = [
|
||||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||||
@ -265,9 +257,17 @@ def _catrec_sorter(b):
|
|||||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
|
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
|
||||||
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||||
]
|
]
|
||||||
length = b[4]
|
|
||||||
name = b[5:5+length]
|
b = b[0] # we are only sorting keys!
|
||||||
return b[:4] + bytes(order[ch] for ch in name)
|
|
||||||
|
return b[:4] + bytes(order[ch] for ch in b[5:])
|
||||||
|
|
||||||
|
def _suggest_allocblk_size(volsize, minalign):
|
||||||
|
min_nonalloc_blks = 6 # just for this estimation
|
||||||
|
retval = minalign
|
||||||
|
while volsize - min_nonalloc_blks*512 > retval*65536:
|
||||||
|
retval += minalign
|
||||||
|
return retval
|
||||||
|
|
||||||
|
|
||||||
class File:
|
class File:
|
||||||
@ -365,18 +365,23 @@ class Volume(_AbstractFolder):
|
|||||||
|
|
||||||
for rec in _dump_btree_recs(from_volume, 512*drAlBlSt + drAlBlkSiz*drCTExtRec_Start):
|
for rec in _dump_btree_recs(from_volume, 512*drAlBlSt + drAlBlkSiz*drCTExtRec_Start):
|
||||||
# create a directory tree from the catalog file
|
# create a directory tree from the catalog file
|
||||||
if rec[0] == 0: continue
|
|
||||||
rec_len = rec[0]
|
rec_len = rec[0]
|
||||||
key = rec[1:1+rec_len]
|
if rec_len == 0: continue
|
||||||
|
|
||||||
|
key = rec[2:1+rec_len]
|
||||||
val = rec[_pad_up(1+rec_len, 2):]
|
val = rec[_pad_up(1+rec_len, 2):]
|
||||||
|
|
||||||
ckrParID, namelen = struct.unpack_from('>LB', key, 1)
|
ckrParID, namelen = struct.unpack_from('>LB', key)
|
||||||
ckrCName = key[6:6+namelen]
|
ckrCName = key[6:6+namelen]
|
||||||
|
|
||||||
datatype = val[0]
|
datatype = (None, 'dir', 'file', 'dthread', 'fthread')[val[0]]
|
||||||
datarec = val[2:]
|
datarec = val[2:]
|
||||||
|
|
||||||
if datatype == 1: # directory
|
print(datatype)
|
||||||
|
print('\t', key)
|
||||||
|
print('\t', datarec)
|
||||||
|
|
||||||
|
if datatype == 'dir':
|
||||||
dirFlags, dirVal, dirDirID, dirCrDat, dirMdDat, dirBkDat, dirUsrInfo, dirFndrInfo \
|
dirFlags, dirVal, dirDirID, dirCrDat, dirMdDat, dirBkDat, dirUsrInfo, dirFndrInfo \
|
||||||
= struct.unpack_from('>HHLLLL16s16s', datarec)
|
= struct.unpack_from('>HHLLLL16s16s', datarec)
|
||||||
|
|
||||||
@ -386,7 +391,7 @@ class Volume(_AbstractFolder):
|
|||||||
|
|
||||||
f.crdat, f.mddat, f.bkdat = dirCrDat, dirMdDat, dirBkDat
|
f.crdat, f.mddat, f.bkdat = dirCrDat, dirMdDat, dirBkDat
|
||||||
|
|
||||||
elif datatype == 2: # file (ignore "thread records" when reading)
|
elif datatype == 'file':
|
||||||
filFlags, filTyp, filUsrWds, filFlNum, \
|
filFlags, filTyp, filUsrWds, filFlNum, \
|
||||||
filStBlk, filLgLen, filPyLen, \
|
filStBlk, filLgLen, filPyLen, \
|
||||||
filRStBlk, filRLgLen, filRPyLen, \
|
filRStBlk, filRLgLen, filRPyLen, \
|
||||||
@ -428,24 +433,46 @@ class Volume(_AbstractFolder):
|
|||||||
|
|
||||||
self.update(cnids[2])
|
self.update(cnids[2])
|
||||||
|
|
||||||
def write(self, size):
|
def write(self, size=800*1024, align=512):
|
||||||
size -= size % 512
|
if align < 512 or align % 512:
|
||||||
|
raise ValueError('align must be multiple of 512')
|
||||||
|
|
||||||
# choose an alloc block size
|
if size < 400 * 1024 or size % 512:
|
||||||
drAlBlkSiz = 512
|
raise ValueError('size must be a multiple of 512b and >= 800K')
|
||||||
while drAlBlkSiz * 65536 < size:
|
|
||||||
drAlBlkSiz += 512
|
|
||||||
|
|
||||||
# decide how many bitmap blocks we need
|
# overall layout:
|
||||||
nbitblks = 0
|
# 1. two boot blocks (offset=0)
|
||||||
while (size - (3 + nbitblks) * 512) // drAlBlkSiz < nbitblks * 512 * 8:
|
# 2. one volume control block (offset=2)
|
||||||
nbitblks += 1
|
# 3. some bitmap blocks (offset=3)
|
||||||
|
# 4. many allocation blocks
|
||||||
|
# 5. duplicate VCB (offset=-2)
|
||||||
|
# 6. unused block (offset=-1)
|
||||||
|
|
||||||
|
# so we will our best guess at these variables as we go:
|
||||||
|
# drNmAlBlks, drAlBlkSiz, drAlBlSt
|
||||||
|
|
||||||
|
# the smallest possible alloc block size
|
||||||
|
drAlBlkSiz = _suggest_allocblk_size(size, align)
|
||||||
|
|
||||||
|
# how many blocks will we use for the bitmap?
|
||||||
|
# (cheat by adding blocks to align the alloc area)
|
||||||
|
bitmap_blk_cnt = 0
|
||||||
|
while (size - (5+bitmap_blk_cnt)*512) // drAlBlkSiz > bitmap_blk_cnt*512*8:
|
||||||
|
bitmap_blk_cnt += 1
|
||||||
|
while (3+bitmap_blk_cnt)*512 % align:
|
||||||
|
bitmap_blk_cnt += 1
|
||||||
|
|
||||||
# decide how many alloc blocks there will be
|
# decide how many alloc blocks there will be
|
||||||
drNmAlBlks = (size - (3 + nbitblks) * 512) // drAlBlkSiz
|
drNmAlBlks = (size - (5+bitmap_blk_cnt)*512) // drAlBlkSiz
|
||||||
blkaccum = []
|
blkaccum = []
|
||||||
|
|
||||||
# <<< put the empty extents overflow file in here >>>
|
# <<< put the empty extents overflow file in here >>>
|
||||||
|
extoflowfile = _mkbtree([])
|
||||||
|
# also need to do some cleverness to ensure that this gets picked up...
|
||||||
|
drXTFlSize = len(extoflowfile)
|
||||||
|
drXTExtRec_Start = len(blkaccum)
|
||||||
|
blkaccum.extend(_chunkify(extoflowfile, drAlBlkSiz))
|
||||||
|
drXTExtRec_Cnt = len(blkaccum) - drXTExtRec_Start
|
||||||
|
|
||||||
# write all the files in the volume
|
# write all the files in the volume
|
||||||
topwrap = _TempWrapper(self)
|
topwrap = _TempWrapper(self)
|
||||||
@ -530,12 +557,14 @@ class Volume(_AbstractFolder):
|
|||||||
|
|
||||||
catalog.append((mainrec_key, mainrec_val))
|
catalog.append((mainrec_key, mainrec_val))
|
||||||
|
|
||||||
thdrec_key = struct.pack('>L', wrap.cnid)
|
thdrec_key = struct.pack('>Lx', wrap.cnid)
|
||||||
thdrec_val_type = 4 if isinstance(wrap.of, File) else 3
|
thdrec_val_type = 4 if isinstance(wrap.of, File) else 3
|
||||||
thdrec_val = struct.pack('>BxxxxxxxxxLB', thdrec_val_type, path2wrap[path[:-1]].cnid, len(path[-1])) + path[-1]
|
thdrec_val = struct.pack('>BxxxxxxxxxLB', thdrec_val_type, path2wrap[path[:-1]].cnid, len(path[-1])) + path[-1]
|
||||||
|
|
||||||
catalog.append((thdrec_key, thdrec_val))
|
catalog.append((thdrec_key, thdrec_val))
|
||||||
|
|
||||||
|
catalog.sort(key=_catrec_sorter)
|
||||||
|
|
||||||
# now it is time to sort these records! fuck that shit...
|
# now it is time to sort these records! fuck that shit...
|
||||||
# catalog.sort...
|
# catalog.sort...
|
||||||
catalogfile = _mkbtree(catalog)
|
catalogfile = _mkbtree(catalog)
|
||||||
@ -545,8 +574,11 @@ class Volume(_AbstractFolder):
|
|||||||
blkaccum.extend(_chunkify(catalogfile, drAlBlkSiz))
|
blkaccum.extend(_chunkify(catalogfile, drAlBlkSiz))
|
||||||
drCTExtRec_Cnt = len(blkaccum) - drCTExtRec_Start
|
drCTExtRec_Cnt = len(blkaccum) - drCTExtRec_Start
|
||||||
|
|
||||||
|
if len(blkaccum) > drNmAlBlks:
|
||||||
|
raise ValueError('Does not fit!')
|
||||||
|
|
||||||
# Create the bitmap of free volume allocation blocks
|
# Create the bitmap of free volume allocation blocks
|
||||||
bitmap = _bits(nbitblks * 512 * 8, len(blkaccum))
|
bitmap = _bits(bitmap_blk_cnt * 512 * 8, len(blkaccum))
|
||||||
|
|
||||||
# Create the Volume Information Block
|
# Create the Volume Information Block
|
||||||
drSigWord = b'BD'
|
drSigWord = b'BD'
|
||||||
@ -555,15 +587,12 @@ class Volume(_AbstractFolder):
|
|||||||
drVBMSt = 3 # first block of volume bitmap
|
drVBMSt = 3 # first block of volume bitmap
|
||||||
drAllocPtr = len(blkaccum)
|
drAllocPtr = len(blkaccum)
|
||||||
drClpSiz = drXTClpSiz = drCTClpSiz = drAlBlkSiz
|
drClpSiz = drXTClpSiz = drCTClpSiz = drAlBlkSiz
|
||||||
drAlBlSt = 3 + nbitblks
|
drAlBlSt = 3 + bitmap_blk_cnt
|
||||||
drFreeBks = drNmAlBlks - len(blkaccum)
|
drFreeBks = drNmAlBlks - len(blkaccum)
|
||||||
drWrCnt = 0 # ????volume write count
|
drWrCnt = 0 # ????volume write count
|
||||||
drVCSize = drVBMCSize = drCtlCSize = 99
|
drVCSize = drVBMCSize = drCtlCSize = 99
|
||||||
drXTFlSize = 0 # currently faking out the extents overflow file
|
|
||||||
drXTExtRec_Start = 0
|
|
||||||
drXTExtRec_Cnt = 0
|
|
||||||
|
|
||||||
headernode = struct.pack('2sLLHHHHHLLHLH28pLHLLLHLL32sHHHLHHxxxxxxxxLHHxxxxxxxx',
|
vib = struct.pack('>2sLLHHHHHLLHLH28pLHLLLHLL32sHHHLHHxxxxxxxxLHHxxxxxxxx',
|
||||||
drSigWord, self.drCrDate, self.drLsMod, self.drAtrb, drNmFls,
|
drSigWord, self.drCrDate, self.drLsMod, self.drAtrb, drNmFls,
|
||||||
drVBMSt, drAllocPtr, drNmAlBlks, drAlBlkSiz, drClpSiz, drAlBlSt,
|
drVBMSt, drAllocPtr, drNmAlBlks, drAlBlkSiz, drClpSiz, drAlBlSt,
|
||||||
drNxtCNID, drFreeBks, self.drVN, self.drVolBkUp, self.drVSeqNum,
|
drNxtCNID, drFreeBks, self.drVN, self.drVolBkUp, self.drVSeqNum,
|
||||||
@ -572,11 +601,13 @@ class Volume(_AbstractFolder):
|
|||||||
drXTFlSize, drXTExtRec_Start, drXTExtRec_Cnt,
|
drXTFlSize, drXTExtRec_Start, drXTExtRec_Cnt,
|
||||||
drCTFlSize, drCTExtRec_Start, drCTExtRec_Cnt,
|
drCTFlSize, drCTExtRec_Start, drCTExtRec_Cnt,
|
||||||
)
|
)
|
||||||
|
vib += bytes(512-len(vib))
|
||||||
|
|
||||||
assert all(len(x) == drAlBlkSiz for x in blkaccum)
|
assert all(len(x) == drAlBlkSiz for x in blkaccum)
|
||||||
finalchunks = [self.bootblocks, headernode, bitmap, *blkaccum]
|
finalchunks = [self.bootblocks, vib, bitmap, *blkaccum]
|
||||||
finalchunks.append(bytes(size - sum(len(x) for x in finalchunks)))
|
finalchunks.append(bytes(size - sum(len(x) for x in finalchunks) - 2*512))
|
||||||
|
finalchunks.append(vib)
|
||||||
|
finalchunks.append(bytes(512))
|
||||||
return b''.join(finalchunks)
|
return b''.join(finalchunks)
|
||||||
|
|
||||||
|
|
||||||
@ -586,16 +617,28 @@ if sys.argv[1:]:
|
|||||||
else:
|
else:
|
||||||
infile = 'SourceForEmulator.dmg'
|
infile = 'SourceForEmulator.dmg'
|
||||||
import pprint
|
import pprint
|
||||||
h = Volume()
|
|
||||||
h.read(open(infile,'rb').read())
|
|
||||||
|
print(_mkbtree([]))
|
||||||
|
|
||||||
|
# h = Volume()
|
||||||
|
# h.read(open(infile,'rb').read())
|
||||||
|
|
||||||
|
|
||||||
# open('/tmp/aj', 'wb').write(h[b'Extensions'][b'AppleJack 2.1'].rsrc)
|
# open('/tmp/aj', 'wb').write(h[b'Extensions'][b'AppleJack 2.1'].rsrc)
|
||||||
# pprint.pprint(h)
|
# pprint.pprint(h)
|
||||||
# for path, obj in h.paths():
|
# for path, obj in h.paths():
|
||||||
# print(path, obj)
|
# print(path, obj)
|
||||||
open('/tmp/thing','wb').write(h.write(128*1024*1024))
|
|
||||||
# h.walk_catalog()
|
|
||||||
|
h = Volume()
|
||||||
|
f = File()
|
||||||
|
h[b'file'] = f
|
||||||
|
wr = h.write(800*1024)
|
||||||
|
open(infile,'wb').write(wr)
|
||||||
|
|
||||||
|
h2 = Volume()
|
||||||
|
h2.read(wr)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user