125 lines
3.2 KiB
Python
125 lines
3.2 KiB
Python
"""
|
|
Reference:
|
|
http://formats.kaitai.io/apple_single_double/index.html
|
|
http://kaiser-edv.de/documents/AppleSingle_AppleDouble.pdf
|
|
above linke is dead, archive:
|
|
https://web.archive.org/web/20180311140826/http://kaiser-edv.de/documents/AppleSingle_AppleDouble.pdf
|
|
"""
|
|
|
|
import enum
|
|
from ctypes import BigEndianStructure, c_uint32, c_uint16, c_char
|
|
from io import RawIOBase
|
|
from typing import List, Optional
|
|
|
|
|
|
class AppleDoubleHeader(BigEndianStructure):
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('signature', c_uint32),
|
|
('version', c_uint32),
|
|
('reserved', c_uint32 * 4), # Must all be zero
|
|
('num_entries', c_uint16)
|
|
]
|
|
|
|
|
|
class AppleDoubleEntry(BigEndianStructure):
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('type', c_uint32),
|
|
('body_offset', c_uint32),
|
|
('body_length', c_uint32)
|
|
]
|
|
|
|
|
|
class EntryType(enum.Enum):
|
|
data_fork = 1
|
|
resource_fork = 2
|
|
real_name = 3 # File name on a file system that supports all the attributes.
|
|
comment = 4
|
|
icon_bw = 5
|
|
icon_color = 6
|
|
file_dates_info = 8 # File creation, modification, access date/timestamps.
|
|
finder_info = 9
|
|
macintosh_file_info = 10
|
|
prodos_file_info = 11
|
|
msdos_file_info = 12
|
|
afp_short_name = 13
|
|
afp_file_info = 14
|
|
afp_directory_id = 15
|
|
|
|
|
|
class Point(BigEndianStructure):
|
|
"""Specifies 2D coordinate in QuickDraw grid."""
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('x', c_uint16),
|
|
('y', c_uint16),
|
|
]
|
|
|
|
|
|
class FinderInfo(BigEndianStructure):
|
|
"""From the older Inside Macintosh publication, Volume II page 84 or Volume IV page 104."""
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
('fdType', c_char * 4),
|
|
('fdCreator', c_char * 4),
|
|
('fdFlags', c_uint16),
|
|
('fdLocation', Point), # File icon's coordinates when displaying this folder.
|
|
('fdFolder', c_uint16) # File folder ID (=window).
|
|
|
|
]
|
|
|
|
|
|
class Entry(object):
|
|
def __init__(self):
|
|
self.info = None
|
|
self.data = None
|
|
|
|
|
|
class AppleDouble(object):
|
|
def __init__(self):
|
|
self.header = AppleDoubleHeader()
|
|
self.entries: List[Entry] = []
|
|
|
|
def get_entry(self, type: EntryType) -> Optional[Entry]:
|
|
for entry in self.entries:
|
|
if entry.info.type == type.value:
|
|
return entry
|
|
return None
|
|
|
|
|
|
SIGNATURE_APPLE_SINGLE = 0x00051600
|
|
SIGNATURE_APPLE_DOUBLE = 0x00051607
|
|
|
|
|
|
def parse(f: RawIOBase) -> AppleDouble:
|
|
header = AppleDoubleHeader()
|
|
f.readinto(header)
|
|
|
|
# Validate signature
|
|
if header.signature not in (SIGNATURE_APPLE_SINGLE, SIGNATURE_APPLE_DOUBLE):
|
|
raise ValueError('Invalid signature')
|
|
|
|
entries = []
|
|
for i in range(header.num_entries):
|
|
entry = Entry()
|
|
info = AppleDoubleEntry()
|
|
f.readinto(info)
|
|
entry.info = info
|
|
entries.append(entry)
|
|
|
|
for entry in entries:
|
|
info = entry.info
|
|
f.seek(info.body_offset)
|
|
data = f.read(info.body_length)
|
|
|
|
if info.type == EntryType.finder_info.value:
|
|
entry.data = FinderInfo.from_buffer_copy(data)
|
|
else:
|
|
entry.data = data
|
|
|
|
result = AppleDouble()
|
|
result.header = header
|
|
result.entries = entries
|
|
return result
|