282 lines
8.4 KiB
Python
282 lines
8.4 KiB
Python
import os
|
|
import math
|
|
import enum
|
|
import destruct
|
|
from destruct import Struct, sizeof
|
|
|
|
from .common import GhostHeaderType, GhostTime, GhostEncrypted, GhostCompressionType, GhostCompressedFile, GhostCompressed
|
|
from .properties import PropertySet, make_property_enum
|
|
from .partition import select_partition_parser, select_partition_index_parser
|
|
|
|
|
|
DRIVE_PROPERTY_TAGS = (
|
|
'SRemote',
|
|
'SDrive',
|
|
'SHeadsPerCyl',
|
|
'SectorsPerTrack',
|
|
'SCylinders',
|
|
'STotalSectors',
|
|
'SSectorsUsed',
|
|
'SEstimatedUsed',
|
|
'SPartitions',
|
|
'SVersion',
|
|
'SFlags',
|
|
'SOs2Name',
|
|
'SFingerprint',
|
|
'SMbr',
|
|
'SIsSpanned',
|
|
'SDiskFormat',
|
|
'SEndOfImagePosition',
|
|
'SPatchedFileCount',
|
|
'SOrder',
|
|
'SBootable',
|
|
'SSystemId',
|
|
'SExtended',
|
|
'SFlagInp',
|
|
'SGflags',
|
|
'SFirstSect',
|
|
'SNumSects',
|
|
'SFpos',
|
|
'SEndFpos',
|
|
'SFileOperationOffset',
|
|
'SSizeAdjustment',
|
|
'SSlot',
|
|
)
|
|
|
|
PID_PROPERTY_TAGS = (
|
|
'PID_FILE_ADDITION_DATA_STREAM_ENTRY_ARRAY',
|
|
'PID_FILE_ADDITION_DATA_STREAM_TYPE',
|
|
'PID_FILE_DELETION_ENTRY_ARRAY',
|
|
'PID_FILE_ADDITION_DATA_STREAM_LENGTH',
|
|
'PID_FILE_ADDITION_IS_HIDDEN',
|
|
'PID_FILE_ADDITION_DATA_STREAM_NAME',
|
|
'PID_FILE_DELETION_OBJECT_ID',
|
|
'PID_FILE_DELETION_ENTRY_PARENT_DIR_MFTNO',
|
|
'PID_FILE_ADDITION_PATH',
|
|
'PID_FILE_ADDITION_CREATION_DATE_TIME',
|
|
'PID_FILE_ADDITION_IS_WRITABLE',
|
|
'PID_FILE_ADDITION_IS_ARCHIVE',
|
|
'PID_FILE_ADDITION_IS_READABLE',
|
|
'PID_FILE_ADDITION_IS_DIRECTORY',
|
|
'PID_FILE_ADDITION_FILE_NAME',
|
|
'PID_FILE_ADDITION_LAST_ACCESS_DATE_TIME',
|
|
'PID_FILE_ADDITION_IS_DELETED',
|
|
'PID_FILE_ADDITION_CHILD_COUNT',
|
|
'PID_FILE_ADDITION_IS_EXECUTABLE',
|
|
'PID_FILE_DELETION_ENTRY_FILENAME',
|
|
'PID_FILE_ADDITION_DATA_STREAM_OFFSET_IN_FILE',
|
|
'PID_FILE_DELETION_MAIN_MFTNO',
|
|
'PID_FILE_ADDITION_IS_SYSTEM',
|
|
'PID_FILE_DELETION_TOTAL_FILE_ENTRY_COUNT',
|
|
'PID_FILE_ADDITION_MODIFIED_DATE_TIME',
|
|
'PID_FILE_ADDITION_FILE_SIZE',
|
|
|
|
'PID_START_BYTE',
|
|
'PID_GPT_CONTAINER_PS',
|
|
'PID_BIOS_SUPERDISK_CONTAINER_PS',
|
|
'PID_PARTIES_CONTAINER_PS',
|
|
'PID_BIOS_CONTAINER_PS',
|
|
'PID_DYNAMICDISK_CONTAINER_PS',
|
|
'PID_BYTE_COUNT',
|
|
'PID_CLEAR_SIGNATURE',
|
|
'PID_LVM_CONTAINER_PS',
|
|
'PID_DISK_NOTIFY_OS',
|
|
'PID_PARTITION_NODE_FORMAT',
|
|
'PID_CONTAINER_ID',
|
|
'PID_ADDITIONAL_BOOT_CODE_SECTOR',
|
|
'PID_PARENT_BOOT_RECORD_SECTOR_OFFSET',
|
|
'PID_PARENT_BOOT_RECORD_SLOT_NUMBER',
|
|
'PID_WIN_9X_ID_PRESERVE',
|
|
'PID_WIN_9X_ID',
|
|
'PID_WIN_NT_ID',
|
|
'PID_ADDITIONAL_BOOT_CODE',
|
|
'PID_ADDITIONAL_BOOT_CODE_DATA',
|
|
'PID_BOOT_CODE',
|
|
'PID_WIN_NT_ID_PRESERVE',
|
|
'PID_GPT_SLOT_COUNT',
|
|
'PID_GPT_UUID',
|
|
|
|
'PID_VOLUME_NAME',
|
|
'PID_VOLUME_TYPE',
|
|
'PID_FORMAT_TYPE',
|
|
'PID_CLEANLY_SHUTDOWN',
|
|
'PID_NO_HARD_ERROR',
|
|
|
|
'PID_POSITION_ID',
|
|
'PID_VOLUME_DEVICE_NAME',
|
|
'PID_FIND_FIRST',
|
|
'PID_EXTENT_CONTAINER_EXTENT_INDEX',
|
|
'PID_VOLUME_SIZE_MINIMUM_PERCENT',
|
|
'PID_VOLUME_BYTES_PER_BLOCK',
|
|
'PID_FIND_BEST_FIT',
|
|
'PID_VOLUME_SIZE_PREFERRED_PERCENT',
|
|
'PID_VOLUME_DRIVE_LETTER',
|
|
'PID_VOLUME_SIZE_PREFERRED',
|
|
'PID_VOLUME_SLOT_NUMBER',
|
|
'PID_VOLUME_START',
|
|
'PID_VOLUME_ALIGNMENT',
|
|
'PID_EXTENT_START',
|
|
'PID_IS_VOLUME_CONTAINER',
|
|
'PID_FORMAT',
|
|
'PID_ACTIVE',
|
|
'PID_VOLUME_START_PREFERRED',
|
|
'PID_EXTENT_SIZE_MINIMUM',
|
|
'PID_EXTENT',
|
|
'PID_VOLUME_SIZE_ORIGINAL',
|
|
'PID_READ_ONLY',
|
|
'PID_FIND_LAST',
|
|
'PID_EXTENT_OWNER_ID',
|
|
'PID_NAME',
|
|
'PID_ROLE_FORMAT_MATCHING_TYPE',
|
|
'PID_EXTENT_SIZE_PREFERRED',
|
|
'PID_VOLUME_SIZE_MINIMUM',
|
|
'PID_FIND_WORST_FIT',
|
|
'PID_VOLUME_PARTITION_TYPE',
|
|
'PID_EXTENT_START_PREFERRED',
|
|
'PID_VOLUME_SIZE_MAXIMUM',
|
|
'PID_VOLUME_SIZE_MAXIMUM_PERCENT',
|
|
'PID_HIDDEN',
|
|
'PID_IS_VOLUME',
|
|
'PID_ALLOCATE_TYPE',
|
|
'PID_VOLUME_NOTIFY_OS',
|
|
'PID_EXTENT_SIZE_MAXIMUM',
|
|
'PID_INCOMPATIBLE_IMAGE_VERSION',
|
|
'PID_ROLE',
|
|
'PID_SYSTEM_ID',
|
|
|
|
'PID_START_CHS',
|
|
'PID_END_CHS',
|
|
'PID_SECTOR',
|
|
'PID_HEAD',
|
|
'PID_CYLINDER',
|
|
'PID_START_CHS_MAXED_OUT',
|
|
'PID_END_CHS_MAXED_OUT',
|
|
)
|
|
|
|
DrivePropertyTag = make_property_enum('DrivePropertyTag', DRIVE_PROPERTY_TAGS + PID_PROPERTY_TAGS)
|
|
|
|
|
|
class GhostPositionMetadata(Struct):
|
|
magic = Sig(b'\x23\x00\x00\x00\xD8\x18\x2F\x01')
|
|
unk1 = Data(10)
|
|
data_offset = UInt(64)
|
|
index_offset = UInt(64)
|
|
|
|
class GhostHeader(Struct):
|
|
magic = Sig(b'\xFE\xEF')
|
|
type = Enum(GhostHeaderType, UInt(8)) # 1-5
|
|
compression = Enum(GhostCompressionType, UInt(8)) # 0-10
|
|
time = GhostTime()
|
|
bool8 = Bool()
|
|
bool9 = Bool()
|
|
binaryresearch_flag1 = Bool()
|
|
binaryresearch_flag2 = Bool()
|
|
binaryresearch_checksum = Data(15)
|
|
unk27 = Data(10)
|
|
bool37 = Bool()
|
|
unk38 = Data(4)
|
|
bool42 = Bool()
|
|
bool43 = Bool()
|
|
bool44 = Bool()
|
|
bool45 = Bool()
|
|
bool46 = Bool()
|
|
data_encrypted = Bool()
|
|
bool48 = Bool()
|
|
bool49 = Bool()
|
|
bool50 = Bool()
|
|
data_at_end = Bool()
|
|
unk52 = UInt(8) # 3 or 10
|
|
bool53 = Bool()
|
|
seed_offset = UInt(8) # must be 0
|
|
wildcard_filename = Bool()
|
|
bool56 = Bool()
|
|
unk57 = Data(4)
|
|
unk61 = UInt(8) # 0 or 1 or 2
|
|
bool62 = Bool()
|
|
bool63 = Bool()
|
|
bool64 = Bool()
|
|
bool65 = Bool()
|
|
data_length = UInt(32)
|
|
unk70 = Data(5)
|
|
pad75 = Pad(437)
|
|
|
|
def on_data_length(self, spec, context):
|
|
if self.data_length == 0:
|
|
self.data_length = 2048
|
|
|
|
class GhostIndex(Struct, generics=['E']):
|
|
magic = Sig(b'\x05')
|
|
unk1 = UInt(8) # if 3 or 4, special
|
|
_pad2 = Pad(2)
|
|
total = UInt(32)
|
|
length = UInt(16)
|
|
offsets = GhostCompressed(destruct.Arr(UInt(64)), GhostCompressionType.Deflate)
|
|
entries = Arr(Lazy(E))
|
|
|
|
def on_length(self, spec, context):
|
|
spec.offsets.length = self.length
|
|
|
|
def on_offsets(self, spec, context):
|
|
spec.entries.count = len(self.offsets)
|
|
c = spec.entries.child
|
|
spec.entries.child = lambda i: destruct.Ref(c, self.offsets[i]) if self.offsets[i] else destruct.Nothing
|
|
spec.entries = destruct.WithFile(spec.entries, lambda f: GhostCompressedFile(f, GhostCompressionType.Deflate))
|
|
|
|
class GhostIndexSet(Struct, generics=['G']):
|
|
first = GhostIndex[G]
|
|
rest = Arr(GhostIndex[G])
|
|
|
|
def on_first(self, spec, context):
|
|
spec.rest.count = math.ceil(self.first.total / len(self.first.entries)) - 1
|
|
|
|
def parse(self, input, context):
|
|
value = super().parse(input, context)
|
|
for x in value.rest:
|
|
value.first.entries.extend(x.entries)
|
|
return value.first.entries
|
|
|
|
class GhostImage(Struct):
|
|
header = GhostHeader()
|
|
positions = Maybe(Ref(GhostPositionMetadata(), -destruct.sizeof(GhostPositionMetadata), os.SEEK_END))
|
|
properties = Ref(
|
|
Switch(options=dict(
|
|
encrypted=GhostEncrypted(PropertySet[DrivePropertyTag]),
|
|
plain=PropertySet[DrivePropertyTag]
|
|
)),
|
|
0, os.SEEK_CUR
|
|
)
|
|
index = Ref(GhostIndexSet[...], 0, os.SEEK_CUR)
|
|
partitions = Arr([], count=0)
|
|
|
|
def on_header(self, spec, context):
|
|
if self.header.data_encrypted:
|
|
spec.properties.child.selector = 'encrypted'
|
|
spec.properties.child.current.seed = self.header.time.seed[self.header.seed_offset]
|
|
spec.properties.child.current.length = self.header.data_length
|
|
else:
|
|
spec.properties.child.selector = 'plain'
|
|
|
|
def on_positions(self, spec, context):
|
|
spec.properties.reference = os.SEEK_END
|
|
spec.properties.point = -(self.positions.data_offset + destruct.sizeof(GhostPositionMetadata))
|
|
spec.index.reference = os.SEEK_END
|
|
spec.index.point = -(self.positions.index_offset + destruct.sizeof(GhostPositionMetadata))
|
|
|
|
def on_properties(self, spec, context):
|
|
spec.partitions.count = len(self.properties[DrivePropertyTag.SPartitions])
|
|
spec.index.child = self.get_index_parser
|
|
spec.partitions.child = self.get_partition_parser
|
|
|
|
def get_index_parser(self, i):
|
|
partition = self.properties[DrivePropertyTag.SPartitions][0] # i
|
|
p = select_partition_index_parser(partition)
|
|
return GhostIndexSet[p]()
|
|
|
|
def get_partition_parser(self, i):
|
|
partition = self.properties[DrivePropertyTag.SPartitions][0] # i
|
|
pos = partition[DrivePropertyTag.SFpos][0]
|
|
count = partition[DrivePropertyTag.SEndFpos][0] - pos
|
|
|
|
p = select_partition_parser(partition)
|
|
return destruct.Ref(destruct.Capped(p, count), pos)
|