BIOSUtilities v24.10.06

24.10.06

Changed BIOSUtility.parse_format() to return a boolean
Changed 7-Zip and EFI decompressors to return booleans
Apple EFI Package Extractor support for InstallAssistant
Apple EFI Image Identifier support for Apple ROM Version
Added Apple EFI Image Identifier class instance attributes
Improved flow of non-PATH external executable dependencies
Fixed crash when attempting to clear read-only attribute
Fixed incompatibility with Python versions prior to 3.12
Performance improvements when initializing BIOSUtilities
Improved argument naming and definitions of "main" script
Improved the README with new "main" and Apple EFI changes
This commit is contained in:
Plato Mavropoulos 2024-10-07 01:24:12 +03:00
parent 4175af9eb1
commit eda154b0f2
26 changed files with 346 additions and 223 deletions

View file

@ -1,3 +1,17 @@
24.10.06
Changed BIOSUtility.parse_format() to return a boolean
Changed 7-Zip and EFI decompressors to return booleans
Apple EFI Package Extractor support for InstallAssistant
Apple EFI Image Identifier support for Apple ROM Version
Added Apple EFI Image Identifier class instance attributes
Improved flow of non-PATH external executable dependencies
Fixed crash when attempting to clear read-only attribute
Fixed incompatibility with Python versions prior to 3.12
Performance improvements when initializing BIOSUtilities
Improved argument naming and definitions of "main" script
Improved the README with new "main" and Apple EFI changes
24.10.03 24.10.03
Added external executable dependencies non-PATH flow (#51) Added external executable dependencies non-PATH flow (#51)

View file

@ -11,19 +11,19 @@ Various BIOS/UEFI-related utilities which aid in modding and/or research
The "main" script provides a simple way to check and parse each of the user provided files against all utilities, in succession. It is ideal for quick drag & drop operations but lacks the finer control of the BIOSUtility method. If needed, a few options can be set, by using the command line: The "main" script provides a simple way to check and parse each of the user provided files against all utilities, in succession. It is ideal for quick drag & drop operations but lacks the finer control of the BIOSUtility method. If needed, a few options can be set, by using the command line:
``` bash ``` bash
usage: main.py [-h] [--output-folder OUTPUT_FOLDER] [--pause-exit] input_files [input_files ...] usage: [-h] [-e] [-o OUTPUT_DIR] paths [paths ...]
positional arguments: positional arguments:
input_files paths
options: options:
-h, --help show help message and exit -h, --help show help and exit
--output-folder OUTPUT_FOLDER extraction folder -e, --auto-exit do not pause on exit
--pause-exit pause on exit -o OUTPUT_DIR, --output-dir OUTPUT_DIR extraction directory
``` ```
``` bash ``` bash
python ./main.py "/path/to/input/file.bin" --output-folder "/path/to/file extractions" python ./main.py "/path/to/input/file.bin" --output-dir "/path/to/file extractions"
``` ```
### BIOSUtility ### BIOSUtility
@ -37,7 +37,7 @@ positional arguments:
paths paths
options: options:
-h, --help show help message and exit -h, --help show help and exit
-e, --auto-exit skip user action prompts -e, --auto-exit skip user action prompts
-o OUTPUT_DIR, --output-dir OUTPUT_DIR output extraction directory -o OUTPUT_DIR, --output-dir OUTPUT_DIR output extraction directory
``` ```
@ -108,7 +108,7 @@ It also allows to use directly the four public methods which are inherited by ev
Run utility after checking for supported format Run utility after checking for supported format
``` python ``` python
run_utility(padding: int = 0) -> int run_utility(padding: int = 0) -> bool
``` ```
#### check_format #### check_format
@ -124,7 +124,7 @@ check_format(input_object: str | bytes | bytearray) -> bool
Process input object as a specific supported format Process input object as a specific supported format
``` python ``` python
parse_format(input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int | None parse_format(input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool
``` ```
#### show_version #### show_version
@ -148,7 +148,7 @@ python -m pip install --upgrade -r requirements.txt
``` ```
``` bash ``` bash
python -m pip install --upgrade pefile python -m pip install --upgrade pefile dissect.util
``` ```
### External Executables / Scripts ### External Executables / Scripts
@ -269,7 +269,7 @@ To run the utility, you do not need any prerequisites.
#### **Description** #### **Description**
Parses Apple EFI images and identifies them based on Intel's official $IBIOSI$ tag, which contains info such as Model, Version, Build, Date and Time. Optionally, the utility can rename the input Apple EFI image based on the retrieved $IBIOSI$ tag info, while also making sure to differentiate any EFI images with the same $IBIOSI$ tag (e.g. Production, Pre-Production) by appending a checksum of their data. Parses Apple EFI images and identifies them based on Intel's official "IBIOSI" tag, which contains info such as Model, Version, Build, Date and Time. Additionally, the utility can provide both "IBIOSI" and "Apple ROM Version" structure info, when available, as well as a suggested EFI image filename, while also making sure to differentiate any EFI images with the same "IBIOSI" tag (e.g. Production, Pre-Production) by appending a checksum of their data.
#### **Usage** #### **Usage**
@ -290,7 +290,7 @@ To run the utility, you must have the following 3rd party tools at PATH or "exte
#### **Description** #### **Description**
Parses Apple EFI PKG firmware packages (i.e. FirmwareUpdate.pkg, BridgeOSUpdateCustomer.pkg), extracts their EFI images, splits those in IM4P format and identifies/renames the final Intel SPI/BIOS images accordingly. The output comprises only final firmware components which are directly usable by end users. Parses Apple EFI PKG firmware packages (e.g. FirmwareUpdate.pkg, BridgeOSUpdateCustomer.pkg, InstallAssistant.pkg, iMacEFIUpdate.pkg, iMacFirmwareUpdate.tar), extracts their EFI images, splits those in IM4P format and identifies/renames the final Intel SPI/BIOS images accordingly. The output comprises only final firmware components which are directly usable by end users.
#### **Usage** #### **Usage**

View file

@ -5,4 +5,4 @@
Copyright (C) 2018-2024 Plato Mavropoulos Copyright (C) 2018-2024 Plato Mavropoulos
""" """
__version__ = '24.10.03' __version__ = '24.10.06'

View file

@ -12,7 +12,7 @@ import os
import re import re
import struct import struct
from typing import Any, Type from typing import Any, Final, Type
from biosutilities.common.externals import big_script_tool from biosutilities.common.externals import big_script_tool
from biosutilities.common.paths import extract_suffix, extract_folder, make_dirs, path_name, safe_name from biosutilities.common.paths import extract_suffix, extract_folder, make_dirs, path_name, safe_name
@ -71,6 +71,7 @@ class IntelBiosGuardHeader(ctypes.LittleEndianStructure):
id_text: str = re.sub(r'[\n\t\r\x00 ]', '', id_byte.decode(encoding='utf-8', errors='ignore')) id_text: str = re.sub(r'[\n\t\r\x00 ]', '', id_byte.decode(encoding='utf-8', errors='ignore'))
id_hexs: str = f'{int.from_bytes(bytes=id_byte, byteorder="big"):0{0x10 * 2}X}' id_hexs: str = f'{int.from_bytes(bytes=id_byte, byteorder="big"):0{0x10 * 2}X}'
id_guid: str = f'{{{id_hexs[:8]}-{id_hexs[8:12]}-{id_hexs[12:16]}-{id_hexs[16:20]}-{id_hexs[20:]}}}' id_guid: str = f'{{{id_hexs[:8]}-{id_hexs[8:12]}-{id_hexs[12:16]}-{id_hexs[16:20]}-{id_hexs[20:]}}}'
return f'{id_text} {id_guid}' return f'{id_text} {id_guid}'
@ -205,12 +206,12 @@ class AmiPfatExtract(BIOSUtility):
TITLE: str = 'AMI BIOS Guard Extractor' TITLE: str = 'AMI BIOS Guard Extractor'
PFAT_AMI_HDR_LEN: int = ctypes.sizeof(AmiBiosGuardHeader) PFAT_AMI_HDR_LEN: Final[int] = ctypes.sizeof(AmiBiosGuardHeader)
PFAT_INT_HDR_LEN: int = ctypes.sizeof(IntelBiosGuardHeader) PFAT_INT_HDR_LEN: Final[int] = ctypes.sizeof(IntelBiosGuardHeader)
PFAT_INT_SIG_HDR_LEN: int = ctypes.sizeof(IntelBiosGuardSignatureHeader) PFAT_INT_SIG_HDR_LEN: Final[int] = ctypes.sizeof(IntelBiosGuardSignatureHeader)
PFAT_INT_SIG_R2K_LEN: int = ctypes.sizeof(IntelBiosGuardSignatureRsa2k) PFAT_INT_SIG_R2K_LEN: Final[int] = ctypes.sizeof(IntelBiosGuardSignatureRsa2k)
PFAT_INT_SIG_R3K_LEN: int = ctypes.sizeof(IntelBiosGuardSignatureRsa3k) PFAT_INT_SIG_R3K_LEN: Final[int] = ctypes.sizeof(IntelBiosGuardSignatureRsa3k)
PFAT_INT_SIG_MAX_LEN: int = PFAT_INT_SIG_HDR_LEN + PFAT_INT_SIG_R3K_LEN PFAT_INT_SIG_MAX_LEN: Final[int] = PFAT_INT_SIG_HDR_LEN + PFAT_INT_SIG_R3K_LEN
def check_format(self, input_object: str | bytes | bytearray) -> bool: def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is AMI BIOS Guard """ """ Check if input is AMI BIOS Guard """
@ -219,7 +220,7 @@ class AmiPfatExtract(BIOSUtility):
return bool(self._get_ami_pfat(input_object=input_buffer)) return bool(self._get_ami_pfat(input_object=input_buffer))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Process and store AMI BIOS Guard output file """ """ Process and store AMI BIOS Guard output file """
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
@ -317,7 +318,7 @@ class AmiPfatExtract(BIOSUtility):
with open(file=in_all_path, mode='wb') as out_all: with open(file=in_all_path, mode='wb') as out_all:
out_all.write(in_all_data + pfat_oob_data) out_all.write(in_all_data + pfat_oob_data)
return 0 return True
def parse_bg_sign(self, input_data: bytes, sign_offset: int, sign_length: int = 0, def parse_bg_sign(self, input_data: bytes, sign_offset: int, sign_length: int = 0,
print_info: bool = False, padding: int = 0) -> int: print_info: bool = False, padding: int = 0) -> int:

View file

@ -13,12 +13,12 @@ import os
import re import re
import struct import struct
from typing import Any from typing import Any, Final
from biosutilities.common.checksums import checksum_16 from biosutilities.common.checksums import checksum_16
from biosutilities.common.compression import efi_decompress, is_efi_compressed from biosutilities.common.compression import efi_decompress, is_efi_compressed
from biosutilities.common.paths import agnostic_path, extract_folder, make_dirs, safe_name, safe_path from biosutilities.common.paths import agnostic_path, extract_folder, make_dirs, safe_name, safe_path
from biosutilities.common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from biosutilities.common.patterns import PAT_AMI_UCP, PAT_INTEL_ENGINE
from biosutilities.common.structs import CHAR, ctypes_struct, UINT8, UINT16, UINT32 from biosutilities.common.structs import CHAR, ctypes_struct, UINT8, UINT16, UINT32
from biosutilities.common.system import printer from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility from biosutilities.common.templates import BIOSUtility
@ -101,11 +101,12 @@ class UiiHeader(ctypes.LittleEndianStructure):
# 0x10 # 0x10
] ]
SBI: dict[int, str] = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'} SBI: Final[dict[int, str]] = {1: 'ALL', 2: 'AMIBIOS8', 3: 'UEFI', 4: 'AMIBIOS8/UEFI'}
SOS: dict[int, str] = {1: 'DOS', 2: 'EFI', 3: 'Windows', 4: 'Linux', 5: 'FreeBSD', 6: 'MacOS', 128: 'Multi-OS'} SOS: Final[dict[int, str]] = {1: 'DOS', 2: 'EFI', 3: 'Windows', 4: 'Linux', 5: 'FreeBSD',
DBW: dict[int, str] = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'} 6: 'MacOS', 128: 'Multi-Platform'}
PTP: dict[int, str] = {1: 'Executable', 2: 'Library', 3: 'Driver'} DBW: Final[dict[int, str]] = {1: '16b', 2: '16/32b', 3: '32b', 4: '64b'}
PMD: dict[int, str] = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'} PTP: Final[dict[int, str]] = {1: 'Executable', 2: 'Library', 3: 'Driver'}
PMD: Final[dict[int, str]] = {1: 'API', 2: 'Console', 3: 'GUI', 4: 'Console/GUI'}
def struct_print(self, description: str, padding: int = 0) -> None: def struct_print(self, description: str, padding: int = 0) -> None:
""" Display structure information """ """ Display structure information """
@ -160,8 +161,8 @@ class DisModule(ctypes.LittleEndianStructure):
# 0x122 # 0x122
] ]
ENDIS: dict[int, str] = {0: 'Disabled', 1: 'Enabled'} ENDIS: Final[dict[int, str]] = {0: 'Disabled', 1: 'Enabled'}
SHOWN: dict[int, str] = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'} SHOWN: Final[dict[int, str]] = {0: 'Hidden', 1: 'Shown', 2: 'Shown Only'}
def struct_print(self, padding: int = 0) -> None: def struct_print(self, padding: int = 0) -> None:
""" Display structure information """ """ Display structure information """
@ -187,14 +188,14 @@ class AmiUcpExtract(BIOSUtility):
] ]
# Get common ctypes Structure Sizes # Get common ctypes Structure Sizes
UAF_HDR_LEN: int = ctypes.sizeof(UafHeader) UAF_HDR_LEN: Final[int] = ctypes.sizeof(UafHeader)
UAF_MOD_LEN: int = ctypes.sizeof(UafModule) UAF_MOD_LEN: Final[int] = ctypes.sizeof(UafModule)
DIS_HDR_LEN: int = ctypes.sizeof(DisHeader) DIS_HDR_LEN: Final[int] = ctypes.sizeof(DisHeader)
DIS_MOD_LEN: int = ctypes.sizeof(DisModule) DIS_MOD_LEN: Final[int] = ctypes.sizeof(DisModule)
UII_HDR_LEN: int = ctypes.sizeof(UiiHeader) UII_HDR_LEN: Final[int] = ctypes.sizeof(UiiHeader)
# AMI UCP Tag Dictionary # AMI UCP Tag Dictionary
UAF_TAG_DICT: dict[str, list[str]] = { UAF_TAG_DICT: Final[dict[str, list[str]]] = {
'@3FI': ['HpBiosUpdate32.efi', 'HpBiosUpdate32.efi', ''], '@3FI': ['HpBiosUpdate32.efi', 'HpBiosUpdate32.efi', ''],
'@3S2': ['HpBiosUpdate32.s12', 'HpBiosUpdate32.s12', ''], '@3S2': ['HpBiosUpdate32.s12', 'HpBiosUpdate32.s12', ''],
'@3S4': ['HpBiosUpdate32.s14', 'HpBiosUpdate32.s14', ''], '@3S4': ['HpBiosUpdate32.s14', 'HpBiosUpdate32.s14', ''],
@ -259,7 +260,7 @@ class AmiUcpExtract(BIOSUtility):
return bool(self._get_ami_ucp(input_object=buffer)[0]) return bool(self._get_ami_ucp(input_object=buffer)[0])
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> None: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract AMI UCP structures """ """ Parse & Extract AMI UCP structures """
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
@ -301,6 +302,8 @@ class AmiUcpExtract(BIOSUtility):
nal_dict = self._uaf_extract(buffer=ucp_buffer, extract_path=extract_path, mod_info=mod_info, nal_dict = self._uaf_extract(buffer=ucp_buffer, extract_path=extract_path, mod_info=mod_info,
nal_dict=nal_dict, padding=padding + 8) nal_dict=nal_dict, padding=padding + 8)
return True
@staticmethod @staticmethod
def _chk16_validate(data: bytes | bytearray, tag: str, padding: int = 0) -> None: def _chk16_validate(data: bytes | bytearray, tag: str, padding: int = 0) -> None:
""" Validate UCP Module Checksum-16 """ """ Validate UCP Module Checksum-16 """
@ -482,7 +485,7 @@ class AmiUcpExtract(BIOSUtility):
# Decompressed @UAF|@HPU Module file path # Decompressed @UAF|@HPU Module file path
dec_fname: str = uaf_fname.replace('.temp', uaf_fext) dec_fname: str = uaf_fname.replace('.temp', uaf_fext)
if efi_decompress(in_path=uaf_fname, out_path=dec_fname, padding=padding + 4) == 0: if efi_decompress(in_path=uaf_fname, out_path=dec_fname, padding=padding + 4):
with open(file=dec_fname, mode='rb') as dec: with open(file=dec_fname, mode='rb') as dec:
uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data
@ -558,26 +561,30 @@ class AmiUcpExtract(BIOSUtility):
# Assign a file path & name to each Tag # Assign a file path & name to each Tag
nal_dict[info_tag] = (info_path, info_name) nal_dict[info_tag] = (info_path, info_name)
insyde_ifd_extract: InsydeIfdExtract = InsydeIfdExtract()
# Parse Insyde BIOS @UAF|@HPU Module (@INS) # Parse Insyde BIOS @UAF|@HPU Module (@INS)
if uaf_tag == '@INS' and InsydeIfdExtract().check_format(input_object=uaf_fname): if uaf_tag == '@INS' and insyde_ifd_extract.check_format(input_object=uaf_fname):
# Generate extraction directory # Generate extraction directory
ins_dir: str = os.path.join(extract_path, safe_name(in_name=f'{uaf_tag}_nested-IFD')) ins_dir: str = os.path.join(extract_path, safe_name(in_name=f'{uaf_tag}_nested-IFD'))
if InsydeIfdExtract().parse_format(input_object=uaf_fname, extract_path=extract_folder(ins_dir), if insyde_ifd_extract.parse_format(input_object=uaf_fname, extract_path=extract_folder(ins_dir),
padding=padding + 4) == 0: padding=padding + 4):
os.remove(path=uaf_fname) # Delete raw nested Insyde IFD image after successful extraction os.remove(path=uaf_fname) # Delete raw nested Insyde IFD image after successful extraction
ami_pfat_extract: AmiPfatExtract = AmiPfatExtract()
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
if AmiPfatExtract().check_format(input_object=uaf_data_raw): if ami_pfat_extract.check_format(input_object=uaf_data_raw):
pfat_dir: str = os.path.join(extract_path, safe_name(in_name=uaf_name)) pfat_dir: str = os.path.join(extract_path, safe_name(in_name=uaf_name))
AmiPfatExtract().parse_format(input_object=uaf_data_raw, extract_path=extract_folder(pfat_dir), ami_pfat_extract.parse_format(input_object=uaf_data_raw, extract_path=extract_folder(pfat_dir),
padding=padding + 4) padding=padding + 4)
os.remove(path=uaf_fname) # Delete raw PFAT BIOS image after successful extraction os.remove(path=uaf_fname) # Delete raw PFAT BIOS image after successful extraction
# Detect Intel Engine firmware image and show ME Analyzer advice # Detect Intel Engine firmware image and show ME Analyzer advice
if uaf_tag.startswith('@ME') and PAT_INTEL_ENG.search(string=uaf_data_raw): if uaf_tag.startswith('@ME') and PAT_INTEL_ENGINE.search(string=uaf_data_raw):
printer(message='Intel Management Engine (ME) Firmware:\n', padding=padding + 4) printer(message='Intel Management Engine (ME) Firmware:\n', padding=padding + 4)
printer(message='Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer', printer(message='Use "ME Analyzer" from https://github.com/platomav/MEAnalyzer',
padding=padding + 8, new_line=False) padding=padding + 8, new_line=False)

View file

@ -14,12 +14,13 @@ import struct
import subprocess import subprocess
import zlib import zlib
from collections import defaultdict
from re import Match from re import Match
from typing import Any, Final from typing import Any, Final
from biosutilities.common.externals import uefiextract_path, uefifind_path from biosutilities.common.externals import uefiextract_path, uefifind_path
from biosutilities.common.paths import delete_dirs, delete_file, path_suffixes, runtime_root from biosutilities.common.paths import delete_dirs, delete_file, path_suffixes, runtime_root
from biosutilities.common.patterns import PAT_APPLE_EFI from biosutilities.common.patterns import PAT_INTEL_IBIOSI, PAT_APPLE_ROM_VER
from biosutilities.common.structs import CHAR, ctypes_struct, UINT8 from biosutilities.common.structs import CHAR, ctypes_struct, UINT8
from biosutilities.common.system import printer from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility from biosutilities.common.templates import BIOSUtility
@ -58,8 +59,10 @@ class IntelBiosId(ctypes.LittleEndianStructure):
def _decode(field: bytes) -> str: def _decode(field: bytes) -> str:
return struct.pack('B' * len(field), *field).decode(encoding='utf-16', errors='ignore').strip('\x00 ') return struct.pack('B' * len(field), *field).decode(encoding='utf-16', errors='ignore').strip('\x00 ')
def get_bios_id(self) -> tuple: def get_bios_id(self) -> dict[str, str]:
""" Create Apple EFI BIOS ID """ """ Get Apple/Intel EFI BIOS ID """
intel_sig: str = self.Signature.decode(encoding='utf-8')
board_id: str = self._decode(field=self.BoardID) board_id: str = self._decode(field=self.BoardID)
board_ext: str = self._decode(field=self.BoardExt) board_ext: str = self._decode(field=self.BoardExt)
@ -72,26 +75,40 @@ class IntelBiosId(ctypes.LittleEndianStructure):
build_hour: str = self._decode(field=self.Hour) build_hour: str = self._decode(field=self.Hour)
build_minute: str = self._decode(field=self.Minute) build_minute: str = self._decode(field=self.Minute)
build_date: str = f'20{build_year}-{build_month}-{build_day}' efi_name_id: str = (f'{board_id}_{board_ext}_{version_major}_{build_type}{version_minor}'
build_time: str = f'{build_hour}-{build_minute}' f'_20{build_year}-{build_month}-{build_day}_{build_hour}-{build_minute}')
return board_id, board_ext, version_major, build_type, version_minor, build_date, build_time return {
'intel_sig': intel_sig,
'board_id': board_id,
'board_ext': board_ext,
'version_major': version_major,
'version_minor': version_minor,
'build_type': build_type,
'build_year': build_year,
'build_month': build_month,
'build_day': build_day,
'build_hour': build_hour,
'build_minute': build_minute,
'efi_name_id': efi_name_id
}
def struct_print(self, padding: int = 0) -> None: def struct_print(self, padding: int = 0) -> None:
""" Display structure information """ """ Display structure information """
board_id, board_ext, version_major, build_type, version_minor, build_date, build_time = self.get_bios_id() ibiosi: dict[str, str] = self.get_bios_id()
intel_id: str = self.Signature.decode(encoding='utf-8') ibiosi_date: str = f'20{ibiosi["build_year"]}-{ibiosi["build_month"]}-{ibiosi["build_day"]}'
ibiosi_time: str = f'{ibiosi["build_hour"]}:{ibiosi["build_minute"]}'
printer(message=['Intel Signature:', intel_id], padding=padding, new_line=False) printer(message=['Intel Signature:', ibiosi['intel_sig']], padding=padding, new_line=False)
printer(message=['Board Identity: ', board_id], padding=padding, new_line=False) printer(message=['Board Identity: ', ibiosi['board_id']], padding=padding, new_line=False)
printer(message=['Apple Identity: ', board_ext], padding=padding, new_line=False) printer(message=['Apple Identity: ', ibiosi['board_ext']], padding=padding, new_line=False)
printer(message=['Major Version: ', version_major], padding=padding, new_line=False) printer(message=['Major Version: ', ibiosi['version_major']], padding=padding, new_line=False)
printer(message=['Minor Version: ', version_minor], padding=padding, new_line=False) printer(message=['Minor Version: ', ibiosi['version_minor']], padding=padding, new_line=False)
printer(message=['Build Type: ', build_type], padding=padding, new_line=False) printer(message=['Build Type: ', ibiosi['build_type']], padding=padding, new_line=False)
printer(message=['Build Date: ', build_date], padding=padding, new_line=False) printer(message=['Build Date: ', ibiosi_date], padding=padding, new_line=False)
printer(message=['Build Time: ', build_time.replace('-', ':')], padding=padding, new_line=False) printer(message=['Build Time: ', ibiosi_time], padding=padding, new_line=False)
class AppleEfiIdentify(BIOSUtility): class AppleEfiIdentify(BIOSUtility):
@ -104,14 +121,16 @@ class AppleEfiIdentify(BIOSUtility):
def __init__(self, arguments: list[str] | None = None) -> None: def __init__(self, arguments: list[str] | None = None) -> None:
super().__init__(arguments=arguments) super().__init__(arguments=arguments)
self.efi_name_id: str = '' self.efi_file_name: str = ''
self.intel_bios_info: dict[str, str] = {}
self.apple_rom_version: defaultdict[str, set] = defaultdict(set)
def check_format(self, input_object: str | bytes | bytearray) -> bool: def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Apple EFI image """ """ Check if input is Apple EFI image """
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
if PAT_APPLE_EFI.search(string=input_buffer): if PAT_INTEL_IBIOSI.search(string=input_buffer):
return True return True
if isinstance(input_object, str) and os.path.isfile(path=input_object): if isinstance(input_object, str) and os.path.isfile(path=input_object):
@ -135,7 +154,7 @@ class AppleEfiIdentify(BIOSUtility):
if input_path != input_object: if input_path != input_object:
delete_file(in_path=input_path) delete_file(in_path=input_path)
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Identify (or Rename) Apple EFI image """ """ Parse & Identify (or Rename) Apple EFI image """
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
@ -148,7 +167,7 @@ class AppleEfiIdentify(BIOSUtility):
with open(file=input_path, mode='wb') as parse_out: with open(file=input_path, mode='wb') as parse_out:
parse_out.write(input_buffer) parse_out.write(input_buffer)
bios_id_match: Match[bytes] | None = PAT_APPLE_EFI.search(string=input_buffer) bios_id_match: Match[bytes] | None = PAT_INTEL_IBIOSI.search(string=input_buffer)
if bios_id_match: if bios_id_match:
bios_id_res: str = f'0x{bios_id_match.start():X}' bios_id_res: str = f'0x{bios_id_match.start():X}'
@ -171,7 +190,7 @@ class AppleEfiIdentify(BIOSUtility):
body_buffer: bytes = raw_body.read() body_buffer: bytes = raw_body.read()
# Detect decompressed $IBIOSI$ pattern # Detect decompressed $IBIOSI$ pattern
bios_id_match = PAT_APPLE_EFI.search(string=body_buffer) bios_id_match = PAT_INTEL_IBIOSI.search(string=body_buffer)
if not bios_id_match: if not bios_id_match:
raise RuntimeError('Failed to detect decompressed $IBIOSI$ pattern!') raise RuntimeError('Failed to detect decompressed $IBIOSI$ pattern!')
@ -183,25 +202,56 @@ class AppleEfiIdentify(BIOSUtility):
except Exception as error: # pylint: disable=broad-except except Exception as error: # pylint: disable=broad-except
printer(message=f'Error: Failed to parse compressed $IBIOSI$ pattern: {error}!', padding=padding) printer(message=f'Error: Failed to parse compressed $IBIOSI$ pattern: {error}!', padding=padding)
return 1 return False
printer(message=f'Detected $IBIOSI$ at {bios_id_res}\n', padding=padding) printer(message=f'Detected Intel BIOS Info at {bios_id_res}\n', padding=padding)
bios_id_hdr.struct_print(padding=padding + 4) bios_id_hdr.struct_print(padding=padding + 4)
input_suffix: str = path_suffixes(input_path)[-1] self.intel_bios_info = bios_id_hdr.get_bios_id()
input_adler32: int = zlib.adler32(input_buffer) self.efi_file_name = (f'{self.intel_bios_info["efi_name_id"]}_{zlib.adler32(input_buffer):08X}'
f'{path_suffixes(in_path=input_path)[-1]}')
fw_id, fw_ext, fw_major, fw_type, fw_minor, fw_date, fw_time = bios_id_hdr.get_bios_id() _ = self._apple_rom_version(input_buffer=input_buffer, padding=padding)
self.efi_name_id = (f'{fw_id}_{fw_ext}_{fw_major}_{fw_type}{fw_minor}_{fw_date}_{fw_time}_'
f'{input_adler32:08X}{input_suffix}')
if input_path != input_object: if input_path != input_object:
delete_file(in_path=input_path) delete_file(in_path=input_path)
return 0 return True
def _apple_rom_version(self, input_buffer: bytes | bytearray, padding: int = 0) -> bool:
rom_version_match: Match[bytes] | None = PAT_APPLE_ROM_VER.search(string=input_buffer)
if rom_version_match:
rom_version_match_off: int = rom_version_match.start()
rom_version_header_len: int = input_buffer[rom_version_match_off:].find(b'\n')
if rom_version_header_len != -1:
rom_version_data_bgn: int = rom_version_match_off + rom_version_header_len
rom_version_data_len: int = input_buffer[rom_version_data_bgn:].find(b'\x00')
if rom_version_data_len != -1:
rom_version_data_end: int = rom_version_data_bgn + rom_version_data_len
rom_version_data: bytes = input_buffer[rom_version_data_bgn:rom_version_data_end]
rom_version_text: str = rom_version_data.decode('utf-8').strip('\n')
for rom_version_line in [line.strip() for line in rom_version_text.split('\n')]:
rom_version_parts: list[str] = rom_version_line.split(sep=':', maxsplit=1)
self.apple_rom_version[rom_version_parts[0].strip()].add(rom_version_parts[1].strip())
printer(message=f'Detected Apple ROM Version at 0x{rom_version_match_off:X}', padding=padding)
printer(message=rom_version_text, strip=True, padding=padding + 4)
return True
return False
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -13,7 +13,7 @@ from re import Match
from typing import Final from typing import Final
from biosutilities.common.paths import make_dirs, path_stem from biosutilities.common.paths import make_dirs, path_stem
from biosutilities.common.patterns import PAT_APPLE_IM4P, PAT_INTEL_IFD from biosutilities.common.patterns import PAT_APPLE_IM4P, PAT_INTEL_FD
from biosutilities.common.system import printer from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes from biosutilities.common.texts import file_to_bytes
@ -32,15 +32,15 @@ class AppleEfiIm4pSplit(BIOSUtility):
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
if PAT_APPLE_IM4P.search(string=input_buffer) and PAT_INTEL_IFD.search(string=input_buffer): if PAT_APPLE_IM4P.search(string=input_buffer) and PAT_INTEL_FD.search(string=input_buffer):
return True return True
return False return False
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Split Apple EFI IM4P image """ """ Parse & Split Apple EFI IM4P image """
exit_codes: list[int] = [] parse_success: bool = True
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
@ -50,7 +50,7 @@ class AppleEfiIm4pSplit(BIOSUtility):
im4p_match: Match[bytes] | None = PAT_APPLE_IM4P.search(string=input_buffer) im4p_match: Match[bytes] | None = PAT_APPLE_IM4P.search(string=input_buffer)
if not im4p_match: if not im4p_match:
return 1 return False
# After IM4P mefi (0x15), multi EFI payloads have _MEFIBIN (0x100) but is difficult to RE w/o varying samples. # After IM4P mefi (0x15), multi EFI payloads have _MEFIBIN (0x100) but is difficult to RE w/o varying samples.
# However, _MEFIBIN is not required for splitting SPI images due to Intel Flash Descriptor Components Density. # However, _MEFIBIN is not required for splitting SPI images due to Intel Flash Descriptor Components Density.
@ -75,7 +75,7 @@ class AppleEfiIm4pSplit(BIOSUtility):
input_buffer = input_buffer[efi_data_bgn:efi_data_bgn + efi_data_len] input_buffer = input_buffer[efi_data_bgn:efi_data_bgn + efi_data_len]
# Parse Intel Flash Descriptor pattern matches # Parse Intel Flash Descriptor pattern matches
for ifd in PAT_INTEL_IFD.finditer(string=input_buffer): for ifd in PAT_INTEL_FD.finditer(string=input_buffer):
# Component Base Address from FD start (ICH8-ICH10 = 1, IBX = 2, CPT+ = 3) # Component Base Address from FD start (ICH8-ICH10 = 1, IBX = 2, CPT+ = 3)
ifd_flmap0_fcba: int = input_buffer[ifd.start() + 0x4] * 0x10 ifd_flmap0_fcba: int = input_buffer[ifd.start() + 0x4] * 0x10
@ -148,9 +148,9 @@ class AppleEfiIm4pSplit(BIOSUtility):
printer(message=f'Error: Bad image size 0x{output_size:07X}, expected 0x{ifd_comp_all_size:07X}!', printer(message=f'Error: Bad image size 0x{output_size:07X}, expected 0x{ifd_comp_all_size:07X}!',
padding=padding + 4) padding=padding + 4)
exit_codes.append(1) parse_success = False
return sum(exit_codes) return parse_success
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -56,9 +56,9 @@ class AppleEfiPbzxExtract(BIOSUtility):
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
return bool(PAT_APPLE_PBZX.search(string=input_buffer[:0x4])) return bool(PAT_APPLE_PBZX.search(string=input_buffer, endpos=4))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract Apple PBZX image """ """ Parse & Extract Apple PBZX image """
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
@ -107,7 +107,7 @@ class AppleEfiPbzxExtract(BIOSUtility):
if cpio_len != len(cpio_bin): if cpio_len != len(cpio_bin):
printer(message='Error: Unexpected CPIO archive size!', padding=padding) printer(message='Error: Unexpected CPIO archive size!', padding=padding)
return 1 return False
cpio_name: str = path_stem(in_path=input_object) if isinstance(input_object, str) else 'Payload' cpio_name: str = path_stem(in_path=input_object) if isinstance(input_object, str) else 'Payload'
@ -119,14 +119,14 @@ class AppleEfiPbzxExtract(BIOSUtility):
# Decompress PBZX > CPIO archive with 7-Zip # Decompress PBZX > CPIO archive with 7-Zip
if is_szip_supported(in_path=cpio_path, padding=padding, args=['-tCPIO'], silent=False): if is_szip_supported(in_path=cpio_path, padding=padding, args=['-tCPIO'], silent=False):
if szip_decompress(in_path=cpio_path, out_path=extract_path, in_name='CPIO', if szip_decompress(in_path=cpio_path, out_path=extract_path, in_name='CPIO',
padding=padding, args=['-tCPIO'], check=True) == 0: padding=padding, args=['-tCPIO']):
os.remove(path=cpio_path) # Successful extraction, delete PBZX > CPIO archive os.remove(path=cpio_path) # Successful extraction, delete PBZX > CPIO archive
else: else:
return 3 return False
else: else:
return 2 return False
return 0 return True
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -10,9 +10,9 @@ Copyright (C) 2019-2024 Plato Mavropoulos
import os import os
from biosutilities.common.compression import is_szip_supported, szip_decompress from biosutilities.common.compression import is_szip_supported, szip_decompress
from biosutilities.common.paths import (copy_file, delete_dirs, extract_folder, path_files, from biosutilities.common.paths import (copy_file, delete_dirs, extract_folder, make_dirs, path_files,
make_dirs, path_name, path_parent, runtime_root) path_name, path_parent, path_suffixes, runtime_root)
from biosutilities.common.patterns import PAT_APPLE_PKG_TAR, PAT_APPLE_PKG_XAR from biosutilities.common.patterns import PAT_APPLE_PKG_DMG, PAT_APPLE_PKG_TAR, PAT_APPLE_PKG_XAR
from biosutilities.common.system import printer from biosutilities.common.system import printer
from biosutilities.common.templates import BIOSUtility from biosutilities.common.templates import BIOSUtility
from biosutilities.common.texts import file_to_bytes from biosutilities.common.texts import file_to_bytes
@ -42,19 +42,22 @@ class AppleEfiPkgExtract(BIOSUtility):
with open(file=input_path, mode='wb') as input_path_object: with open(file=input_path, mode='wb') as input_path_object:
input_path_object.write(input_buffer) input_path_object.write(input_buffer)
if is_szip_supported(in_path=input_path, args=['-tXAR']): if is_szip_supported(in_path=input_path, args=['-tXAR:s0']):
if bool(PAT_APPLE_PKG_XAR.search(string=input_buffer, endpos=4)): if bool(PAT_APPLE_PKG_XAR.search(string=input_buffer, endpos=4)):
is_apple_efi_pkg = True is_apple_efi_pkg = True
elif is_szip_supported(in_path=input_path, args=['-tTAR']): elif is_szip_supported(in_path=input_path, args=['-tTAR:s0']):
if bool(PAT_APPLE_PKG_TAR.search(string=input_buffer)): if bool(PAT_APPLE_PKG_TAR.search(string=input_buffer)):
is_apple_efi_pkg = True is_apple_efi_pkg = True
elif is_szip_supported(in_path=input_path, args=['-tDMG:s0']):
if bool(PAT_APPLE_PKG_DMG.search(string=input_buffer, endpos=200)):
is_apple_efi_pkg = True
if input_path != input_object: if input_path != input_object:
os.remove(path=input_path) os.remove(path=input_path)
return is_apple_efi_pkg return is_apple_efi_pkg
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract Apple EFI PKG packages """ """ Parse & Extract Apple EFI PKG packages """
if isinstance(input_object, str) and os.path.isfile(path=input_object): if isinstance(input_object, str) and os.path.isfile(path=input_object):
@ -69,13 +72,15 @@ class AppleEfiPkgExtract(BIOSUtility):
working_dir: str = os.path.join(extract_path, 'temp') working_dir: str = os.path.join(extract_path, 'temp')
for pkg_type in ('XAR', 'TAR'): make_dirs(in_path=working_dir, delete=True)
for pkg_type in ('XAR', 'TAR', 'DMG'):
if is_szip_supported(in_path=input_path, padding=padding, args=[f'-t{pkg_type}']): if is_szip_supported(in_path=input_path, padding=padding, args=[f'-t{pkg_type}']):
if szip_decompress(in_path=input_path, out_path=working_dir, in_name=pkg_type, padding=padding, if szip_decompress(in_path=input_path, out_path=working_dir, in_name=pkg_type, padding=padding,
args=[f'-t{pkg_type}'], check=True) == 0: args=None if pkg_type == 'DMG' else [f'-t{pkg_type}']):
break break
else: else:
return 1 return False
if input_path != input_object: if input_path != input_object:
os.remove(path=input_path) os.remove(path=input_path)
@ -83,22 +88,42 @@ class AppleEfiPkgExtract(BIOSUtility):
for work_file in path_files(in_path=working_dir): for work_file in path_files(in_path=working_dir):
self._pbzx_zip(input_path=work_file, extract_path=extract_path, padding=padding + 4) self._pbzx_zip(input_path=work_file, extract_path=extract_path, padding=padding + 4)
self._gzip_cpio(input_path=work_file, extract_path=extract_path, padding=padding + 4) self._gzip_cpio(input_path=work_file, extract_path=extract_path, padding=padding + 4)
self._tar_gzip(input_path=work_file, extract_path=extract_path, padding=padding + 4) self._dmg_zip(input_path=work_file, extract_path=extract_path, padding=padding + 4)
self._xar_gzip(input_path=work_file, extract_path=extract_path, padding=padding + 4)
delete_dirs(in_path=working_dir) delete_dirs(in_path=working_dir)
return 0 return True
def _tar_gzip(self, input_path: str, extract_path: str, padding: int = 0) -> None: def _xar_gzip(self, input_path: str, extract_path: str, padding: int = 0) -> None:
""" TAR > GZIP """ """ XAR/TAR > GZIP """
if is_szip_supported(in_path=input_path, padding=padding, args=['-tTAR']): for pkg_type in ('XAR', 'TAR'):
tar_path: str = extract_folder(in_path=input_path) if is_szip_supported(in_path=input_path, padding=padding, args=[f'-t{pkg_type}']):
pkg_path: str = extract_folder(in_path=input_path, suffix=f'_{pkg_type.lower()}_gzip')
if szip_decompress(in_path=input_path, out_path=tar_path, in_name='TAR', padding=padding, if szip_decompress(in_path=input_path, out_path=pkg_path, in_name=pkg_type,
args=['-tTAR'], check=False) == 0: padding=padding, args=[f'-t{pkg_type}']):
for tar_file in path_files(in_path=tar_path): for pkg_file in path_files(in_path=pkg_path):
self._gzip_cpio(input_path=tar_file, extract_path=extract_path, padding=padding + 4) self._gzip_cpio(input_path=pkg_file, extract_path=extract_path, padding=padding + 4)
break
def _dmg_zip(self, input_path: str, extract_path: str, padding: int = 0) -> None:
""" DMG > ZIP """
if is_szip_supported(in_path=input_path, padding=padding, args=['-tDMG']):
dmg_path: str = extract_folder(in_path=input_path, suffix='_dmg_zip')
if szip_decompress(in_path=input_path, out_path=dmg_path, in_name='DMG', padding=padding, args=None):
for dmg_file in path_files(in_path=dmg_path):
if is_szip_supported(in_path=dmg_file, padding=padding + 4, args=['-tZIP']):
zip_path: str = extract_folder(in_path=dmg_file)
if szip_decompress(in_path=dmg_file, out_path=zip_path, in_name='ZIP',
padding=padding + 4, args=['-tZIP']):
for zip_file in path_files(in_path=zip_path):
self._im4p_id(input_path=zip_file, output_path=extract_path, padding=padding + 8)
def _pbzx_zip(self, input_path: str, extract_path: str, padding: int = 0) -> None: def _pbzx_zip(self, input_path: str, extract_path: str, padding: int = 0) -> None:
""" PBZX > ZIP """ """ PBZX > ZIP """
@ -108,9 +133,9 @@ class AppleEfiPkgExtract(BIOSUtility):
if pbzx_module.check_format(input_object=input_path): if pbzx_module.check_format(input_object=input_path):
printer(message=f'Extracting PBZX via {pbzx_module.title}', padding=padding) printer(message=f'Extracting PBZX via {pbzx_module.title}', padding=padding)
pbzx_path: str = extract_folder(in_path=input_path) pbzx_path: str = extract_folder(in_path=input_path, suffix='_pbzx_zip')
if pbzx_module.parse_format(input_object=input_path, extract_path=pbzx_path, padding=padding + 4) == 0: if pbzx_module.parse_format(input_object=input_path, extract_path=pbzx_path, padding=padding + 4):
printer(message=f'Successful PBZX extraction via {pbzx_module.title}!', padding=padding) printer(message=f'Successful PBZX extraction via {pbzx_module.title}!', padding=padding)
for pbzx_file in path_files(in_path=pbzx_path): for pbzx_file in path_files(in_path=pbzx_path):
@ -118,7 +143,7 @@ class AppleEfiPkgExtract(BIOSUtility):
zip_path: str = extract_folder(in_path=pbzx_file) zip_path: str = extract_folder(in_path=pbzx_file)
if szip_decompress(in_path=pbzx_file, out_path=zip_path, in_name='ZIP', if szip_decompress(in_path=pbzx_file, out_path=zip_path, in_name='ZIP',
padding=padding + 4, args=['-tZIP'], check=False) == 0: padding=padding + 4, args=['-tZIP']):
for zip_file in path_files(in_path=zip_path): for zip_file in path_files(in_path=zip_path):
self._im4p_id(input_path=zip_file, output_path=extract_path, padding=padding + 8) self._im4p_id(input_path=zip_file, output_path=extract_path, padding=padding + 8)
@ -126,16 +151,16 @@ class AppleEfiPkgExtract(BIOSUtility):
""" GZIP > CPIO """ """ GZIP > CPIO """
if is_szip_supported(in_path=input_path, padding=padding, args=['-tGZIP']): if is_szip_supported(in_path=input_path, padding=padding, args=['-tGZIP']):
gzip_path: str = extract_folder(in_path=input_path) gzip_path: str = extract_folder(in_path=input_path, suffix='_gzip_cpio')
if szip_decompress(in_path=input_path, out_path=gzip_path, in_name='GZIP', padding=padding, if szip_decompress(in_path=input_path, out_path=gzip_path, in_name='GZIP',
args=['-tGZIP'], check=True) == 0: padding=padding, args=['-tGZIP']):
for gzip_file in path_files(in_path=gzip_path): for gzip_file in path_files(in_path=gzip_path):
if is_szip_supported(in_path=gzip_file, padding=padding + 4, args=['-tCPIO']): if is_szip_supported(in_path=gzip_file, padding=padding + 4, args=['-tCPIO']):
cpio_path: str = extract_folder(in_path=gzip_file) cpio_path: str = extract_folder(in_path=gzip_file)
if szip_decompress(in_path=gzip_file, out_path=cpio_path, in_name='CPIO', if szip_decompress(in_path=gzip_file, out_path=cpio_path, in_name='CPIO',
padding=padding + 4, args=['-tCPIO'], check=False) == 0: padding=padding + 4, args=['-tCPIO']):
for cpio_file in path_files(in_path=cpio_path): for cpio_file in path_files(in_path=cpio_path):
self._im4p_id(input_path=cpio_file, output_path=extract_path, padding=padding + 8) self._im4p_id(input_path=cpio_file, output_path=extract_path, padding=padding + 8)
@ -143,6 +168,9 @@ class AppleEfiPkgExtract(BIOSUtility):
def _im4p_id(input_path: str, output_path: str, padding: int = 0) -> None: def _im4p_id(input_path: str, output_path: str, padding: int = 0) -> None:
""" Split IM4P (if applicable), identify and rename EFI """ """ Split IM4P (if applicable), identify and rename EFI """
if path_suffixes(in_path=input_path)[-1].lower() not in ('.fd', '.scap', '.im4p'):
return None
if not AppleEfiIdentify().check_format(input_object=input_path): if not AppleEfiIdentify().check_format(input_object=input_path):
return None return None
@ -169,11 +197,9 @@ class AppleEfiPkgExtract(BIOSUtility):
if efi_id_module.check_format(input_object=efi_source): if efi_id_module.check_format(input_object=efi_source):
printer(message=f'Identifying EFI via {efi_id_module.title}', padding=padding + 4) printer(message=f'Identifying EFI via {efi_id_module.title}', padding=padding + 4)
efi_id_exit: int = efi_id_module.parse_format( if efi_id_module.parse_format(input_object=efi_source, extract_path=extract_folder(in_path=efi_source),
input_object=efi_source, extract_path=extract_folder(in_path=efi_source), padding=padding + 8) padding=padding + 8):
efi_dest: str = os.path.join(path_parent(in_path=efi_source), efi_id_module.efi_file_name)
if efi_id_exit == 0:
efi_dest: str = os.path.join(path_parent(in_path=efi_source), efi_id_module.efi_name_id)
os.rename(src=efi_source, dst=efi_dest) os.rename(src=efi_source, dst=efi_dest)

View file

@ -30,7 +30,7 @@ class AwardBiosExtract(BIOSUtility):
return bool(PAT_AWARD_LZH.search(string=in_buffer)) return bool(PAT_AWARD_LZH.search(string=in_buffer))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> None: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract Award BIOS image """ """ Parse & Extract Award BIOS image """
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
@ -74,7 +74,7 @@ class AwardBiosExtract(BIOSUtility):
# 7-Zip returns critical exit code (i.e. 2) if LZH CRC is wrong, do not check result # 7-Zip returns critical exit code (i.e. 2) if LZH CRC is wrong, do not check result
szip_decompress(in_path=lzh_path, out_path=extract_path, in_name=lzh_text, szip_decompress(in_path=lzh_path, out_path=extract_path, in_name=lzh_text,
padding=padding + 4, check=False) padding=padding + 4)
# Manually check if 7-Zip extracted LZH due to its CRC check issue # Manually check if 7-Zip extracted LZH due to its CRC check issue
if os.path.isfile(path=mod_path): if os.path.isfile(path=mod_path):
@ -88,6 +88,8 @@ class AwardBiosExtract(BIOSUtility):
self.parse_format(input_object=mod_path, extract_path=extract_folder(mod_path), self.parse_format(input_object=mod_path, extract_path=extract_folder(mod_path),
padding=padding + 8) padding=padding + 8)
return True
if __name__ == '__main__': if __name__ == '__main__':
AwardBiosExtract().run_utility() AwardBiosExtract().run_utility()

View file

@ -41,7 +41,7 @@ def is_szip_supported(in_path: str, padding: int = 0, args: list | None = None,
def szip_decompress(in_path: str, out_path: str, in_name: str | None, padding: int = 0, args: list | None = None, def szip_decompress(in_path: str, out_path: str, in_name: str | None, padding: int = 0, args: list | None = None,
check: bool = False, silent: bool = False) -> int: check: bool = False, silent: bool = False) -> bool:
""" Archive decompression via 7-Zip """ """ Archive decompression via 7-Zip """
if not in_name: if not in_name:
@ -64,12 +64,12 @@ def szip_decompress(in_path: str, out_path: str, in_name: str | None, padding: i
if not silent: if not silent:
printer(message=f'Error: 7-Zip could not extract {in_name} file {in_path}: {error}!', padding=padding) printer(message=f'Error: 7-Zip could not extract {in_name} file {in_path}: {error}!', padding=padding)
return 1 return False
if not silent: if not silent:
printer(message=f'Successful {in_name} decompression via 7-Zip!', padding=padding) printer(message=f'Successful {in_name} decompression via 7-Zip!', padding=padding)
return 0 return True
def efi_compress_sizes(data: bytes | bytearray) -> tuple[int, int]: def efi_compress_sizes(data: bytes | bytearray) -> tuple[int, int]:
@ -98,7 +98,7 @@ def is_efi_compressed(data: bytes | bytearray, strict: bool = True) -> bool:
def efi_decompress(in_path: str, out_path: str, padding: int = 0, silent: bool = False, def efi_decompress(in_path: str, out_path: str, padding: int = 0, silent: bool = False,
comp_type: str = '--uefi') -> int: comp_type: str = '--uefi') -> bool:
""" EFI/Tiano Decompression via TianoCompress """ """ EFI/Tiano Decompression via TianoCompress """
try: try:
@ -114,9 +114,9 @@ def efi_decompress(in_path: str, out_path: str, padding: int = 0, silent: bool =
if not silent: if not silent:
printer(message=f'Error: TianoCompress could not extract file {in_path}: {error}!', padding=padding) printer(message=f'Error: TianoCompress could not extract file {in_path}: {error}!', padding=padding)
return 1 return False
if not silent: if not silent:
printer(message='Successful EFI decompression via TianoCompress!', padding=padding) printer(message='Successful EFI decompression via TianoCompress!', padding=padding)
return 0 return True

View file

@ -20,7 +20,7 @@ from biosutilities.common.paths import project_root
from biosutilities.common.texts import to_string from biosutilities.common.texts import to_string
def get_external_path(cmd: str | list | tuple, raise_on_error: bool = True) -> str | None: def get_external_path(cmd: str | list | tuple) -> str:
""" Get external dependency path (PATH environment variable or "external" directory) """ """ Get external dependency path (PATH environment variable or "external" directory) """
external_root: str = os.path.join(project_root(), 'external') external_root: str = os.path.join(project_root(), 'external')
@ -33,18 +33,15 @@ def get_external_path(cmd: str | list | tuple, raise_on_error: bool = True) -> s
if command_path and os.path.isfile(path=command_path): if command_path and os.path.isfile(path=command_path):
return command_path return command_path
if raise_on_error:
raise OSError(f'{to_string(in_object=cmd, sep_char=", ")} could not be found!') raise OSError(f'{to_string(in_object=cmd, sep_char=", ")} could not be found!')
return None
def big_script_tool() -> Type | None: def big_script_tool() -> Type | None:
""" Get Intel BIOS Guard Script Tool class """ """ Get Intel BIOS Guard Script Tool class """
bgst: str | None = get_external_path(cmd='big_script_tool', raise_on_error=False) try:
bgst: str = get_external_path(cmd='big_script_tool')
if bgst is not None:
bgst_spec: ModuleSpec | None = spec_from_file_location( bgst_spec: ModuleSpec | None = spec_from_file_location(
name='big_script_tool', location=re.sub(r'\.PY$', '.py', bgst)) name='big_script_tool', location=re.sub(r'\.PY$', '.py', bgst))
@ -57,35 +54,37 @@ def big_script_tool() -> Type | None:
bgst_spec.loader.exec_module(module=bgst_module) bgst_spec.loader.exec_module(module=bgst_module)
return getattr(bgst_module, 'BigScript') return getattr(bgst_module, 'BigScript')
except OSError:
pass
return None return None
def comextract_path() -> str | None: def comextract_path() -> str:
""" Get ToshibaComExtractor path """ """ Get ToshibaComExtractor path """
return get_external_path(cmd='comextract') return get_external_path(cmd='comextract')
def szip_path() -> str | None: def szip_path() -> str:
""" Get 7-Zip path """ """ Get 7-Zip path """
return get_external_path(cmd=['7zzs', '7zz', '7z']) return get_external_path(cmd=['7zzs', '7zz', '7z'])
def tiano_path() -> str | None: def tiano_path() -> str:
""" Get TianoCompress path """ """ Get TianoCompress path """
return get_external_path(cmd='TianoCompress') return get_external_path(cmd='TianoCompress')
def uefifind_path() -> str | None: def uefifind_path() -> str:
""" Get UEFIFind path """ """ Get UEFIFind path """
return get_external_path(cmd='UEFIFind') return get_external_path(cmd='UEFIFind')
def uefiextract_path() -> str | None: def uefiextract_path() -> str:
""" Get UEFIExtract path """ """ Get UEFIExtract path """
return get_external_path(cmd='UEFIExtract') return get_external_path(cmd='UEFIExtract')

View file

@ -132,7 +132,7 @@ def delete_dirs(in_path: str) -> None:
""" Delete folder(s), if present """ """ Delete folder(s), if present """
if Path(in_path).is_dir(): if Path(in_path).is_dir():
shutil.rmtree(path=in_path, onexc=clear_readonly_callback) shutil.rmtree(path=in_path, onerror=clear_readonly_callback) # pylint: disable=deprecated-argument
def delete_file(in_path: str) -> None: def delete_file(in_path: str) -> None:
@ -164,7 +164,7 @@ def clear_readonly_callback(in_func: Callable, in_path: str, _) -> None:
clear_readonly(in_path=in_path) clear_readonly(in_path=in_path)
in_func(in_path=in_path) in_func(path=in_path)
def path_files(in_path: str, follow_links: bool = False) -> list[str]: def path_files(in_path: str, follow_links: bool = False) -> list[str]:

View file

@ -19,9 +19,8 @@ PAT_AMI_UCP: Final[re.Pattern[bytes]] = re.compile(
flags=re.DOTALL flags=re.DOTALL
) )
PAT_APPLE_EFI: Final[re.Pattern[bytes]] = re.compile( PAT_APPLE_ROM_VER: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\$IBIOSI\$.{16}\x2E\x00.{6}\x2E\x00.{8}\x2E\x00.{6}\x2E\x00.{20}\x00{2}', pattern=br'Apple ROM Version\x0A\x20{2}'
flags=re.DOTALL
) )
PAT_APPLE_IM4P: Final[re.Pattern[bytes]] = re.compile( PAT_APPLE_IM4P: Final[re.Pattern[bytes]] = re.compile(
@ -32,14 +31,18 @@ PAT_APPLE_PBZX: Final[re.Pattern[bytes]] = re.compile(
pattern=br'pbzx' pattern=br'pbzx'
) )
PAT_APPLE_PKG_XAR: Final[re.Pattern[bytes]] = re.compile( PAT_APPLE_PKG_DMG: Final[re.Pattern[bytes]] = re.compile(
pattern=br'xar!' pattern=br'EFI PART'
) )
PAT_APPLE_PKG_TAR: Final[re.Pattern[bytes]] = re.compile( PAT_APPLE_PKG_TAR: Final[re.Pattern[bytes]] = re.compile(
pattern=br'<key>IFPkgDescriptionDescription</key>' pattern=br'<key>IFPkgDescriptionDescription</key>'
) )
PAT_APPLE_PKG_XAR: Final[re.Pattern[bytes]] = re.compile(
pattern=br'xar!'
)
PAT_AWARD_LZH: Final[re.Pattern[bytes]] = re.compile( PAT_AWARD_LZH: Final[re.Pattern[bytes]] = re.compile(
pattern=br'-lh[04567]-' pattern=br'-lh[04567]-'
) )
@ -71,16 +74,21 @@ PAT_INSYDE_SFX: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\x0D\x0A;!@InstallEnd@!\x0D\x0A(7z\xBC\xAF\x27|\x6E\xF4\x79\x5F\x4E)' pattern=br'\x0D\x0A;!@InstallEnd@!\x0D\x0A(7z\xBC\xAF\x27|\x6E\xF4\x79\x5F\x4E)'
) )
PAT_INTEL_ENG: Final[re.Pattern[bytes]] = re.compile( PAT_INTEL_ENGINE: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))', pattern=br'\x04\x00{3}[\xA1\xE1]\x00{3}.{8}\x86\x80.{9}\x00\$((MN2)|(MAN))',
flags=re.DOTALL flags=re.DOTALL
) )
PAT_INTEL_IFD: Final[re.Pattern[bytes]] = re.compile( PAT_INTEL_FD: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\x5A\xA5\xF0\x0F.{172}\xFF{16}', pattern=br'\x5A\xA5\xF0\x0F.{172}\xFF{16}',
flags=re.DOTALL flags=re.DOTALL
) )
PAT_INTEL_IBIOSI: Final[re.Pattern[bytes]] = re.compile(
pattern=br'\$IBIOSI\$.{16}\x2E\x00.{6}\x2E\x00.{8}\x2E\x00.{6}\x2E\x00.{20}\x00{2}',
flags=re.DOTALL
)
PAT_MICROSOFT_CAB: Final[re.Pattern[bytes]] = re.compile( PAT_MICROSOFT_CAB: Final[re.Pattern[bytes]] = re.compile(
pattern=br'MSCF\x00{4}' pattern=br'MSCF\x00{4}'
) )

View file

@ -30,17 +30,19 @@ def python_version() -> tuple:
def printer(message: str | list | tuple | None = None, padding: int = 0, new_line: bool = True, def printer(message: str | list | tuple | None = None, padding: int = 0, new_line: bool = True,
pause: bool = False, sep_char: str = ' ') -> None: pause: bool = False, sep_char: str = ' ', strip: bool = False) -> None:
""" Show message(s), controlling padding, newline, pausing & separator """ """ Show message(s), controlling padding, newline, stripping, pausing & separating """
message_string: str = to_string(in_object='' if message is None else message, sep_char=sep_char) message_string: str = to_string(in_object='' if message is None else message, sep_char=sep_char)
message_output: str = '\n' if new_line else '' message_output: str = '\n' if new_line else ''
for line_index, line_text in enumerate(iterable=message_string.split('\n')): for message_line_index, message_line_text in enumerate(iterable=message_string.split('\n')):
line_newline: str = '' if line_index == 0 else '\n' line_new: str = '' if message_line_index == 0 else '\n'
message_output += f'{line_newline}{" " * padding}{line_text}' line_text: str = message_line_text.strip() if strip else message_line_text
message_output += f'{line_new}{" " * padding}{line_text}'
if pause: if pause:
input(message_output) input(message_output)

View file

@ -30,10 +30,6 @@ class BIOSUtility:
MIN_PYTHON_VER: Final[tuple[int, int]] = (3, 10) MIN_PYTHON_VER: Final[tuple[int, int]] = (3, 10)
def __init__(self, arguments: list[str] | None = None) -> None: def __init__(self, arguments: list[str] | None = None) -> None:
self._check_sys_py()
self._check_sys_os()
self.title: str = f'{self.TITLE.strip()} v{__version__}' self.title: str = f'{self.TITLE.strip()} v{__version__}'
argparser: ArgumentParser = ArgumentParser(allow_abbrev=False) argparser: ArgumentParser = ArgumentParser(allow_abbrev=False)
@ -53,9 +49,13 @@ class BIOSUtility:
self._output_path: str = '' self._output_path: str = ''
def run_utility(self, padding: int = 0) -> int: def run_utility(self, padding: int = 0) -> bool:
""" Run utility after checking for supported format """ """ Run utility after checking for supported format """
self._check_sys_py()
self._check_sys_os()
self.show_version(padding=padding) self.show_version(padding=padding)
self._setup_input_files(padding=padding) self._setup_input_files(padding=padding)
@ -85,8 +85,7 @@ class BIOSUtility:
break break
if self.parse_format(input_object=input_file, extract_path=extract_path, if self.parse_format(input_object=input_file, extract_path=extract_path, padding=padding + 8):
padding=padding + 8) in [0, None]:
exit_code -= 1 exit_code -= 1
if is_empty_dir(in_path=extract_path): if is_empty_dir(in_path=extract_path):
@ -94,14 +93,14 @@ class BIOSUtility:
printer(message='Done!\n' if not self.arguments.auto_exit else None, pause=not self.arguments.auto_exit) printer(message='Done!\n' if not self.arguments.auto_exit else None, pause=not self.arguments.auto_exit)
return exit_code return exit_code == 0
def show_version(self, is_boxed: bool = True, padding: int = 0) -> None: def show_version(self, is_boxed: bool = True, padding: int = 0) -> None:
""" Show title and version of utility """ """ Show title and version of utility """
printer(message=to_boxed(in_text=self.title) if is_boxed else self.title, new_line=False, padding=padding) printer(message=to_boxed(in_text=self.title) if is_boxed else self.title, new_line=False, padding=padding)
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int | None: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Process input object as a specific supported format """ """ Process input object as a specific supported format """
raise NotImplementedError(f'Method "parse_format" not implemented at {__name__}') raise NotImplementedError(f'Method "parse_format" not implemented at {__name__}')
@ -112,6 +111,8 @@ class BIOSUtility:
raise NotImplementedError(f'Method "check_format" not implemented at {__name__}') raise NotImplementedError(f'Method "check_format" not implemented at {__name__}')
def _setup_input_files(self, padding: int = 0) -> None: def _setup_input_files(self, padding: int = 0) -> None:
self._input_files = []
input_paths: list[str] = self.arguments.paths input_paths: list[str] = self.arguments.paths
if not input_paths: if not input_paths:
@ -126,6 +127,8 @@ class BIOSUtility:
self._input_files.append(input_path_real) self._input_files.append(input_path_real)
def _setup_output_dir(self, padding: int = 0) -> None: def _setup_output_dir(self, padding: int = 0) -> None:
self._output_path = ''
output_path: str = self.arguments.output_dir output_path: str = self.arguments.output_dir
if not output_path: if not output_path:

View file

@ -15,7 +15,7 @@ import os
import zlib import zlib
from re import Match from re import Match
from typing import Any from typing import Any, Final
from biosutilities.common.checksums import checksum_8_xor from biosutilities.common.checksums import checksum_8_xor
from biosutilities.common.compression import is_szip_supported, szip_decompress from biosutilities.common.compression import is_szip_supported, szip_decompress
@ -231,13 +231,13 @@ class DellPfsExtract(BIOSUtility):
(['-s', '--structure'], {'help': 'show PFS structure information', 'action': 'store_true'}) (['-s', '--structure'], {'help': 'show PFS structure information', 'action': 'store_true'})
] ]
PFS_HEAD_LEN: int = ctypes.sizeof(DellPfsHeader) PFS_HEAD_LEN: Final[int] = ctypes.sizeof(DellPfsHeader)
PFS_FOOT_LEN: int = ctypes.sizeof(DellPfsFooter) PFS_FOOT_LEN: Final[int] = ctypes.sizeof(DellPfsFooter)
PFS_INFO_LEN: int = ctypes.sizeof(DellPfsInfo) PFS_INFO_LEN: Final[int] = ctypes.sizeof(DellPfsInfo)
PFS_NAME_LEN: int = ctypes.sizeof(DellPfsName) PFS_NAME_LEN: Final[int] = ctypes.sizeof(DellPfsName)
PFS_META_LEN: int = ctypes.sizeof(DellPfsMetadata) PFS_META_LEN: Final[int] = ctypes.sizeof(DellPfsMetadata)
PFS_PFAT_LEN: int = ctypes.sizeof(DellPfsPfatMetadata) PFS_PFAT_LEN: Final[int] = ctypes.sizeof(DellPfsPfatMetadata)
PFAT_HDR_LEN: int = ctypes.sizeof(IntelBiosGuardHeader) PFAT_HDR_LEN: Final[int] = ctypes.sizeof(IntelBiosGuardHeader)
def check_format(self, input_object: str | bytes | bytearray) -> bool: def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Dell PFS/PKG image """ """ Check if input is Dell PFS/PKG image """
@ -252,7 +252,7 @@ class DellPfsExtract(BIOSUtility):
return False return False
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> None: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract Dell PFS Update image """ """ Parse & Extract Dell PFS Update image """
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
@ -280,6 +280,8 @@ class DellPfsExtract(BIOSUtility):
pfs_name=pfs_name, pfs_index=pfs_index, pfs_count=1, is_rec=False, pfs_name=pfs_name, pfs_index=pfs_index, pfs_count=1, is_rec=False,
padding=padding) padding=padding)
return True
@staticmethod @staticmethod
def _is_pfs_pkg(input_object: str | bytes | bytearray) -> bool: def _is_pfs_pkg(input_object: str | bytes | bytearray) -> bool:
""" """
@ -353,7 +355,7 @@ class DellPfsExtract(BIOSUtility):
if is_szip_supported(in_path=pkg_tar_path, padding=0, args=['-tTAR']): if is_szip_supported(in_path=pkg_tar_path, padding=0, args=['-tTAR']):
if szip_decompress(in_path=pkg_tar_path, out_path=working_path, in_name='TAR', padding=0, if szip_decompress(in_path=pkg_tar_path, out_path=working_path, in_name='TAR', padding=0,
args=['-tTAR'], check=True, silent=True) == 0: args=['-tTAR'], check=True, silent=True):
os.remove(path=pkg_tar_path) os.remove(path=pkg_tar_path)
else: else:
return pfs_results return pfs_results
@ -1005,6 +1007,8 @@ class DellPfsExtract(BIOSUtility):
# Get PFAT Header Flags (SFAM, ProtectEC, GFXMitDis, FTU, Reserved) # Get PFAT Header Flags (SFAM, ProtectEC, GFXMitDis, FTU, Reserved)
is_sfam, _, _, _, _ = pfat_hdr.get_flags() is_sfam, _, _, _, _ = pfat_hdr.get_flags()
ami_pfat_extract: AmiPfatExtract = AmiPfatExtract()
# Parse sub-PFS PFAT Signature, if applicable (only when PFAT Header > SFAM flag is set) # Parse sub-PFS PFAT Signature, if applicable (only when PFAT Header > SFAM flag is set)
if is_sfam: if is_sfam:
if self.arguments.structure: if self.arguments.structure:
@ -1014,13 +1018,13 @@ class DellPfsExtract(BIOSUtility):
if _pfat_sign_len == 0: if _pfat_sign_len == 0:
_pfat_sign_sig: bytes = pfat_hdr.get_hdr_marker() _pfat_sign_sig: bytes = pfat_hdr.get_hdr_marker()
_pfat_sign_pfs: int = pfs_entry_size + self.PFS_PFAT_LEN _pfat_sign_pfs: int = pfs_entry_size + self.PFS_PFAT_LEN
_pfat_sign_max: int = _pfat_sign_pfs + AmiPfatExtract.PFAT_INT_SIG_MAX_LEN + len(_pfat_sign_sig) _pfat_sign_max: int = _pfat_sign_pfs + ami_pfat_extract.PFAT_INT_SIG_MAX_LEN + len(_pfat_sign_sig)
_pfat_sign_lim: int = pfat_payload_end + _pfat_sign_max _pfat_sign_lim: int = pfat_payload_end + _pfat_sign_max
_pfat_sign_off: int = pfat_payload.find(_pfat_sign_sig, pfat_payload_end, _pfat_sign_lim) _pfat_sign_off: int = pfat_payload.find(_pfat_sign_sig, pfat_payload_end, _pfat_sign_lim)
_pfat_sign_len = _pfat_sign_off - pfat_payload_end - _pfat_sign_pfs _pfat_sign_len = _pfat_sign_off - pfat_payload_end - _pfat_sign_pfs
# Get sub-PFS PFAT Signature Structure values # Get sub-PFS PFAT Signature Structure values
pfat_sign_len: int = AmiPfatExtract().parse_bg_sign( pfat_sign_len: int = ami_pfat_extract.parse_bg_sign(
input_data=pfat_payload, sign_offset=pfat_payload_end, sign_length=_pfat_sign_len, input_data=pfat_payload, sign_offset=pfat_payload_end, sign_length=_pfat_sign_len,
print_info=self.arguments.structure, padding=padding + 16) print_info=self.arguments.structure, padding=padding + 16)
@ -1033,7 +1037,7 @@ class DellPfsExtract(BIOSUtility):
if self.arguments.structure: if self.arguments.structure:
printer(message=f'PFAT Block {pfat_entry_idx_ord} - Script:\n', padding=padding + 12) printer(message=f'PFAT Block {pfat_entry_idx_ord} - Script:\n', padding=padding + 12)
_ = AmiPfatExtract().parse_bg_script(script_data=pfat_script_data, padding=padding + 16) _ = ami_pfat_extract.parse_bg_script(script_data=pfat_script_data, padding=padding + 16)
# The payload of sub-PFS PFAT Entries is not in proper order by default # The payload of sub-PFS PFAT Entries is not in proper order by default
# We can get each payload's order from PFAT Script > OpCode #2 (set I0 imm) # We can get each payload's order from PFAT Script > OpCode #2 (set I0 imm)

View file

@ -30,7 +30,7 @@ class FujitsuSfxExtract(BIOSUtility):
return bool(PAT_FUJITSU_SFX.search(string=input_buffer)) return bool(PAT_FUJITSU_SFX.search(string=input_buffer))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract Fujitsu SFX image """ """ Parse & Extract Fujitsu SFX image """
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
@ -39,7 +39,7 @@ class FujitsuSfxExtract(BIOSUtility):
match_cab: re.Match[bytes] | None = PAT_FUJITSU_SFX.search(string=input_buffer) match_cab: re.Match[bytes] | None = PAT_FUJITSU_SFX.search(string=input_buffer)
if not match_cab: if not match_cab:
return 1 return False
printer(message='Detected obfuscated CAB archive!', padding=padding) printer(message='Detected obfuscated CAB archive!', padding=padding)
@ -78,14 +78,14 @@ class FujitsuSfxExtract(BIOSUtility):
if is_szip_supported(in_path=cab_path, padding=padding + 8, silent=False): if is_szip_supported(in_path=cab_path, padding=padding + 8, silent=False):
if szip_decompress(in_path=cab_path, out_path=extract_path, in_name='FjSfxBinay CAB', if szip_decompress(in_path=cab_path, out_path=extract_path, in_name='FjSfxBinay CAB',
padding=padding + 8, check=True) == 0: padding=padding + 8, check=True):
os.remove(path=cab_path) os.remove(path=cab_path)
else: else:
return 3 return False
else: else:
return 2 return False
return 0 return True
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -34,7 +34,7 @@ class FujitsuUpcExtract(BIOSUtility):
return is_upc return is_upc
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract Fujitsu UPC image """ """ Parse & Extract Fujitsu UPC image """
make_dirs(in_path=extract_path, delete=True) make_dirs(in_path=extract_path, delete=True)
@ -56,12 +56,12 @@ class FujitsuUpcExtract(BIOSUtility):
output_path: str = os.path.join(extract_path, f'{input_name}.bin') output_path: str = os.path.join(extract_path, f'{input_name}.bin')
efi_code: int = efi_decompress(in_path=input_path, out_path=output_path, padding=padding) efi_status: bool = efi_decompress(in_path=input_path, out_path=output_path, padding=padding)
if input_path != input_object: if input_path != input_object:
os.remove(path=input_path) os.remove(path=input_path)
return efi_code return efi_status
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -11,7 +11,7 @@ import ctypes
import os import os
import re import re
from typing import Any from typing import Any, Final
from biosutilities.common.compression import is_szip_supported, szip_decompress from biosutilities.common.compression import is_szip_supported, szip_decompress
from biosutilities.common.paths import extract_folder, path_files, make_dirs, path_name, safe_name from biosutilities.common.paths import extract_folder, path_files, make_dirs, path_name, safe_name
@ -58,10 +58,10 @@ class InsydeIfdExtract(BIOSUtility):
TITLE: str = 'Insyde iFlash/iFdPacker Extractor' TITLE: str = 'Insyde iFlash/iFdPacker Extractor'
# Insyde iFdPacker known 7-Zip SFX Password # Insyde iFdPacker known 7-Zip SFX Password
INS_SFX_PWD: str = 'Y`t~i!L@i#t$U%h^s7A*l(f)E-d=y+S_n?i' INS_SFX_PWD: Final[str] = 'Y`t~i!L@i#t$U%h^s7A*l(f)E-d=y+S_n?i'
# Insyde iFlash known Image Names # Insyde iFlash known Image Names
INS_IFL_IMG: dict = { INS_IFL_IMG: Final[dict[str, list[str]]] = {
'BIOSCER': ['Certificate', 'bin'], 'BIOSCER': ['Certificate', 'bin'],
'BIOSCR2': ['Certificate 2nd', 'bin'], 'BIOSCR2': ['Certificate 2nd', 'bin'],
'BIOSIMG': ['BIOS-UEFI', 'bin'], 'BIOSIMG': ['BIOS-UEFI', 'bin'],
@ -77,7 +77,7 @@ class InsydeIfdExtract(BIOSUtility):
} }
# Get common ctypes Structure Sizes # Get common ctypes Structure Sizes
INS_IFL_LEN: int = ctypes.sizeof(IflashHeader) INS_IFL_LEN: Final[int] = ctypes.sizeof(IflashHeader)
def check_format(self, input_object: str | bytes | bytearray) -> bool: def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Insyde iFlash/iFdPacker Update image """ """ Check if input is Insyde iFlash/iFdPacker Update image """
@ -92,7 +92,7 @@ class InsydeIfdExtract(BIOSUtility):
return False return False
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract Insyde iFlash/iFdPacker Update images """ """ Parse & Extract Insyde iFlash/iFdPacker Update images """
input_buffer: bytes = file_to_bytes(in_object=input_object) input_buffer: bytes = file_to_bytes(in_object=input_object)
@ -105,7 +105,7 @@ class InsydeIfdExtract(BIOSUtility):
ifdpack_code: int = self._insyde_packer_extract(input_buffer=input_buffer, extract_path=ifdpack_path, ifdpack_code: int = self._insyde_packer_extract(input_buffer=input_buffer, extract_path=ifdpack_path,
padding=padding) padding=padding)
return iflash_code and ifdpack_code return (iflash_code and ifdpack_code) == 0
def _insyde_iflash_detect(self, input_buffer: bytes) -> list: def _insyde_iflash_detect(self, input_buffer: bytes) -> list:
""" Detect Insyde iFlash Update image """ """ Detect Insyde iFlash Update image """
@ -219,7 +219,7 @@ class InsydeIfdExtract(BIOSUtility):
if is_szip_supported(in_path=sfx_path, padding=padding + 8, args=[f'-p{self.INS_SFX_PWD}'], silent=False): if is_szip_supported(in_path=sfx_path, padding=padding + 8, args=[f'-p{self.INS_SFX_PWD}'], silent=False):
if szip_decompress(in_path=sfx_path, out_path=extract_path, in_name='Insyde iFdPacker > 7-Zip SFX', if szip_decompress(in_path=sfx_path, out_path=extract_path, in_name='Insyde iFdPacker > 7-Zip SFX',
padding=padding + 8, args=[f'-p{self.INS_SFX_PWD}'], check=True) == 0: padding=padding + 8, args=[f'-p{self.INS_SFX_PWD}'], check=True):
os.remove(path=sfx_path) os.remove(path=sfx_path)
else: else:
return 125 return 125
@ -232,10 +232,10 @@ class InsydeIfdExtract(BIOSUtility):
if self.check_format(input_object=sfx_file): if self.check_format(input_object=sfx_file):
printer(message=path_name(in_path=sfx_file), padding=padding + 12) printer(message=path_name(in_path=sfx_file), padding=padding + 12)
ifd_code: int = self.parse_format(input_object=sfx_file, extract_path=extract_folder(sfx_file), ifd_status: int = self.parse_format(input_object=sfx_file, extract_path=extract_folder(sfx_file),
padding=padding + 16) padding=padding + 16)
exit_codes.append(ifd_code) exit_codes.append(0 if ifd_status else 1)
return sum(exit_codes) return sum(exit_codes)

View file

@ -12,6 +12,8 @@ import logging
import os import os
import re import re
from typing import Final
import pefile import pefile
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
@ -33,9 +35,9 @@ class PanasonicBiosExtract(BIOSUtility):
TITLE: str = 'Panasonic BIOS Package Extractor' TITLE: str = 'Panasonic BIOS Package Extractor'
PAN_PE_DESC_UNP: str = 'UNPACK UTILITY' PAN_PE_DESC_UNP: Final[str] = 'UNPACK UTILITY'
PAN_PE_DESC_UPD: str = 'BIOS UPDATE' PAN_PE_DESC_UPD: Final[str] = 'BIOS UPDATE'
def check_format(self, input_object: str | bytes | bytearray) -> bool: def check_format(self, input_object: str | bytes | bytearray) -> bool:
""" Check if input is Panasonic BIOS Package PE """ """ Check if input is Panasonic BIOS Package PE """
@ -51,7 +53,7 @@ class PanasonicBiosExtract(BIOSUtility):
return True return True
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract Panasonic BIOS Package PE """ """ Parse & Extract Panasonic BIOS Package PE """
upd_pe_file: pefile.PE = ms_pe(in_file=input_object, padding=padding) # type: ignore upd_pe_file: pefile.PE = ms_pe(in_file=input_object, padding=padding) # type: ignore
@ -89,7 +91,7 @@ class PanasonicBiosExtract(BIOSUtility):
is_upd_extracted = self._panasonic_img_extract(pe_file=upd_pe_file, extract_path=extract_path, is_upd_extracted = self._panasonic_img_extract(pe_file=upd_pe_file, extract_path=extract_path,
pe_name=upd_pe_name, padding=upd_padding + 8) pe_name=upd_pe_name, padding=upd_padding + 8)
return 0 if is_upd_extracted else 1 return is_upd_extracted
@staticmethod @staticmethod
def _panasonic_pkg_name(input_object: str | bytes | bytearray) -> str: def _panasonic_pkg_name(input_object: str | bytes | bytearray) -> str:
@ -123,7 +125,7 @@ class PanasonicBiosExtract(BIOSUtility):
printer(message=f'Panasonic BIOS Package > PE > CAB {cab_tag}', padding=padding) printer(message=f'Panasonic BIOS Package > PE > CAB {cab_tag}', padding=padding)
if szip_decompress(in_path=cab_path, out_path=extract_path, in_name='CAB', if szip_decompress(in_path=cab_path, out_path=extract_path, in_name='CAB',
padding=padding + 4, check=True) == 0: padding=padding + 4, check=True):
os.remove(path=cab_path) # Successful extraction, delete CAB archive os.remove(path=cab_path) # Successful extraction, delete CAB archive
for extracted_file_path in path_files(in_path=extract_path): for extracted_file_path in path_files(in_path=extract_path):
@ -181,11 +183,13 @@ class PanasonicBiosExtract(BIOSUtility):
printer(message='Successful PE Resource extraction!', padding=padding + 4) printer(message='Successful PE Resource extraction!', padding=padding + 4)
ami_pfat_extract: AmiPfatExtract = AmiPfatExtract()
# Detect & Unpack AMI BIOS Guard (PFAT) BIOS image # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image
if AmiPfatExtract().check_format(input_object=res_raw): if ami_pfat_extract.check_format(input_object=res_raw):
pfat_dir: str = os.path.join(extract_path, res_tag) pfat_dir: str = os.path.join(extract_path, res_tag)
AmiPfatExtract().parse_format(input_object=res_raw, extract_path=pfat_dir, ami_pfat_extract.parse_format(input_object=res_raw, extract_path=pfat_dir,
padding=padding + 8) padding=padding + 8)
else: else:
if is_ms_pe(in_file=res_raw): if is_ms_pe(in_file=res_raw):

View file

@ -110,7 +110,7 @@ class PhoenixTdkExtract(BIOSUtility):
return bool(self._get_phoenix_tdk(in_buffer=input_buffer)[1] is not None) return bool(self._get_phoenix_tdk(in_buffer=input_buffer)[1] is not None)
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract Phoenix Tools Development Kit (TDK) Packer """ """ Parse & Extract Phoenix Tools Development Kit (TDK) Packer """
exit_code: int = 0 exit_code: int = 0
@ -199,7 +199,7 @@ class PhoenixTdkExtract(BIOSUtility):
with open(file=mod_file, mode='wb') as out_file: with open(file=mod_file, mode='wb') as out_file:
out_file.write(mod_data) out_file.write(mod_data)
return exit_code return exit_code == 0
@staticmethod @staticmethod
def _get_tdk_base(in_buffer: bytes | bytearray, pack_off: int) -> int | None: def _get_tdk_base(in_buffer: bytes | bytearray, pack_off: int) -> int | None:

View file

@ -11,6 +11,7 @@ import logging
import os import os
from re import Match from re import Match
from typing import Final
from pefile import PE from pefile import PE
@ -28,7 +29,7 @@ class PortwellEfiExtract(BIOSUtility):
TITLE: str = 'Portwell EFI Update Extractor' TITLE: str = 'Portwell EFI Update Extractor'
FILE_NAMES: dict[int, str] = { FILE_NAMES: Final[dict[int, str]] = {
0: 'Flash.efi', 0: 'Flash.efi',
1: 'Fparts.txt', 1: 'Fparts.txt',
2: 'Update.nsh', 2: 'Update.nsh',
@ -56,7 +57,7 @@ class PortwellEfiExtract(BIOSUtility):
return False return False
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> None: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract Portwell UEFI Unpacker """ """ Parse & Extract Portwell UEFI Unpacker """
# Initialize EFI Payload file chunks # Initialize EFI Payload file chunks
@ -83,6 +84,8 @@ class PortwellEfiExtract(BIOSUtility):
self._parse_efi_files(extract_path=extract_path, efi_files=efi_files, padding=padding) self._parse_efi_files(extract_path=extract_path, efi_files=efi_files, padding=padding)
return True
@staticmethod @staticmethod
def _get_portwell_pe(in_buffer: bytes) -> tuple: def _get_portwell_pe(in_buffer: bytes) -> tuple:
""" Get PE of Portwell EFI executable """ """ Get PE of Portwell EFI executable """
@ -161,7 +164,7 @@ class PortwellEfiExtract(BIOSUtility):
os.replace(src=file_path, dst=comp_fname) os.replace(src=file_path, dst=comp_fname)
# Successful decompression, delete compressed file # Successful decompression, delete compressed file
if efi_decompress(in_path=comp_fname, out_path=file_path, padding=padding + 8) == 0: if efi_decompress(in_path=comp_fname, out_path=file_path, padding=padding + 8):
os.remove(path=comp_fname) os.remove(path=comp_fname)

View file

@ -30,7 +30,7 @@ class ToshibaComExtract(BIOSUtility):
return bool(PAT_TOSHIBA_COM.search(string=input_buffer, endpos=0x100)) return bool(PAT_TOSHIBA_COM.search(string=input_buffer, endpos=0x100))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract Toshiba BIOS COM image """ """ Parse & Extract Toshiba BIOS COM image """
make_dirs(in_path=extract_path, delete=True) make_dirs(in_path=extract_path, delete=True)
@ -55,14 +55,14 @@ class ToshibaComExtract(BIOSUtility):
except Exception as error: # pylint: disable=broad-except except Exception as error: # pylint: disable=broad-except
printer(message=f'Error: ToshibaComExtractor could not extract {input_path}: {error}!', padding=padding) printer(message=f'Error: ToshibaComExtractor could not extract {input_path}: {error}!', padding=padding)
return 1 return False
if input_path != input_object: if input_path != input_object:
os.remove(path=input_path) os.remove(path=input_path)
printer(message='Successful extraction via ToshibaComExtractor!', padding=padding) printer(message='Successful extraction via ToshibaComExtractor!', padding=padding)
return 0 return True
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -31,7 +31,7 @@ class VaioPackageExtract(BIOSUtility):
return bool(PAT_VAIO_CFG.search(string=input_buffer)) return bool(PAT_VAIO_CFG.search(string=input_buffer))
def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> int: def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool:
""" Parse & Extract or Unlock VAIO Packaging Manager """ """ Parse & Extract or Unlock VAIO Packaging Manager """
input_buffer: bytes = file_to_bytes(input_object) input_buffer: bytes = file_to_bytes(input_object)
@ -47,9 +47,9 @@ class VaioPackageExtract(BIOSUtility):
else: else:
printer(message='Error: Failed to Extract or Unlock executable!', padding=padding) printer(message='Error: Failed to Extract or Unlock executable!', padding=padding)
return 1 return False
return 0 return True
@staticmethod @staticmethod
def _vaio_cabinet(name: str, buffer: bytes | bytearray, extract_path: str, padding: int = 0) -> int: def _vaio_cabinet(name: str, buffer: bytes | bytearray, extract_path: str, padding: int = 0) -> int:
@ -94,7 +94,7 @@ class VaioPackageExtract(BIOSUtility):
if is_szip_supported(in_path=cab_path, padding=padding + 8, silent=False): if is_szip_supported(in_path=cab_path, padding=padding + 8, silent=False):
if szip_decompress(in_path=cab_path, out_path=extract_path, in_name='VAIO CAB', if szip_decompress(in_path=cab_path, out_path=extract_path, in_name='VAIO CAB',
padding=padding + 8, check=True) == 0: padding=padding + 8, check=True):
os.remove(path=cab_path) os.remove(path=cab_path)
else: else:
return 3 return 3

16
main.py
View file

@ -29,18 +29,18 @@ from biosutilities.vaio_package_extract import VaioPackageExtract
if __name__ == '__main__': if __name__ == '__main__':
main_argparser: ArgumentParser = ArgumentParser(allow_abbrev=False) main_argparser: ArgumentParser = ArgumentParser(allow_abbrev=False)
main_argparser.add_argument('input_files', nargs='+') main_argparser.add_argument('paths', nargs='+')
main_argparser.add_argument('--output-folder', help='extraction folder') main_argparser.add_argument('-e', '--auto-exit', help='do not pause on exit', action='store_true')
main_argparser.add_argument('--pause-exit', help='pause on exit', action='store_false') main_argparser.add_argument('-o', '--output-dir', help='extraction directory')
main_arguments: Namespace = main_argparser.parse_args() main_arguments: Namespace = main_argparser.parse_args()
if main_arguments.output_folder: if main_arguments.output_dir:
output_folder: Path = Path(main_arguments.output_folder) output_folder: Path = Path(main_arguments.output_dir)
else: else:
output_folder = Path(main_arguments.input_files[0]).parent output_folder = Path(main_arguments.paths[0]).parent
util_arguments: list[str] = [*main_arguments.input_files, '-e', '-o', str(output_folder.absolute())] util_arguments: list[str] = [*main_arguments.paths, '-e', '-o', str(output_folder.absolute())]
AmiUcpExtract(arguments=util_arguments).run_utility() AmiUcpExtract(arguments=util_arguments).run_utility()
AmiPfatExtract(arguments=util_arguments).run_utility() AmiPfatExtract(arguments=util_arguments).run_utility()
@ -59,5 +59,5 @@ if __name__ == '__main__':
AppleEfiIm4pSplit(arguments=util_arguments).run_utility() AppleEfiIm4pSplit(arguments=util_arguments).run_utility()
AppleEfiIdentify(arguments=util_arguments).run_utility() AppleEfiIdentify(arguments=util_arguments).run_utility()
if main_arguments.pause_exit: if not main_arguments.auto_exit:
input('Press any key to exit...') input('Press any key to exit...')