mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-09 13:52:00 -04:00
Dell PFS BIOS Extractor v4.0
Added PFS Revision 2 support
This commit is contained in:
parent
27d3364f58
commit
3ee5628ee3
2 changed files with 83 additions and 26 deletions
|
@ -3,11 +3,11 @@
|
|||
"""
|
||||
Dell PFS Extract
|
||||
Dell PFS BIOS Extractor
|
||||
Copyright (C) 2019 Plato Mavropoulos
|
||||
Copyright (C) 2019-2020 Plato Mavropoulos
|
||||
Inspired from https://github.com/LongSoft/PFSExtractor-RS by Nikolaj Schlej
|
||||
"""
|
||||
|
||||
title = 'Dell PFS BIOS Extractor v3.6'
|
||||
title = 'Dell PFS BIOS Extractor v4.0'
|
||||
|
||||
import os
|
||||
import re
|
||||
|
@ -26,7 +26,6 @@ uint16_t = ctypes.c_ushort
|
|||
uint32_t = ctypes.c_uint
|
||||
uint64_t = ctypes.c_uint64
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
class PFS_HDR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
|
@ -42,7 +41,6 @@ class PFS_HDR(ctypes.LittleEndianStructure) :
|
|||
print('HeaderVersion : %d' % self.HeaderVersion)
|
||||
print('PayloadSize : 0x%X' % self.PayloadSize)
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
class PFS_FTR(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
|
@ -58,12 +56,11 @@ class PFS_FTR(ctypes.LittleEndianStructure) :
|
|||
print('Checksum : 0x%0.8X' % self.Checksum)
|
||||
print('Tag : %s' % self.Tag.decode('utf-8'))
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
class PFS_ENTRY(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('GUID', uint32_t*4), # 0x00 Little Endian
|
||||
('HeaderVersion', uint32_t), # 0x10
|
||||
('HeaderVersion', uint32_t), # 0x10 1
|
||||
('VersionType', uint8_t*4), # 0x14
|
||||
('Version', uint16_t*4), # 0x18
|
||||
('Reserved', uint64_t), # 0x20
|
||||
|
@ -93,7 +90,40 @@ class PFS_ENTRY(ctypes.LittleEndianStructure) :
|
|||
print('DataMetSigSize : 0x%X' % self.DataMetSigSize)
|
||||
print('Unknown : %s' % Unknown)
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
class PFS_ENTRY_R2(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
('GUID', uint32_t*4), # 0x00 Little Endian
|
||||
('HeaderVersion', uint32_t), # 0x10 2
|
||||
('VersionType', uint8_t*4), # 0x14
|
||||
('Version', uint16_t*4), # 0x18
|
||||
('Reserved', uint64_t), # 0x20
|
||||
('DataSize', uint32_t), # 0x28
|
||||
('DataSigSize', uint32_t), # 0x2C
|
||||
('DataMetSize', uint32_t), # 0x30
|
||||
('DataMetSigSize', uint32_t), # 0x34
|
||||
('Unknown', uint32_t*8), # 0x38
|
||||
# 0x58
|
||||
]
|
||||
|
||||
def pfs_print(self) :
|
||||
GUID = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.GUID))
|
||||
VersionType = ''.join('%0.4X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.VersionType))
|
||||
Version = ''.join('%0.4X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Version))
|
||||
Unknown = ''.join('%0.8X' % int.from_bytes(struct.pack('<I', val), 'little') for val in reversed(self.Unknown))
|
||||
|
||||
print('\nPFS Entry:\n')
|
||||
print('GUID : %s' % GUID)
|
||||
print('HeaderVersion : %d' % self.HeaderVersion)
|
||||
print('VersionType : %s' % VersionType)
|
||||
print('Version : %s' % Version)
|
||||
print('Reserved : 0x%X' % self.Reserved)
|
||||
print('DataSize : 0x%X' % self.DataSize)
|
||||
print('DataSigSize : 0x%X' % self.DataSigSize)
|
||||
print('DataMetSize : 0x%X' % self.DataMetSize)
|
||||
print('DataMetSigSize : 0x%X' % self.DataMetSigSize)
|
||||
print('Unknown : %s' % Unknown)
|
||||
|
||||
class PFS_INFO(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
|
@ -117,7 +147,6 @@ class PFS_INFO(ctypes.LittleEndianStructure) :
|
|||
print('VersionType : %s' % VersionType)
|
||||
print('CharacterCount : %d' % (self.CharacterCount * 2))
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
class METADATA_INFO(ctypes.LittleEndianStructure) :
|
||||
_pack_ = 1
|
||||
_fields_ = [
|
||||
|
@ -160,7 +189,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
|||
return # Critical error, abort
|
||||
|
||||
# Validate that a known PFS Header Version was encountered
|
||||
if pfs_hdr.HeaderVersion != 1 :
|
||||
if pfs_hdr.HeaderVersion not in (1,2) :
|
||||
print('\n Error: Unknown PFS Header Version %d!' % pfs_hdr.HeaderVersion)
|
||||
|
||||
# Get PFS Footer Data after PFS Header Payload
|
||||
|
@ -192,12 +221,13 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
|||
entry_start = 0 # Increasing PFS Entry starting offset
|
||||
entries_all = [] # Storage for each PFS Entry details
|
||||
pfs_info = [] # Buffer for PFS Information Entry Data
|
||||
pfs_entry_struct, pfs_entry_size = get_pfs_entry(payload, entry_start)
|
||||
while len(payload[entry_start:entry_start + pfs_entry_size]) == pfs_entry_size :
|
||||
# Get PFS Entry Structure values
|
||||
pfs_entry = get_struct(payload, entry_start, PFS_ENTRY)
|
||||
pfs_entry = get_struct(payload, entry_start, pfs_entry_struct)
|
||||
|
||||
# Validate that a known PFS Entry Header Version was encountered
|
||||
if pfs_entry.HeaderVersion != 1 :
|
||||
if pfs_entry.HeaderVersion not in (1,2) :
|
||||
print('\n Error: Unknown PFS Entry Header Version %d!' % pfs_entry.HeaderVersion)
|
||||
|
||||
# Validate that the PFS Entry Reserved field is empty
|
||||
|
@ -321,12 +351,23 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
|||
# The 0xE sized zlib "BIOS" section pattern (0xAA type) should be found after the Compressed Size
|
||||
zlib_bios_match = zlib_bios_pattern.search(entry_data)
|
||||
|
||||
# Check if a sub PFS Header with Payload in Chunked Entries was encountered
|
||||
# Chunk Entries can be determined via the "Dell" string at offset 0x5C
|
||||
if entry_hdr.Tag == b'PFS.HDR.' and entry_data[0x5C:0x60] == b'Dell' :
|
||||
# Check if a sub PFS Header with Payload has Chunked Entries
|
||||
# Chunked Entries can be determined via the "DellX" string
|
||||
pfs_entry_struct, pfs_entry_size = get_pfs_entry(entry_data, pfs_header_size)
|
||||
chunk_tag_off = pfs_header_size + pfs_entry_size + 0x4 # Chunk Tag starts at 0x4 and is probably 0x10 sized
|
||||
chunk_tag = entry_data[chunk_tag_off:chunk_tag_off + 0x10].replace(b'\x00',b'\x20').decode('utf-8','ignore').strip()
|
||||
|
||||
if chunk_tag in ('DellX7','DellX11') :
|
||||
is_chunk = True
|
||||
elif chunk_tag.startswith('Dell') :
|
||||
is_chunk = True
|
||||
print('\n Error: Unknown sub PFS Entry Chunk Tag %s!' % chunk_tag)
|
||||
else :
|
||||
is_chunk = False
|
||||
|
||||
if entry_hdr.Tag == b'PFS.HDR.' and is_chunk :
|
||||
# Validate that a known sub PFS Header Version was encountered
|
||||
if entry_hdr.HeaderVersion != 1 :
|
||||
if entry_hdr.HeaderVersion not in (1,2) :
|
||||
print('\n Error: Unknown sub PFS Entry Header Version %d!' % entry_hdr.HeaderVersion)
|
||||
|
||||
# Get sub PFS Footer Data after sub PFS Header Payload
|
||||
|
@ -356,12 +397,13 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
|||
# Parse all sub PFS Payload Entries/Chunks
|
||||
chunk_data_all = [] # Storage for each sub PFS Entry/Chunk Order + Data
|
||||
chunk_entry_start = 0 # Increasing sub PFS Entry/Chunk starting offset
|
||||
pfs_entry_struct, pfs_entry_size = get_pfs_entry(chunks_payload, chunk_entry_start) # Get PFS_HDR Info
|
||||
while len(chunks_payload[chunk_entry_start:chunk_entry_start + pfs_entry_size]) == pfs_entry_size :
|
||||
# Get sub PFS Entry Structure values
|
||||
pfs_chunk_entry = get_struct(chunks_payload, chunk_entry_start, PFS_ENTRY)
|
||||
pfs_chunk_entry = get_struct(chunks_payload, chunk_entry_start, pfs_entry_struct)
|
||||
|
||||
# Validate that a known sub PFS Entry Header Version was encountered
|
||||
if pfs_chunk_entry.HeaderVersion != 1 :
|
||||
if pfs_chunk_entry.HeaderVersion not in (1,2) :
|
||||
print('\n Error: Unknown sub PFS Chunk Entry Header Version %d!' % pfs_chunk_entry.HeaderVersion)
|
||||
|
||||
# Validate that the sub PFS Entry Reserved field is empty
|
||||
|
@ -372,10 +414,16 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
|||
# This is not useful as the Version of each Chunk does not matter at all
|
||||
chunk_entry_version = get_version(pfs_chunk_entry.Version, pfs_chunk_entry.VersionType)
|
||||
|
||||
# Each sub PFS Payload Entry/Chunk includes some Extra Chunk Data/Information at the beginning
|
||||
# We must determine the Chunk Extra Info size to remove its Data from the final Chunk Raw Data
|
||||
# The Chunk Extra Info consists of a Header 0x28 (?), variable sized Flags & End of Flags (0x8)
|
||||
chunk_raw_size_off = chunk_entry_start + pfs_entry_size + 0x20 # Chunk Raw Data Size is at 0x20-0x24
|
||||
chunk_raw_size = int.from_bytes(chunks_payload[chunk_raw_size_off:chunk_raw_size_off + 0x4], 'little')
|
||||
chunk_info_size = pfs_chunk_entry.DataSize - chunk_raw_size # Get Chunk Extra Info size
|
||||
|
||||
# The sub PFS Payload Entries/Chunks are not in proper order by default
|
||||
# Each Chunk includes a 0x248 sized Header followed by the Chunk Data but
|
||||
# we are only interested in byte 0x86 which holds the Chunk Order Number
|
||||
chunk_entry_number = chunks_payload[chunk_entry_start + 0x86]
|
||||
# However, we can get the Chunk Order Number from a Chunk Extra Info byte
|
||||
chunk_entry_number = chunks_payload[chunk_entry_start + pfs_entry_size + 0x3E] # Chunk Order Number is at 0x3E
|
||||
|
||||
# Sub PFS Entry Data starts after the sub PFS Entry Structure
|
||||
chunk_entry_data_start = chunk_entry_start + pfs_entry_size
|
||||
|
@ -398,17 +446,19 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count) :
|
|||
chunk_entry_met = chunks_payload[chunk_entry_met_start:chunk_entry_met_end] # Store sub PFS Entry Metadata
|
||||
chunk_entry_met_sig = chunks_payload[chunk_entry_met_sig_start:chunk_entry_met_sig_end] # Store sub PFS Entry Metadata Signature
|
||||
|
||||
# Store each sub PFS Entry/Chunk Order Number & Data
|
||||
chunk_data_all.append((chunk_entry_number, chunk_entry_data))
|
||||
# Store each sub PFS Entry/Chunk Extra Info Size, Order Number & Raw Data
|
||||
chunk_data_all.append((chunk_entry_number, chunk_entry_data, chunk_info_size))
|
||||
|
||||
chunk_entry_start = chunk_entry_met_sig_end # Next sub PFS Entry/Chunk starts after sub PFS Entry Metadata Signature
|
||||
|
||||
pfs_entry_struct, pfs_entry_size = get_pfs_entry(chunks_payload, chunk_entry_start) # Get Next PFS_HDR Info
|
||||
|
||||
chunk_data_all.sort() # Sort all sub PFS Entries/Chunks based on their Order Number
|
||||
|
||||
entry_data = b'' # Initialize new PFS Entry Data
|
||||
for chunk in chunk_data_all :
|
||||
# Merge all sub PFS Chunks into the final new PFS Entry Data
|
||||
entry_data += chunk[1][0x248:] # Skip the sub PFS Chunk Header (0x248) when merging
|
||||
entry_data += chunk[1][chunk[2]:] # Skip the sub PFS Chunk Extra Info when merging
|
||||
|
||||
entry_type = 'CHUNKS' # Re-set PFS Entry Type from OTHER to CHUNKS, in case such info is needed afterwards
|
||||
|
||||
|
@ -561,6 +611,14 @@ def get_version(version_fields, version_types) :
|
|||
|
||||
return version
|
||||
|
||||
# Get PFS Entry Structure & Size via its Version
|
||||
def get_pfs_entry(buffer, offset) :
|
||||
pfs_entry_ver = int.from_bytes(buffer[offset + 0x10:offset + 0x14], 'little') # PFS Entry Version
|
||||
|
||||
if pfs_entry_ver == 1 : return PFS_ENTRY, ctypes.sizeof(PFS_ENTRY)
|
||||
elif pfs_entry_ver == 2 : return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2)
|
||||
else : return PFS_ENTRY_R2, ctypes.sizeof(PFS_ENTRY_R2)
|
||||
|
||||
# Process ctypes Structure Classes
|
||||
def get_struct(buffer, start_offset, class_name, param_list = None) :
|
||||
if param_list is None : param_list = []
|
||||
|
@ -612,7 +670,6 @@ args = parser.parse_args()
|
|||
# Get ctypes Structure Sizes
|
||||
pfs_header_size = ctypes.sizeof(PFS_HDR)
|
||||
pfs_footer_size = ctypes.sizeof(PFS_FTR)
|
||||
pfs_entry_size = ctypes.sizeof(PFS_ENTRY)
|
||||
pfs_info_size = ctypes.sizeof(PFS_INFO)
|
||||
met_info_size = ctypes.sizeof(METADATA_INFO)
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ Various BIOS Utilities for Modding/Research
|
|||
|
||||
#### **Description**
|
||||
|
||||
Parses modern icon-less Dell PFS BIOS executables and extracts their SPI/BIOS/UEFI firmware components. It supports all Dell PFS formats, including those which are originally compressed or split in chunks. The output comprises only final firmware components which are directly usable by end users. An optional Advanced user mode is available as well, which additionally extracts firmware Signatures and more Metadata.
|
||||
Parses modern icon-less Dell PFS BIOS executables and extracts their SPI/BIOS/UEFI firmware components. It supports all Dell PFS revisions and formats, including those which are originally compressed or split in chunks. The output comprises only final firmware components which are directly usable by end users. An optional Advanced user mode is available as well, which additionally extracts firmware Signatures and more Metadata.
|
||||
|
||||
#### **Usage**
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue