mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-12 22:26:13 -04:00
Updated Dell PFS/PKG Update Extractor v6.0_a15
Padding is now added between sub-PFS PFAT Entry gaps Thanks for the help @NikolajSchlej
This commit is contained in:
parent
6de50c422f
commit
c1f4ab9121
1 changed files with 52 additions and 42 deletions
|
@ -7,7 +7,7 @@ Dell PFS/PKG Update Extractor
|
||||||
Copyright (C) 2018-2022 Plato Mavropoulos
|
Copyright (C) 2018-2022 Plato Mavropoulos
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TITLE = 'Dell PFS/PKG Update Extractor v6.0_a13'
|
TITLE = 'Dell PFS/PKG Update Extractor v6.0_a15'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
|
@ -192,82 +192,82 @@ class DellPfsPfatMetadata(ctypes.LittleEndianStructure):
|
||||||
# Each section starts with a 0x30 header, which begins with pattern 72135500.
|
# Each section starts with a 0x30 header, which begins with pattern 72135500.
|
||||||
# The section length is found at 0x10-0x14 and its (optional) MD5 hash at 0x20-0x30.
|
# The section length is found at 0x10-0x14 and its (optional) MD5 hash at 0x20-0x30.
|
||||||
# Section data can be raw or LZMA2 (7zXZ) compressed. The latter contains the PFS update image.
|
# Section data can be raw or LZMA2 (7zXZ) compressed. The latter contains the PFS update image.
|
||||||
def is_pfs_pkg(in_file):
|
def is_pfs_pkg(input_file):
|
||||||
in_buffer = file_to_bytes(in_file)
|
input_buffer = file_to_bytes(input_file)
|
||||||
|
|
||||||
return PAT_DELL_PKG.search(in_buffer)
|
return PAT_DELL_PKG.search(input_buffer)
|
||||||
|
|
||||||
# The Dell PFS update images usually contain multiple sections.
|
# The Dell PFS update images usually contain multiple sections.
|
||||||
# Each section is zlib-compressed with header pattern ********++EEAA761BECBB20F1E651--789C,
|
# Each section is zlib-compressed with header pattern ********++EEAA761BECBB20F1E651--789C,
|
||||||
# where ******** is the zlib stream size, ++ is the section type and -- the header Checksum XOR 8.
|
# where ******** is the zlib stream size, ++ is the section type and -- the header Checksum XOR 8.
|
||||||
# The "Firmware" section has type AA and its files are stored in PFS format.
|
# The "Firmware" section has type AA and its files are stored in PFS format.
|
||||||
# The "Utility" section has type BB and its files are stored in PFS, BIN or 7z formats.
|
# The "Utility" section has type BB and its files are stored in PFS, BIN or 7z formats.
|
||||||
def is_pfs_hdr(in_file):
|
def is_pfs_hdr(input_file):
|
||||||
in_buffer = file_to_bytes(in_file)
|
input_buffer = file_to_bytes(input_file)
|
||||||
|
|
||||||
return bool(PAT_DELL_HDR.search(in_buffer))
|
return bool(PAT_DELL_HDR.search(input_buffer))
|
||||||
|
|
||||||
# Each section is followed by the footer pattern ********EEAAEE8F491BE8AE143790--,
|
# Each section is followed by the footer pattern ********EEAAEE8F491BE8AE143790--,
|
||||||
# where ******** is the zlib stream size and ++ the footer Checksum XOR 8.
|
# where ******** is the zlib stream size and ++ the footer Checksum XOR 8.
|
||||||
def is_pfs_ftr(in_file):
|
def is_pfs_ftr(input_file):
|
||||||
in_buffer = file_to_bytes(in_file)
|
input_buffer = file_to_bytes(input_file)
|
||||||
|
|
||||||
return bool(PAT_DELL_FTR.search(in_buffer))
|
return bool(PAT_DELL_FTR.search(input_buffer))
|
||||||
|
|
||||||
# Check if input is Dell PFS/PKG image
|
# Check if input is Dell PFS/PKG image
|
||||||
def is_dell_pfs(in_file):
|
def is_dell_pfs(input_file):
|
||||||
in_buffer = file_to_bytes(in_file)
|
input_buffer = file_to_bytes(input_file)
|
||||||
|
|
||||||
is_pkg = is_pfs_pkg(in_buffer)
|
is_pkg = is_pfs_pkg(input_buffer)
|
||||||
|
|
||||||
is_hdr = is_pfs_hdr(in_buffer)
|
is_hdr = is_pfs_hdr(input_buffer)
|
||||||
|
|
||||||
is_ftr = is_pfs_ftr(in_buffer)
|
is_ftr = is_pfs_ftr(input_buffer)
|
||||||
|
|
||||||
return bool(is_pkg or is_hdr and is_ftr)
|
return bool(is_pkg or is_hdr and is_ftr)
|
||||||
|
|
||||||
# Parse & Extract Dell PFS/PKG Update image
|
# Parse & Extract Dell PFS/PKG Update image
|
||||||
def pfs_pkg_parse(in_file, output_path, padding=0, structure=True, advanced=True):
|
def pfs_pkg_parse(input_file, extract_path, padding=0, structure=True, advanced=True):
|
||||||
in_buffer = file_to_bytes(in_file)
|
input_buffer = file_to_bytes(input_file)
|
||||||
|
|
||||||
make_dirs(output_path, delete=True)
|
make_dirs(extract_path, delete=True)
|
||||||
|
|
||||||
is_dell_pkg = is_pfs_pkg(in_buffer)
|
is_dell_pkg = is_pfs_pkg(input_buffer)
|
||||||
|
|
||||||
if is_dell_pkg:
|
if is_dell_pkg:
|
||||||
pfs_results = thinos_pkg_extract(in_buffer, output_path)
|
pfs_results = thinos_pkg_extract(input_buffer, extract_path)
|
||||||
else:
|
else:
|
||||||
pfs_results = {path_stem(in_file) if os.path.isfile(in_file) else 'Image': in_buffer}
|
pfs_results = {path_stem(input_file) if os.path.isfile(input_file) else 'Image': input_buffer}
|
||||||
|
|
||||||
# Parse each Dell PFS image contained in the input file
|
# Parse each Dell PFS image contained in the input file
|
||||||
for pfs_index,(pfs_name,pfs_buffer) in enumerate(pfs_results.items(), start=1):
|
for pfs_index,(pfs_name,pfs_buffer) in enumerate(pfs_results.items(), start=1):
|
||||||
# At ThinOS PKG packages, multiple PFS images may be included in separate model-named folders
|
# At ThinOS PKG packages, multiple PFS images may be included in separate model-named folders
|
||||||
pfs_path = os.path.join(output_path, f'{pfs_index} {pfs_name}') if is_dell_pkg else output_path
|
pfs_path = os.path.join(extract_path, f'{pfs_index} {pfs_name}') if is_dell_pkg else extract_path
|
||||||
# Parse each PFS ZLIB section
|
# Parse each PFS ZLIB section
|
||||||
for zlib_offset in get_section_offsets(pfs_buffer):
|
for zlib_offset in get_section_offsets(pfs_buffer):
|
||||||
# Call the PFS ZLIB section parser function
|
# Call the PFS ZLIB section parser function
|
||||||
pfs_section_parse(pfs_buffer, zlib_offset, pfs_path, pfs_name, pfs_index, 1, False, padding, structure, advanced)
|
pfs_section_parse(pfs_buffer, zlib_offset, pfs_path, pfs_name, pfs_index, 1, False, padding, structure, advanced)
|
||||||
|
|
||||||
# Extract Dell ThinOS PKG 7zXZ
|
# Extract Dell ThinOS PKG 7zXZ
|
||||||
def thinos_pkg_extract(in_file, output_path):
|
def thinos_pkg_extract(input_file, extract_path):
|
||||||
in_buffer = file_to_bytes(in_file)
|
input_buffer = file_to_bytes(input_file)
|
||||||
|
|
||||||
# Initialize PFS results (Name: Buffer)
|
# Initialize PFS results (Name: Buffer)
|
||||||
pfs_results = {}
|
pfs_results = {}
|
||||||
|
|
||||||
# Search input image for ThinOS PKG 7zXZ header
|
# Search input image for ThinOS PKG 7zXZ header
|
||||||
thinos_pkg_match = PAT_DELL_PKG.search(in_buffer)
|
thinos_pkg_match = PAT_DELL_PKG.search(input_buffer)
|
||||||
|
|
||||||
lzma_len_off = thinos_pkg_match.start() + 0x10
|
lzma_len_off = thinos_pkg_match.start() + 0x10
|
||||||
lzma_len_int = int.from_bytes(in_buffer[lzma_len_off:lzma_len_off + 0x4], 'little')
|
lzma_len_int = int.from_bytes(input_buffer[lzma_len_off:lzma_len_off + 0x4], 'little')
|
||||||
lzma_bin_off = thinos_pkg_match.end() - 0x5
|
lzma_bin_off = thinos_pkg_match.end() - 0x5
|
||||||
lzma_bin_dat = in_buffer[lzma_bin_off:lzma_bin_off + lzma_len_int]
|
lzma_bin_dat = input_buffer[lzma_bin_off:lzma_bin_off + lzma_len_int]
|
||||||
|
|
||||||
# Check if the compressed 7zXZ stream is complete
|
# Check if the compressed 7zXZ stream is complete
|
||||||
if len(lzma_bin_dat) != lzma_len_int:
|
if len(lzma_bin_dat) != lzma_len_int:
|
||||||
return pfs_results
|
return pfs_results
|
||||||
|
|
||||||
working_path = os.path.join(output_path, 'THINOS_PKG_TEMP')
|
working_path = os.path.join(extract_path, 'THINOS_PKG_TEMP')
|
||||||
|
|
||||||
make_dirs(working_path, delete=True)
|
make_dirs(working_path, delete=True)
|
||||||
|
|
||||||
|
@ -319,7 +319,7 @@ def get_section_offsets(buffer):
|
||||||
return pfs_zlib_list
|
return pfs_zlib_list
|
||||||
|
|
||||||
# Dell PFS ZLIB Section Parser
|
# Dell PFS ZLIB Section Parser
|
||||||
def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, pfs_count, is_rec, padding=0, structure=True, advanced=True):
|
def pfs_section_parse(zlib_data, zlib_start, extract_path, pfs_name, pfs_index, pfs_count, is_rec, padding=0, structure=True, advanced=True):
|
||||||
is_zlib_error = False # Initialize PFS ZLIB-related error state
|
is_zlib_error = False # Initialize PFS ZLIB-related error state
|
||||||
|
|
||||||
section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB)
|
section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB)
|
||||||
|
@ -329,7 +329,7 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p
|
||||||
printer(f'Extracting Dell PFS {pfs_index} > {pfs_name} > {section_name}', padding)
|
printer(f'Extracting Dell PFS {pfs_index} > {pfs_name} > {section_name}', padding)
|
||||||
|
|
||||||
# Set PFS ZLIB Section extraction sub-directory path
|
# Set PFS ZLIB Section extraction sub-directory path
|
||||||
section_path = os.path.join(output_path, safe_name(section_name))
|
section_path = os.path.join(extract_path, safe_name(section_name))
|
||||||
|
|
||||||
# Create extraction sub-directory and delete old (if present, not in recursions)
|
# Create extraction sub-directory and delete old (if present, not in recursions)
|
||||||
make_dirs(section_path, delete=(not is_rec), parents=True, exist_ok=True)
|
make_dirs(section_path, delete=(not is_rec), parents=True, exist_ok=True)
|
||||||
|
@ -395,7 +395,7 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p
|
||||||
pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, structure, advanced)
|
pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, structure, advanced)
|
||||||
|
|
||||||
# Parse & Extract Dell PFS Volume
|
# Parse & Extract Dell PFS Volume
|
||||||
def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, padding=0, structure=True, advanced=True):
|
def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, extract_path, padding=0, structure=True, advanced=True):
|
||||||
# Show PFS Volume indicator
|
# Show PFS Volume indicator
|
||||||
if structure:
|
if structure:
|
||||||
printer('PFS Volume:', padding)
|
printer('PFS Volume:', padding)
|
||||||
|
@ -619,7 +619,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, padding=0,
|
||||||
sub_pfs_name = f'{info_all[pfs_count - 2][1]} v{info_all[pfs_count - 2][2]}' if info_all else ' UNKNOWN'
|
sub_pfs_name = f'{info_all[pfs_count - 2][1]} v{info_all[pfs_count - 2][2]}' if info_all else ' UNKNOWN'
|
||||||
|
|
||||||
# Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections)
|
# Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections)
|
||||||
sub_pfs_path = os.path.join(output_path, f'{pfs_count} {safe_name(sub_pfs_name)}')
|
sub_pfs_path = os.path.join(extract_path, f'{pfs_count} {safe_name(sub_pfs_name)}')
|
||||||
|
|
||||||
# Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count)
|
# Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count)
|
||||||
pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, padding + 4, structure, advanced)
|
pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, padding + 4, structure, advanced)
|
||||||
|
@ -691,7 +691,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, padding=0,
|
||||||
# Write/Extract PFS Entry files
|
# Write/Extract PFS Entry files
|
||||||
for file in write_files:
|
for file in write_files:
|
||||||
full_name = f'{pfs_index} {pfs_name} -- {file_index} {file_name} v{file_version}' # Full PFS Entry Name
|
full_name = f'{pfs_index} {pfs_name} -- {file_index} {file_name} v{file_version}' # Full PFS Entry Name
|
||||||
pfs_file_write(file[0], file[1], file_type, full_name, output_path, padding, structure, advanced)
|
pfs_file_write(file[0], file[1], file_type, full_name, extract_path, padding, structure, advanced)
|
||||||
|
|
||||||
# Get PFS Footer Data after PFS Header Payload
|
# Get PFS Footer Data after PFS Header Payload
|
||||||
pfs_footer = buffer[PFS_HEAD_LEN + pfs_hdr.PayloadSize:PFS_HEAD_LEN + pfs_hdr.PayloadSize + PFS_FOOT_LEN]
|
pfs_footer = buffer[PFS_HEAD_LEN + pfs_hdr.PayloadSize:PFS_HEAD_LEN + pfs_hdr.PayloadSize + PFS_FOOT_LEN]
|
||||||
|
@ -766,7 +766,7 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True):
|
||||||
pfat_footer = entry_data[PFS_HEAD_LEN + entry_hdr.PayloadSize:PFS_HEAD_LEN + entry_hdr.PayloadSize + PFS_FOOT_LEN]
|
pfat_footer = entry_data[PFS_HEAD_LEN + entry_hdr.PayloadSize:PFS_HEAD_LEN + entry_hdr.PayloadSize + PFS_FOOT_LEN]
|
||||||
|
|
||||||
# Parse all sub-PFS Payload PFAT Entries
|
# Parse all sub-PFS Payload PFAT Entries
|
||||||
pfat_data_all = [] # Storage for all sub-PFS PFAT Entries Order/Offset & Payload/Raw Data
|
pfat_entries_all = [] # Storage for all sub-PFS PFAT Entries Order/Offset & Payload/Raw Data
|
||||||
pfat_entry_start = 0 # Increasing sub-PFS PFAT Entry start offset
|
pfat_entry_start = 0 # Increasing sub-PFS PFAT Entry start offset
|
||||||
pfat_entry_index = 0 # Increasing sub-PFS PFAT Entry count index
|
pfat_entry_index = 0 # Increasing sub-PFS PFAT Entry count index
|
||||||
_, pfs_entry_size = get_pfs_entry(pfat_payload, 0) # Get initial PFS PFAT Entry Size for loop
|
_, pfs_entry_size = get_pfs_entry(pfat_payload, 0) # Get initial PFS PFAT Entry Size for loop
|
||||||
|
@ -855,26 +855,36 @@ def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True):
|
||||||
pfat_entry_data_raw = pfat_payload_data # Prefer Data from PFAT Block, in case PFAT Entry differs
|
pfat_entry_data_raw = pfat_payload_data # Prefer Data from PFAT Block, in case PFAT Entry differs
|
||||||
|
|
||||||
# Store each sub-PFS PFAT Entry Order/Offset and Payload/Raw Data (w/o PFAT)
|
# Store each sub-PFS PFAT Entry Order/Offset and Payload/Raw Data (w/o PFAT)
|
||||||
pfat_data_all.append((pfat_entry_off, pfat_entry_data_raw))
|
pfat_entries_all.append((pfat_entry_off, pfat_entry_data_raw))
|
||||||
|
|
||||||
pfat_entry_start = pfat_next_entry # Next sub-PFS PFAT Entry starts after sub-PFS Entry Metadata Signature
|
pfat_entry_start = pfat_next_entry # Next sub-PFS PFAT Entry starts after sub-PFS Entry Metadata Signature
|
||||||
|
|
||||||
pfat_entry_index += 1
|
pfat_entry_index += 1
|
||||||
|
|
||||||
pfat_data_all.sort() # Sort all sub-PFS PFAT Entries payloads/data based on their Order/Offset
|
pfat_entries_all.sort() # Sort all sub-PFS PFAT Entries payloads/data based on their Order/Offset
|
||||||
|
|
||||||
entry_data = b'' # Initialize new sub-PFS Entry Data
|
sorted_start_expected = pfat_entries_all[0][0] # Initialize sub-PFS PFAT Entry expected Offset
|
||||||
for pfat_data in pfat_data_all:
|
final_entry_data = b'' # Initialize final sub-PFS Entry Data from ordered PFAT Entries
|
||||||
entry_data += pfat_data[1] # Merge all sub-PFS PFAT Entry Payload/Raw into the final sub-PFS Entry Data
|
|
||||||
|
|
||||||
# Verify that the Order/Offset of the last PFAT Entry w/ its Size matches the final sub-PFS Entry Data Size
|
# Parse all sorted sub-PFS PFAT Entries and merge their payload/data
|
||||||
if len(entry_data) != pfat_data_all[-1][0] + len(pfat_data_all[-1][1]):
|
for sorted_start,sorted_data in pfat_entries_all:
|
||||||
|
# Fill any data gaps between sorted sub-PFS PFAT Entries with padding
|
||||||
|
if sorted_start != sorted_start_expected:
|
||||||
|
# The sub-PFS PFAT Entry expected Start is the previous Offset + Size
|
||||||
|
final_entry_data += b'\xFF' * (sorted_start - sorted_start_expected)
|
||||||
|
|
||||||
|
final_entry_data += sorted_data # Append sorted sub-PFS PFAT Entry payload/data
|
||||||
|
|
||||||
|
sorted_start_expected = sorted_start + len(sorted_data) # Set next sub-PFS PFAT Entry expected Start
|
||||||
|
|
||||||
|
# Verify that the end offset of the last PFAT Entry matches the final sub-PFS Entry Data Size
|
||||||
|
if len(final_entry_data) != pfat_entries_all[-1][0] + len(pfat_entries_all[-1][1]):
|
||||||
printer('Error: Detected sub-PFS PFAT Entry Buffer & Last Offset Size mismatch!', padding + 8)
|
printer('Error: Detected sub-PFS PFAT Entry Buffer & Last Offset Size mismatch!', padding + 8)
|
||||||
|
|
||||||
# Analyze sub-PFS Footer Structure
|
# Analyze sub-PFS Footer Structure
|
||||||
chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padding + 4, structure)
|
chk_pfs_ftr(pfat_footer, pfat_payload, entry_hdr.PayloadSize, 'Sub-PFS', padding + 4, structure)
|
||||||
|
|
||||||
return entry_data
|
return final_entry_data
|
||||||
|
|
||||||
# Get Dell PFS Entry Structure & Size via its Version
|
# Get Dell PFS Entry Structure & Size via its Version
|
||||||
def get_pfs_entry(buffer, offset):
|
def get_pfs_entry(buffer, offset):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue