From d8e23f9ef332240b53ad24ba24b2363699222b7b Mon Sep 17 00:00:00 2001 From: Plato Mavropoulos Date: Wed, 23 Oct 2024 13:24:16 +0300 Subject: [PATCH] BIOSUtilities v24.10.23 New "package" flow, arguments now provided during utility call (README) New "main" flow, using old "run_utility" method of BIOSUtility (README) Removed "run_utility" and "show_version" methods from base BIOSUtility Removed argparse argument parsing logic from base BIOSUtility class Removed notion of "pause" (i.e. user action) from BIOSUtility logic Adjusted the README with usage info for "main" and "package" flows --- CHANGELOG | 9 ++ README.md | 143 ++++++++---------- biosutilities/__init__.py | 2 +- biosutilities/ami_pfat_extract.py | 54 +++---- biosutilities/ami_ucp_extract.py | 85 ++++++----- biosutilities/apple_efi_id.py | 60 ++++---- biosutilities/apple_efi_im4p.py | 24 ++-- biosutilities/apple_efi_pbzx.py | 32 ++--- biosutilities/apple_efi_pkg.py | 74 +++++----- biosutilities/award_bios_extract.py | 33 ++--- biosutilities/common/system.py | 7 +- biosutilities/common/templates.py | 154 ++------------------ biosutilities/common/texts.py | 10 +- biosutilities/dell_pfs_extract.py | 89 ++++++------ biosutilities/fujitsu_sfx_extract.py | 28 ++-- biosutilities/fujitsu_upc_extract.py | 34 ++--- biosutilities/insyde_ifd_extract.py | 31 ++-- biosutilities/panasonic_bios_extract.py | 44 +++--- biosutilities/phoenix_tdk_extract.py | 36 +++-- biosutilities/portwell_efi_extract.py | 21 ++- biosutilities/toshiba_com_extract.py | 29 ++-- biosutilities/vaio_package_extract.py | 28 ++-- main.py | 184 +++++++++++++++++++----- requirements-dev.txt | 2 +- 24 files changed, 561 insertions(+), 652 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bcbb5c6..0d208c5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,12 @@ +24.10.23 + +New "package" flow, arguments now provided during utility call (README) +New "main" flow, using old "run_utility" method of BIOSUtility (README) +Removed "run_utility" and "show_version" methods from base BIOSUtility +Removed argparse argument parsing logic from base BIOSUtility class +Removed notion of "pause" (i.e. user action) from BIOSUtility logic +Adjusted the README with usage info for "main" and "package" flows + 24.10.18 Removed all Python built-in library keyword arguments (#55) diff --git a/README.md b/README.md index 4dca9d5..eb7b5ff 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ Various BIOS/UEFI-related utilities which aid in modding and/or research ### Main -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 "Package" method. If needed, a few options can be set, by using the command line: ``` bash -usage: [-h] [-e] [-o OUTPUT_DIR] paths [paths ...] +usage: main.py [-h] [-e] [-o OUTPUT_DIR] [paths ...] positional arguments: paths @@ -26,29 +26,9 @@ options: python ./main.py "/path/to/input/file.bin" --output-dir "/path/to/file extractions" ``` -### BIOSUtility +If no arguments/options are provided, the "main" script requests the input and output paths from the user. If no output path is provided, the utility will use the parent directory of the first input file or fallback to the runtime execution directory. -Each utility is derived from a base template: BIOSUtility. The base BIOSUtility offers the following options, applicable to all utilities: - -``` bash -usage: [-h] [-e] [-o OUTPUT_DIR] [paths ...] - -positional arguments: - paths - -options: - -h, --help show help and exit - -e, --auto-exit skip user action prompts - -o OUTPUT_DIR, --output-dir OUTPUT_DIR output extraction directory -``` - -``` bash -python -m biosutilities.ami_pfat_extract -e "/path/to/input/file1.bin" "/path/to/input/file2.bin" "/path/to/input/folder/with/files/" -o "/path/to/output_directory" -``` - -If no arguments are provided, the BIOSUtility.run_utility() method gets executed, which will request the input and output paths from the user. If no output path is provided, the utility will use the parent directory of the first input file or fallback to the runtime execution directory. - -``` bash +``` python Enter input file or directory path: "C:\P5405CSA.303" Enter output directory path: "C:\P5405CSA.303_output" @@ -56,7 +36,7 @@ Enter output directory path: "C:\P5405CSA.303_output" ### Package -All utilities form the "biosutilities" python package, which can be installed from PyPi: +Each utility is derived from a base "BIOSUtility" template and all utilities form the "biosutilities" python package, which can be installed from PyPi: ``` bash python -m pip install --upgrade biosutilities[pefile,lznt1] @@ -67,72 +47,67 @@ Installing the python package is the recommended way to call one or more utiliti ``` python from biosutilities.ami_pfat_extract import AmiPfatExtract -ami_pfat_extractor = AmiPfatExtract() +ami_pfat_extractor = AmiPfatExtract(input_object='/path/to/input/file.bin', extract_path='/path/to/output/folder/') -ami_pfat_extractor.check_format(input_object='/path/to/input/file.bin') -ami_pfat_extractor.parse_format(input_object='/path/to/input/file.bin', extract_path='/path/to/output/folder/') +is_supported = ami_pfat_extractor.check_format() +is_extracted = ami_pfat_extractor.parse_format() ``` ``` python from biosutilities.dell_pfs_extract import DellPfsExtract -dell_pfs_extractor = DellPfsExtract() +with open('/path/to/input/file.bin', 'rb') as pfs_file: + pfs_data = pfs_file.read() -with open(file='/path/to/input/file.bin', mode='rb') as pfs_file: - pfs_buffer = pfs_file.read() +dell_pfs_extractor = DellPfsExtract(input_object=pfs_data, extract_path='/path/to/output/directory/', padding=8) -dell_pfs_extractor.check_format(input_object=pfs_buffer) -dell_pfs_extractor.parse_format(input_object=pfs_buffer, extract_path='/path/to/output/directory/', padding=8) +is_supported = dell_pfs_extractor.check_format() +is_extracted = dell_pfs_extractor.parse_format() ``` +#### Arguments + +Each BIOSUtility expects the following required and optional arguments to check and/or parse a given file format: + +##### input_object (required) + ``` python -from biosutilities.phoenix_tdk_extract import PhoenixTdkExtract - -phoenix_tdk_extractor = PhoenixTdkExtract(arguments=['-e', '/path/to/input/file.bin', '-o', '/path/to/output/folder/']) - -phoenix_tdk_extractor.run_utility(padding=4) +input_object: str | bytes | bytearray = b'' ``` +##### extract_path (required) + ``` python -from biosutilities.apple_efi_pbzx import AppleEfiPbzxExtract - -apple_efi_pbzx_extractor = AppleEfiPbzxExtract() - -apple_efi_pbzx_extractor.show_version(is_boxed=False, padding=12) +extract_path: str = '' ``` -It also allows to use directly the four public methods which are inherited by every utility from the base BIOSUtility class. - -#### run_utility - -Run utility after checking for supported format +##### padding (optional) ``` python -run_utility(padding: int = 0) -> bool +padding: int = 0 ``` -#### check_format +If the required arguments are not provided, it is still possible to use the BIOSUtility-inherited instance to access +auxiliary public methods and class constants. However, checking and/or parsing of file formats will not yield results. + +#### Methods + +Once the BIOSUtility-inherited object is initialized with arguments, its two public methods can be called: + +##### check_format Check if input object is of specific supported format ``` python -check_format(input_object: str | bytes | bytearray) -> bool +is_supported: bool = check_format() ``` -#### parse_format +##### parse_format Process input object as a specific supported format ``` python -parse_format(input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool -``` - -#### show_version - -Show title and version of utility - -``` python -show_version(is_boxed: bool = True, padding: int = 0) -> None +is_extracted: bool = parse_format() ``` ## Compatibility @@ -208,7 +183,7 @@ Parses AMI BIOS Guard (a.k.a. PFAT, Platform Firmware Armoring Technology) image Note that the AMI PFAT structure may not have an explicit component order. AMI's BIOS Guard Firmware Update Tool (AFUBGT) updates components based on the user/OEM provided Parameters and Options or Index Information table, when applicable. Thus, merging all the components together does not usually yield a proper SPI/BIOS/UEFI image. The utility does generate such a merged file with the name "00 -- \\_ALL.bin" but it is up to the end user to determine its usefulness. Additionally, any custom OEM data, after the AMI PFAT structure, is stored in the last file with the name "\ -- \_OOB.bin" and it is once again up to the end user to determine its usefulness. In cases where the trailing custom OEM data includes a nested AMI PFAT structure, the utility will process and extract it automatically as well. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. @@ -224,11 +199,11 @@ Optionally, to decompile the AMI PFAT \> Intel BIOS Guard Scripts, you must have Parses AMI UCP (Utility Configuration Program) Update executables, extracts their firmware components (e.g. SPI/BIOS/UEFI, EC, ME etc) and shows all relevant info. It supports all AMI UCP revisions and formats, including those with nested AMI PFAT, AMI UCP or Insyde iFlash/iFdPacker structures. The output comprises only final firmware components and utilities which are directly usable by end users. -#### Usage +#### Arguments Additional optional arguments are provided for this utility: -* -c or --checksum : verify AMI UCP Checksums (slow) +* checksum -> bool : verify AMI UCP Checksums (slow) #### Prerequisites @@ -249,7 +224,7 @@ Note: On Linux and macOS, you'll need to compile TianoCompress from sources as n Parses Apple IM4P multi-EFI files and splits all detected EFI firmware into separate Intel SPI/BIOS images. The output comprises only final firmware components and utilities which are directly usable by end users. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. @@ -263,17 +238,17 @@ To run the utility, you do not need any prerequisites. 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 +#### Arguments Additional optional arguments are provided for this utility: -* -q or --silent : suppress structure display +* silent -> bool : suppress structure display The utility exposes certain public class attributes, once parse_format() method has been successfully executed: -* _efi_file_name_ -> str : Suggested image filename, based on Intel "IBIOSI" information -* _intel_bios_info_ -> dict[str, str] : Information contained at Intel "IBIOSI" structure -* _apple_rom_version_ -> defaultdict[str, set] : Information contained at "Apple ROM Version" structure +* efi_file_name -> str : Suggested image filename, based on Intel "IBIOSI" information +* intel_bios_info -> dict[str, str] : Information contained at Intel "IBIOSI" structure +* apple_rom_version -> defaultdict[str, set] : Information contained at "Apple ROM Version" structure #### Prerequisites @@ -288,7 +263,7 @@ To run the utility, you must have the following 3rd party tools at PATH or "exte 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 +#### Arguments No additional optional arguments are provided for this utility. @@ -304,7 +279,7 @@ To run the utility, you must have the following 3rd party tools at PATH or "exte Parses Apple EFI PBZX images, re-assembles their CPIO payload and extracts its firmware components (e.g. IM4P, EFI, Utilities, Scripts etc). It supports CPIO re-assembly from both Raw and XZ compressed PBZX Chunks. The output comprises only final firmware components and utilities which are directly usable by end users. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. @@ -320,7 +295,7 @@ To run the utility, you must have the following 3rd party tools at PATH or "exte Parses Award BIOS images and extracts their modules (e.g. RAID, MEMINIT, \_EN_CODE, awardext etc). It supports all Award BIOS image revisions and formats, including those which contain LZH compressed files. The output comprises only final firmware components which are directly usable by end users. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. @@ -336,12 +311,12 @@ To run the utility, you must have the following 3rd party tool at PATH or "exter Parses Dell PFS Update images and extracts their Firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and Utilities (e.g. Flasher etc) component sections. It supports all Dell PFS revisions and formats, including those which are originally LZMA compressed in ThinOS packages (PKG), ZLIB compressed or Intel BIOS Guard (PFAT) protected. The output comprises only final firmware components which are directly usable by end users. -#### Usage +#### Arguments Additional optional arguments are provided for this utility: -* -a or --advanced : extract signatures and metadata -* -s or --structure : show PFS structure information +* advanced -> bool : extract signatures and metadata +* structure -> bool : show PFS structure information #### Prerequisites @@ -355,7 +330,7 @@ Optionally, to decompile the Intel BIOS Guard (PFAT) Scripts, you must have the Parses Fujitsu SFX BIOS images and extracts their obfuscated Microsoft CAB archived firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. WinPhlash, PHLASH.INI etc) components. The output comprises only final firmware components which are directly usable by end users. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. @@ -371,7 +346,7 @@ To run the utility, you must have the following 3rd party tool at PATH or "exter Parses Fujitsu UPC BIOS images and extracts their EFI compressed SPI/BIOS/UEFI firmware component. The output comprises only a final firmware component which is directly usable by end users. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. @@ -389,7 +364,7 @@ Note: On Linux and macOS, you'll need to compile TianoCompress from sources as n Parses Insyde iFlash/iFdPacker Update images and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc) and utilities (e.g. InsydeFlash, H2OFFT, FlsHook, iscflash, platform.ini etc) components. It supports all Insyde iFlash/iFdPacker revisions and formats, including those which are 7-Zip SFX 7z compressed in raw, obfuscated or password-protected form. The output comprises only final firmware components which are directly usable by end users. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. @@ -403,7 +378,7 @@ To run the utility, you do not need any prerequisites. Parses Panasonic BIOS Package executables and extracts their firmware (e.g. SPI, BIOS/UEFI, EC etc) and utilities (e.g. winprom, configuration etc) components. It supports all Panasonic BIOS Package revisions and formats, including those which contain LZNT1 compressed files and/or AMI PFAT payloads. The output comprises only final firmware components which are directly usable by end users. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. @@ -424,7 +399,7 @@ Moreover, you must have the following 3rd party tool at PATH or "external": Parses Phoenix Tools Development Kit (TDK) Packer executables and extracts their firmware (e.g. SPI, BIOS/UEFI, EC etc) and utilities (e.g. WinFlash etc) components. It supports all Phoenix TDK Packer revisions and formats, including those which contain LZMA compressed files. The output comprises only final firmware components which are directly usable by end users. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. @@ -440,7 +415,7 @@ To run the utility, you must have the following 3rd party Python module installe Parses Portwell UEFI Unpacker EFI executables (usually named "Update.efi") and extracts their firmware (e.g. SPI, BIOS/UEFI, EC etc) and utilities (e.g. Flasher etc) components. It supports all known Portwell UEFI Unpacker revisions (v1.1, v1.2, v2.0) and formats (used, empty, null), including those which contain EFI compressed files. The output comprises only final firmware components and utilities which are directly usable by end users. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. @@ -462,7 +437,7 @@ Note: On Linux and macOS, you'll need to compile TianoCompress from sources as n Parses Toshiba BIOS COM images and extracts their raw or compressed SPI/BIOS/UEFI firmware component. This utility is effectively a python wrapper around [ToshibaComExtractor by LongSoft](https://github.com/LongSoft/ToshibaComExtractor). The output comprises only a final firmware component which is directly usable by end users. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. @@ -480,7 +455,7 @@ Note: On Linux, you'll need to compile comextract from sources as no pre-built b Parses VAIO Packaging Manager executables and extracts their firmware (e.g. SPI, BIOS/UEFI, EC, ME etc), utilities (e.g. WBFLASH etc) and driver (audio, video etc) components. If direct extraction fails, it attempts to unlock the executable in order to run at all non-VAIO systems and allow the user to choose the extraction location. It supports all VAIO Packaging Manager revisions and formats, including those which contain obfuscated Microsoft CAB archives or obfuscated unlock values. The output comprises only final firmware components which are directly usable by end users. -#### Usage +#### Arguments No additional optional arguments are provided for this utility. diff --git a/biosutilities/__init__.py b/biosutilities/__init__.py index 72781dd..ac2b219 100644 --- a/biosutilities/__init__.py +++ b/biosutilities/__init__.py @@ -5,4 +5,4 @@ Copyright (C) 2018-2024 Plato Mavropoulos """ -__version__ = '24.10.18' +__version__ = '24.10.23' diff --git a/biosutilities/ami_pfat_extract.py b/biosutilities/ami_pfat_extract.py index 06372bb..2324461 100644 --- a/biosutilities/ami_pfat_extract.py +++ b/biosutilities/ami_pfat_extract.py @@ -213,17 +213,17 @@ class AmiPfatExtract(BIOSUtility): PFAT_INT_SIG_R3K_LEN: Final[int] = ctypes.sizeof(IntelBiosGuardSignatureRsa3k) 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) -> bool: """ Check if input is AMI BIOS Guard """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) 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) -> bool: + def parse_format(self) -> bool: """ 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=self.input_object) pfat_buffer: bytes = self._get_ami_pfat(input_object=input_buffer) @@ -233,19 +233,19 @@ class AmiPfatExtract(BIOSUtility): bg_sign_len: int = 0 - extract_name: str = path_name(in_path=extract_path).removesuffix(extract_suffix()) + extract_name: str = path_name(in_path=self.extract_path).removesuffix(extract_suffix()) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) - block_all, block_off, file_count = self._parse_pfat_hdr(buffer=pfat_buffer, padding=padding) + block_all, block_off, file_count = self._parse_pfat_hdr(buffer=pfat_buffer, padding=self.padding) for block in block_all: file_desc, file_name, _, _, _, file_index, block_index, block_count = block if block_index == 0: - printer(message=file_desc, padding=padding + 4) + printer(message=file_desc, padding=self.padding + 4) - file_path = os.path.join(extract_path, self._get_file_name(index=file_index + 1, name=file_name)) + file_path = os.path.join(self.extract_path, self._get_file_name(index=file_index + 1, name=file_name)) all_blocks_dict[file_index] = b'' @@ -253,9 +253,9 @@ class AmiPfatExtract(BIOSUtility): bg_hdr: Any = ctypes_struct(buffer=pfat_buffer, start_offset=block_off, class_object=IntelBiosGuardHeader) - printer(message=f'Intel BIOS Guard {block_status} Header:\n', padding=padding + 8) + printer(message=f'Intel BIOS Guard {block_status} Header:\n', padding=self.padding + 8) - bg_hdr.struct_print(padding=padding + 12) + bg_hdr.struct_print(padding=self.padding + 12) bg_script_bgn: int = block_off + self.PFAT_INT_HDR_LEN bg_script_end: int = bg_script_bgn + bg_hdr.ScriptSize @@ -270,7 +270,7 @@ class AmiPfatExtract(BIOSUtility): is_sfam, _, _, _, _ = bg_hdr.get_flags() # SFAM, ProtectEC, GFXMitDis, FTU, Reserved if is_sfam: - printer(message=f'Intel BIOS Guard {block_status} Signature:\n', padding=padding + 8) + printer(message=f'Intel BIOS Guard {block_status} Signature:\n', padding=self.padding + 8) # Manual BIOS Guard Signature length detection from Header pattern (e.g. Panasonic) if bg_sign_len == 0: @@ -280,11 +280,11 @@ class AmiPfatExtract(BIOSUtility): # Adjust next block to start after current block Data + Signature block_off += self.parse_bg_sign(input_data=pfat_buffer, sign_offset=bg_data_end, - sign_length=bg_sign_len, print_info=True, padding=padding + 12) + sign_length=bg_sign_len, print_info=True, padding=self.padding + 12) - printer(message=f'Intel BIOS Guard {block_status} Script:\n', padding=padding + 8) + printer(message=f'Intel BIOS Guard {block_status} Script:\n', padding=self.padding + 8) - _ = self.parse_bg_script(script_data=pfat_buffer[bg_script_bgn:bg_script_end], padding=padding + 12) + _ = self.parse_bg_script(script_data=pfat_buffer[bg_script_bgn:bg_script_end], padding=self.padding + 12) with open(file_path, 'ab') as out_dat: out_dat.write(bg_data_bin) @@ -292,27 +292,33 @@ class AmiPfatExtract(BIOSUtility): all_blocks_dict[file_index] += bg_data_bin if block_index + 1 == block_count: - if self.check_format(input_object=all_blocks_dict[file_index]): - self.parse_format(input_object=all_blocks_dict[file_index], - extract_path=extract_folder(file_path), padding=padding + 8) + ami_pfat_extract: AmiPfatExtract = AmiPfatExtract( + input_object=all_blocks_dict[file_index], extract_path=extract_folder(file_path), + padding=self.padding + 8) + + if ami_pfat_extract.check_format(): + ami_pfat_extract.parse_format() pfat_oob_data: bytes = pfat_buffer[block_off:] # Store out-of-bounds data after the end of PFAT files pfat_oob_name: str = self._get_file_name(index=file_count + 1, name=f'{extract_name}_OOB.bin') - pfat_oob_path: str = os.path.join(extract_path, pfat_oob_name) + pfat_oob_path: str = os.path.join(self.extract_path, pfat_oob_name) with open(pfat_oob_path, 'wb') as out_oob: out_oob.write(pfat_oob_data) - if self.check_format(input_object=pfat_oob_data): - self.parse_format(input_object=pfat_oob_data, extract_path=extract_folder(pfat_oob_path), padding=padding) + ami_pfat_extract = AmiPfatExtract( + input_object=pfat_oob_data, extract_path=extract_folder(pfat_oob_path), padding=self.padding) + + if ami_pfat_extract.check_format(): + ami_pfat_extract.parse_format() in_all_data: bytes = b''.join([block[1] for block in sorted(all_blocks_dict.items())]) in_all_name: str = self._get_file_name(index=0, name=f'{extract_name}_ALL.bin') - in_all_path: str = os.path.join(extract_path, in_all_name) + in_all_path: str = os.path.join(self.extract_path, in_all_name) with open(in_all_path, 'wb') as out_all: out_all.write(in_all_data + pfat_oob_data) @@ -471,7 +477,3 @@ class AmiPfatExtract(BIOSUtility): printer(message=block[0], padding=padding + 8, new_line=False) return block_all, hdr_size, files_count - - -if __name__ == '__main__': - AmiPfatExtract().run_utility() diff --git a/biosutilities/ami_ucp_extract.py b/biosutilities/ami_ucp_extract.py index d85ac2a..782bbb7 100644 --- a/biosutilities/ami_ucp_extract.py +++ b/biosutilities/ami_ucp_extract.py @@ -183,10 +183,6 @@ class AmiUcpExtract(BIOSUtility): TITLE: str = 'AMI UCP Update Extractor' - ARGUMENTS: list[tuple[list[str], dict[str, str]]] = [ - (['-c', '--checksum'], {'help': 'verify AMI UCP Checksums (slow)', 'action': 'store_true'}) - ] - # Get common ctypes Structure Sizes UAF_HDR_LEN: Final[int] = ctypes.sizeof(UafHeader) UAF_MOD_LEN: Final[int] = ctypes.sizeof(UafModule) @@ -254,23 +250,29 @@ class AmiUcpExtract(BIOSUtility): '@W64': ['amifldrv64.sys', 'amifldrv64.sys', ''] } - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def __init__(self, input_object: str | bytes | bytearray = b'', extract_path: str = '', padding: int = 0, + checksum: bool = False) -> None: + super().__init__(input_object=input_object, extract_path=extract_path, padding=padding) + + self.checksum: bool = checksum + + def check_format(self) -> bool: """ Check if input is AMI UCP image """ - buffer: bytes = file_to_bytes(in_object=input_object) + buffer: bytes = file_to_bytes(in_object=self.input_object) 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) -> bool: + def parse_format(self) -> bool: """ Parse & Extract AMI UCP structures """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) nal_dict: dict[str, tuple[str, str]] = {} # Initialize @NAL Dictionary per UCP - printer(message='Utility Configuration Program', padding=padding) + printer(message='Utility Configuration Program', padding=self.padding) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) # Get best AMI UCP Pattern match based on @UAF|@HPU Size ucp_buffer, ucp_tag = self._get_ami_ucp(input_object=input_buffer) @@ -278,9 +280,9 @@ class AmiUcpExtract(BIOSUtility): # Parse @UAF|@HPU Header Structure uaf_hdr: Any = ctypes_struct(buffer=ucp_buffer, start_offset=0, class_object=UafHeader) - printer(message=f'Utility Auxiliary File > {ucp_tag}:\n', padding=padding + 4) + printer(message=f'Utility Auxiliary File > {ucp_tag}:\n', padding=self.padding + 4) - uaf_hdr.struct_print(padding=padding + 8) + uaf_hdr.struct_print(padding=self.padding + 8) fake = struct.pack(' None: - super().__init__(arguments=arguments) + def __init__(self, input_object: str | bytes | bytearray = b'', extract_path: str = '', padding: int = 0, + silent: bool = False) -> None: + super().__init__(input_object=input_object, extract_path=extract_path, padding=padding) + + self.silent: bool = silent 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) -> bool: """ Check if input is Apple EFI image """ - if isinstance(input_object, str) and is_file(in_path=input_object) and is_access(in_path=input_object): - if path_suffixes(in_path=input_object)[-1].lower() not in ('.fd', '.scap', '.im4p'): + if isinstance(self.input_object, str) and is_file(in_path=self.input_object) and is_access( + in_path=self.input_object): + if path_suffixes(in_path=self.input_object)[-1].lower() not in ('.fd', '.scap', '.im4p'): return False - input_path: str = input_object + input_path: str = self.input_object input_buffer: bytes = file_to_bytes(in_object=input_path) - elif isinstance(input_object, (bytes, bytearray)): + elif isinstance(self.input_object, (bytes, bytearray)): input_path = os.path.join(runtime_root(), 'APPLE_EFI_ID_INPUT_BUFFER_CHECK.tmp') - input_buffer = input_object + input_buffer = self.input_object with open(input_path, 'wb') as check_out: check_out.write(input_buffer) @@ -160,16 +160,16 @@ class AppleEfiIdentify(BIOSUtility): return False finally: - if input_path != input_object: + if input_path != self.input_object: delete_file(in_path=input_path) - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ 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=self.input_object) - if isinstance(input_object, str) and is_file(in_path=input_object): - input_path: str = input_object + if isinstance(self.input_object, str) and is_file(in_path=self.input_object): + input_path: str = self.input_object else: input_path = os.path.join(runtime_root(), 'APPLE_EFI_ID_INPUT_BUFFER_PARSE.bin') @@ -190,12 +190,12 @@ class AppleEfiIdentify(BIOSUtility): self.PAT_UEFIFIND], text=True)[:36] # UEFIExtract must create its output folder itself - delete_dirs(in_path=extract_path) + delete_dirs(in_path=self.extract_path) - _ = subprocess.run([uefiextract_path(), input_path, bios_id_res, '-o', extract_path, '-m', 'body'], + _ = subprocess.run([uefiextract_path(), input_path, bios_id_res, '-o', self.extract_path, '-m', 'body'], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - with open(os.path.join(extract_path, 'body.bin'), 'rb') as raw_body: + with open(os.path.join(self.extract_path, 'body.bin'), 'rb') as raw_body: body_buffer: bytes = raw_body.read() # Detect decompressed $IBIOSI$ pattern @@ -207,25 +207,25 @@ class AppleEfiIdentify(BIOSUtility): bios_id_hdr = ctypes_struct(buffer=body_buffer, start_offset=bios_id_match.start(), class_object=IntelBiosId) - delete_dirs(in_path=extract_path) # Successful UEFIExtract extraction, remove its output folder + delete_dirs(in_path=self.extract_path) # Successful UEFIExtract extraction, remove its output folder 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=self.padding) return False - if not self.arguments.silent: - printer(message=f'Detected Intel BIOS Info at {bios_id_res}\n', padding=padding) + if not self.silent: + printer(message=f'Detected Intel BIOS Info at {bios_id_res}\n', padding=self.padding) - bios_id_hdr.struct_print(padding=padding + 4) + bios_id_hdr.struct_print(padding=self.padding + 4) self.intel_bios_info = bios_id_hdr.get_bios_id() 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]}') - _ = self._apple_rom_version(input_buffer=input_buffer, padding=padding) + _ = self._apple_rom_version(input_buffer=input_buffer, padding=self.padding) - if input_path != input_object: + if input_path != self.input_object: delete_file(in_path=input_path) return True @@ -255,7 +255,7 @@ class AppleEfiIdentify(BIOSUtility): self.apple_rom_version[rom_version_parts[0].strip()].add(rom_version_parts[1].strip()) - if not self.arguments.silent: + if not self.silent: 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) @@ -263,7 +263,3 @@ class AppleEfiIdentify(BIOSUtility): return True return False - - -if __name__ == '__main__': - AppleEfiIdentify().run_utility() diff --git a/biosutilities/apple_efi_im4p.py b/biosutilities/apple_efi_im4p.py index 215b78c..fb9ae59 100644 --- a/biosutilities/apple_efi_im4p.py +++ b/biosutilities/apple_efi_im4p.py @@ -27,27 +27,27 @@ class AppleEfiIm4pSplit(BIOSUtility): # Intel Flash Descriptor Component Sizes (2MB, 4MB, 8MB, 16MB and 32MB) IFD_COMP_LEN: Final[dict[int, int]] = {2: 0x200000, 3: 0x400000, 4: 0x800000, 5: 0x1000000, 6: 0x2000000} - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input is Apple EFI IM4P image """ - if isinstance(input_object, str) and not input_object.lower().endswith('.im4p'): + if isinstance(self.input_object, str) and not self.input_object.lower().endswith('.im4p'): return False - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) if PAT_APPLE_IM4P.search(input_buffer) and PAT_INTEL_FD.search(input_buffer): return True return False - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ Parse & Split Apple EFI IM4P image """ parse_success: bool = True - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) # Detect IM4P EFI pattern im4p_match: Match[bytes] | None = PAT_APPLE_IM4P.search(input_buffer) @@ -138,23 +138,19 @@ class AppleEfiIm4pSplit(BIOSUtility): output_size: int = len(output_data) - output_name: str = path_stem(in_path=input_object) if isinstance(input_object, str) else 'Part' + output_name: str = path_stem(in_path=self.input_object) if isinstance(self.input_object, str) else 'Part' - output_path: str = os.path.join(extract_path, f'{output_name}_[{ifd_data_txt}].fd') + output_path: str = os.path.join(self.extract_path, f'{output_name}_[{ifd_data_txt}].fd') with open(output_path, 'wb') as output_image: output_image.write(output_data) - printer(message=f'Split Apple EFI image at {ifd_data_txt}!', padding=padding) + printer(message=f'Split Apple EFI image at {ifd_data_txt}!', padding=self.padding) if output_size != ifd_comp_all_size: printer(message=f'Error: Bad image size 0x{output_size:07X}, expected 0x{ifd_comp_all_size:07X}!', - padding=padding + 4) + padding=self.padding + 4) parse_success = False return parse_success - - -if __name__ == '__main__': - AppleEfiIm4pSplit().run_utility() diff --git a/biosutilities/apple_efi_pbzx.py b/biosutilities/apple_efi_pbzx.py index 8ba1de8..78bf196 100644 --- a/biosutilities/apple_efi_pbzx.py +++ b/biosutilities/apple_efi_pbzx.py @@ -51,19 +51,19 @@ class AppleEfiPbzxExtract(BIOSUtility): PBZX_CHUNK_HDR_LEN: Final[int] = ctypes.sizeof(PbzxChunk) - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input is Apple PBZX image """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) return bool(PAT_APPLE_PBZX.search(input_buffer, 0, 4)) - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ Parse & Extract Apple PBZX image """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) cpio_bin: bytes = b'' # Initialize PBZX > CPIO Buffer @@ -74,9 +74,9 @@ class AppleEfiPbzxExtract(BIOSUtility): while chunk_off < len(input_buffer): chunk_hdr: Any = ctypes_struct(buffer=input_buffer, start_offset=chunk_off, class_object=PbzxChunk) - printer(message=f'PBZX Chunk at 0x{chunk_off:08X}\n', padding=padding) + printer(message=f'PBZX Chunk at 0x{chunk_off:08X}\n', padding=self.padding) - chunk_hdr.struct_print(padding=padding + 4) + chunk_hdr.struct_print(padding=self.padding + 4) # PBZX Chunk data starts after its Header comp_bgn: int = chunk_off + self.PBZX_CHUNK_HDR_LEN @@ -90,7 +90,7 @@ class AppleEfiPbzxExtract(BIOSUtility): # Attempt XZ decompression, if applicable to Chunk data cpio_bin += lzma.LZMADecompressor().decompress(comp_bin) - printer(message='Successful LZMA decompression!', padding=padding + 8) + printer(message='Successful LZMA decompression!', padding=self.padding + 8) except Exception as error: # pylint: disable=broad-except logging.debug('Error: Failed to LZMA decompress PBZX Chunk 0x%X: %s', chunk_off, error) @@ -105,21 +105,21 @@ class AppleEfiPbzxExtract(BIOSUtility): # Check that CPIO size is valid based on all Chunks > Initial Size if cpio_len != len(cpio_bin): - printer(message='Error: Unexpected CPIO archive size!', padding=padding) + printer(message='Error: Unexpected CPIO archive size!', padding=self.padding) 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=self.input_object) if isinstance(self.input_object, str) else 'Payload' - cpio_path: str = os.path.join(extract_path, f'{cpio_name}.cpio') + cpio_path: str = os.path.join(self.extract_path, f'{cpio_name}.cpio') with open(cpio_path, 'wb') as cpio_object: cpio_object.write(cpio_bin) # Decompress PBZX > CPIO archive with 7-Zip - 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', - padding=padding, args=['-tCPIO']): + if is_szip_supported(in_path=cpio_path, padding=self.padding, args=['-tCPIO'], silent=False): + if szip_decompress(in_path=cpio_path, out_path=self.extract_path, in_name='CPIO', + padding=self.padding, args=['-tCPIO']): os.remove(cpio_path) # Successful extraction, delete PBZX > CPIO archive else: return False @@ -127,7 +127,3 @@ class AppleEfiPbzxExtract(BIOSUtility): return False return True - - -if __name__ == '__main__': - AppleEfiPbzxExtract().run_utility() diff --git a/biosutilities/apple_efi_pkg.py b/biosutilities/apple_efi_pkg.py index 4562c82..733be10 100644 --- a/biosutilities/apple_efi_pkg.py +++ b/biosutilities/apple_efi_pkg.py @@ -26,15 +26,15 @@ class AppleEfiPkgExtract(BIOSUtility): TITLE: str = 'Apple EFI Package Extractor' - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input is Apple EFI PKG package """ is_apple_efi_pkg: bool = False - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) - if isinstance(input_object, str) and is_file(in_path=input_object): - input_path: str = input_object + if isinstance(self.input_object, str) and is_file(in_path=self.input_object): + input_path: str = self.input_object else: input_path = os.path.join(runtime_root(), 'APPLE_EFI_PKG_INPUT_BUFFER_CHECK.bin') @@ -47,45 +47,45 @@ class AppleEfiPkgExtract(BIOSUtility): break - if input_path != input_object: + if input_path != self.input_object: os.remove(input_path) return is_apple_efi_pkg - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ Parse & Extract Apple EFI PKG packages """ - if isinstance(input_object, str) and is_file(in_path=input_object): - input_path: str = input_object + if isinstance(self.input_object, str) and is_file(in_path=self.input_object): + input_path: str = self.input_object else: input_path = os.path.join(runtime_root(), 'APPLE_EFI_PKG_INPUT_BUFFER_PARSE.bin') with open(input_path, 'wb') as input_path_object: - input_path_object.write(file_to_bytes(in_object=input_object)) + input_path_object.write(file_to_bytes(in_object=self.input_object)) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) - working_dir: str = os.path.join(extract_path, 'temp') + working_dir: str = os.path.join(self.extract_path, 'temp') make_dirs(in_path=working_dir) for pkg_type in ('XAR', 'TAR', 'DMG'): - 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 is_szip_supported(in_path=input_path, padding=self.padding, args=[f'-t{pkg_type}']): + if szip_decompress(in_path=input_path, out_path=working_dir, in_name=pkg_type, padding=self.padding, args=None if pkg_type == 'DMG' else [f'-t{pkg_type}']): break else: return False - if input_path != input_object: + if input_path != self.input_object: os.remove(input_path) for work_file in path_files(in_path=working_dir): if is_file(in_path=work_file) and is_access(in_path=work_file): - 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._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) + self._pbzx_zip(input_path=work_file, extract_path=self.extract_path, padding=self.padding + 4) + self._gzip_cpio(input_path=work_file, extract_path=self.extract_path, padding=self.padding + 4) + self._dmg_zip(input_path=work_file, extract_path=self.extract_path, padding=self.padding + 4) + self._xar_gzip(input_path=work_file, extract_path=self.extract_path, padding=self.padding + 4) delete_dirs(in_path=working_dir) @@ -126,15 +126,16 @@ class AppleEfiPkgExtract(BIOSUtility): def _pbzx_zip(self, input_path: str, extract_path: str, padding: int = 0) -> None: """ PBZX > ZIP """ - pbzx_module: AppleEfiPbzxExtract = AppleEfiPbzxExtract() + pbzx_path: str = extract_folder(in_path=input_path, suffix='_pbzx_zip') - if pbzx_module.check_format(input_object=input_path): - printer(message=f'Extracting PBZX via {pbzx_module.title}', padding=padding) + pbzx_module: AppleEfiPbzxExtract = AppleEfiPbzxExtract( + input_object=input_path, extract_path=pbzx_path, padding=padding + 4) - pbzx_path: str = extract_folder(in_path=input_path, suffix='_pbzx_zip') + if pbzx_module.check_format(): + printer(message=f'Extracting PBZX via {pbzx_module.TITLE}', padding=padding) - 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) + if pbzx_module.parse_format(): + printer(message=f'Successful PBZX extraction via {pbzx_module.TITLE}!', padding=padding) for pbzx_file in path_files(in_path=pbzx_path): if is_file(in_path=pbzx_file) and is_access(in_path=pbzx_file): @@ -174,7 +175,7 @@ class AppleEfiPkgExtract(BIOSUtility): 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(input_object=input_path).check_format(): return None input_name: str = path_name(in_path=input_path) @@ -183,12 +184,13 @@ class AppleEfiPkgExtract(BIOSUtility): working_dir: str = extract_folder(in_path=input_path) - im4p_module: AppleEfiIm4pSplit = AppleEfiIm4pSplit() + im4p_module: AppleEfiIm4pSplit = AppleEfiIm4pSplit( + input_object=input_path, extract_path=working_dir, padding=padding + 8) - if im4p_module.check_format(input_object=input_path): - printer(message=f'Splitting IM4P via {im4p_module.title}', padding=padding + 4) + if im4p_module.check_format(): + printer(message=f'Splitting IM4P via {im4p_module.TITLE}', padding=padding + 4) - im4p_module.parse_format(input_object=input_path, extract_path=working_dir, padding=padding + 8) + im4p_module.parse_format() else: make_dirs(in_path=working_dir, delete=True) @@ -196,13 +198,13 @@ class AppleEfiPkgExtract(BIOSUtility): for efi_source in path_files(in_path=working_dir): if is_file(in_path=efi_source) and is_access(in_path=efi_source): - efi_id_module: AppleEfiIdentify = AppleEfiIdentify() + efi_id_module: AppleEfiIdentify = AppleEfiIdentify( + input_object=efi_source, extract_path=extract_folder(in_path=efi_source), padding=padding + 8) - if efi_id_module.check_format(input_object=efi_source): - printer(message=f'Identifying EFI via {efi_id_module.title}', padding=padding + 4) + if efi_id_module.check_format(): + printer(message=f'Identifying EFI via {efi_id_module.TITLE}', padding=padding + 4) - if efi_id_module.parse_format(input_object=efi_source, extract_path=extract_folder( - in_path=efi_source), padding=padding + 8): + if efi_id_module.parse_format(): efi_dest: str = os.path.join(path_parent(in_path=efi_source), efi_id_module.efi_file_name) os.replace(efi_source, efi_dest) @@ -214,7 +216,3 @@ class AppleEfiPkgExtract(BIOSUtility): delete_dirs(in_path=working_dir) return None - - -if __name__ == '__main__': - AppleEfiPkgExtract().run_utility() diff --git a/biosutilities/award_bios_extract.py b/biosutilities/award_bios_extract.py index cfcb30d..08094ea 100644 --- a/biosutilities/award_bios_extract.py +++ b/biosutilities/award_bios_extract.py @@ -22,19 +22,19 @@ class AwardBiosExtract(BIOSUtility): TITLE: str = 'Award BIOS Module Extractor' - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input is Award BIOS image """ - in_buffer: bytes = file_to_bytes(in_object=input_object) + in_buffer: bytes = file_to_bytes(in_object=self.input_object) return bool(PAT_AWARD_LZH.search(in_buffer)) - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ Parse & Extract Award BIOS image """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) for lzh_match in PAT_AWARD_LZH.finditer(input_buffer): lzh_type: str = lzh_match.group(0).decode('utf-8') @@ -52,7 +52,7 @@ class AwardBiosExtract(BIOSUtility): if len(mod_bin) != 0x2 + hdr_len + mod_len: printer(message=f'Error: Skipped incomplete LZH stream at 0x{mod_bgn:X}!', - padding=padding, new_line=True) + padding=self.padding, new_line=True) continue @@ -61,9 +61,9 @@ class AwardBiosExtract(BIOSUtility): else: tag_txt = f'{mod_bgn:X}_{mod_end:X}' - printer(message=f'{lzh_text} > {tag_txt} [0x{mod_bgn:06X}-0x{mod_end:06X}]', padding=padding) + printer(message=f'{lzh_text} > {tag_txt} [0x{mod_bgn:06X}-0x{mod_end:06X}]', padding=self.padding) - mod_path: str = os.path.join(extract_path, tag_txt) + mod_path: str = os.path.join(self.extract_path, tag_txt) lzh_path: str = f'{mod_path}.lzh' @@ -71,8 +71,8 @@ class AwardBiosExtract(BIOSUtility): lzh_file.write(mod_bin) # Store LZH archive # 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, - padding=padding + 4) + szip_decompress(in_path=lzh_path, out_path=self.extract_path, in_name=lzh_text, + padding=self.padding + 4) # Manually check if 7-Zip extracted LZH due to its CRC check issue if is_file(in_path=mod_path): @@ -80,14 +80,11 @@ class AwardBiosExtract(BIOSUtility): os.remove(lzh_path) # Successful extraction, delete LZH archive + award_bios_extract: AwardBiosExtract = AwardBiosExtract( + input_object=mod_path, extract_path=extract_folder(mod_path), padding=self.padding + 8) + # Extract any nested LZH archives - if self.check_format(input_object=mod_path): - # Recursively extract nested Award BIOS modules - self.parse_format(input_object=mod_path, extract_path=extract_folder(mod_path), - padding=padding + 8) + if award_bios_extract.check_format(): + award_bios_extract.parse_format() return True - - -if __name__ == '__main__': - AwardBiosExtract().run_utility() diff --git a/biosutilities/common/system.py b/biosutilities/common/system.py index 971e1be..bef62f9 100644 --- a/biosutilities/common/system.py +++ b/biosutilities/common/system.py @@ -30,7 +30,7 @@ def python_version() -> tuple: def printer(message: str | list | tuple | None = None, padding: int = 0, new_line: bool = True, - pause: bool = False, sep_char: str = ' ', strip: bool = False) -> None: + sep_char: str = ' ', strip: bool = False) -> None: """ 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) @@ -44,7 +44,4 @@ def printer(message: str | list | tuple | None = None, padding: int = 0, new_lin message_output += f'{line_new}{" " * padding}{line_text}' - if pause: - input(message_output) - else: - print(message_output) + print(message_output) diff --git a/biosutilities/common/templates.py b/biosutilities/common/templates.py index cddf4c1..224e17a 100644 --- a/biosutilities/common/templates.py +++ b/biosutilities/common/templates.py @@ -5,161 +5,23 @@ Copyright (C) 2022-2024 Plato Mavropoulos """ -import os -import sys - -from argparse import ArgumentParser, Namespace -from typing import Final - -from biosutilities import __version__ -from biosutilities.common.paths import (delete_dirs, extract_folder, is_access, is_dir, is_file, is_empty_dir, - path_files, path_name, path_parent, real_path, runtime_root) -from biosutilities.common.system import system_platform, python_version, printer -from biosutilities.common.texts import remove_quotes, to_boxed, to_ordinal - class BIOSUtility: """ Base utility class for BIOSUtilities """ TITLE: str = 'BIOS Utility' - ARGUMENTS: list[tuple[list[str], dict[str, str]]] = [] + def __init__(self, input_object: str | bytes | bytearray = b'', extract_path: str = '', padding: int = 0) -> None: + self.input_object: str | bytes | bytearray = input_object + self.extract_path: str = extract_path + self.padding: int = padding - MAX_FAT32_ITEMS: Final[int] = 65535 - - MIN_PYTHON_VER: Final[tuple[int, int]] = (3, 10) - - def __init__(self, arguments: list[str] | None = None) -> None: - self.title: str = f'{self.TITLE.strip()} v{__version__}' - - argparser: ArgumentParser = ArgumentParser(allow_abbrev=False) - - argparser.add_argument('paths', nargs='*') - argparser.add_argument('-e', '--auto-exit', help='skip user action prompts', action='store_true') - argparser.add_argument('-o', '--output-dir', help='output extraction directory') - - for argument in self.ARGUMENTS: - argparser.add_argument(*argument[0], **argument[1]) # type: ignore - - sys_argv: list[str] = arguments if isinstance(arguments, list) and arguments else sys.argv[1:] - - self.arguments: Namespace = argparser.parse_known_args(sys_argv)[0] - - self._input_files: list[str] = [] - - self._output_path: str = '' - - def run_utility(self, padding: int = 0) -> bool: - """ Run utility after checking for supported format """ - - self._check_sys_py() - - self._check_sys_os() - - self.show_version(padding=padding) - - self._setup_input_files(padding=padding) - - self._setup_output_dir(padding=padding) - - exit_code: int = len(self._input_files) - - for input_file in self._input_files: - input_name: str = path_name(in_path=input_file, limit=True) - - printer(message=input_name, padding=padding + 4) - - if not self.check_format(input_object=input_file): - printer(message='Error: This is not a supported format!', padding=padding + 8) - - continue - - extract_path: str = os.path.join(self._output_path, extract_folder(in_path=input_name)) - - if is_dir(in_path=extract_path): - for suffix in range(2, self.MAX_FAT32_ITEMS): - renamed_path: str = f'{os.path.normpath(extract_path)}_{to_ordinal(in_number=suffix)}' - - if not is_dir(in_path=renamed_path): - extract_path = renamed_path - - break - - if self.parse_format(input_object=input_file, extract_path=extract_path, padding=padding + 8): - exit_code -= 1 - - if is_empty_dir(in_path=extract_path): - delete_dirs(in_path=extract_path) - - printer(message='Done!\n' if not self.arguments.auto_exit else None, pause=not self.arguments.auto_exit) - - return exit_code == 0 - - def show_version(self, is_boxed: bool = True, padding: int = 0) -> None: - """ 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) - - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: - """ Process input object as a specific supported format """ - - raise NotImplementedError(f'Method "parse_format" not implemented at {__name__}') - - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input object is of specific supported format """ raise NotImplementedError(f'Method "check_format" not implemented at {__name__}') - def _setup_input_files(self, padding: int = 0) -> None: - self._input_files = [] + def parse_format(self) -> bool: + """ Process input object as a specific supported format """ - input_paths: list[str] = self.arguments.paths - - if not input_paths: - input_paths = [remove_quotes(in_text=input(f'\n{" " * padding}Enter input file or directory path: '))] - - for input_path in [input_path for input_path in input_paths if input_path]: - input_path_real: str = real_path(in_path=input_path) - - if is_dir(in_path=input_path_real): - for input_file in path_files(in_path=input_path_real): - if is_file(in_path=input_file) and is_access(in_path=input_file): - self._input_files.append(input_file) - elif is_file(in_path=input_path_real) and is_access(in_path=input_path_real): - self._input_files.append(input_path_real) - - def _setup_output_dir(self, padding: int = 0) -> None: - self._output_path = '' - - output_path: str = self.arguments.output_dir - - if not output_path: - output_path = remove_quotes(in_text=input(f'\n{" " * padding}Enter output directory path: ')) - - if not output_path and self._input_files: - output_path = str(path_parent(in_path=self._input_files[0])) - - if output_path and is_dir(in_path=output_path) and is_access(in_path=output_path): - self._output_path = output_path - else: - self._output_path = runtime_root() - - def _check_sys_py(self) -> None: - """ Check Python Version """ - - sys_py: tuple = python_version() - - if sys_py < self.MIN_PYTHON_VER: - min_py_str: str = '.'.join(map(str, self.MIN_PYTHON_VER)) - sys_py_str: str = '.'.join(map(str, sys_py[:2])) - - raise RuntimeError(f'Python >= {min_py_str} required, not {sys_py_str}') - - @staticmethod - def _check_sys_os() -> None: - """ Check OS Platform """ - - os_tag, is_win, is_lnx = system_platform() - - if not (is_win or is_lnx): - raise OSError(f'Unsupported operating system: {os_tag}') + raise NotImplementedError(f'Method "parse_format" not implemented at {__name__}') diff --git a/biosutilities/common/texts.py b/biosutilities/common/texts.py index 70c13f4..8d585bd 100644 --- a/biosutilities/common/texts.py +++ b/biosutilities/common/texts.py @@ -37,13 +37,11 @@ def to_ordinal(in_number: int) -> str: def file_to_bytes(in_object: str | bytes | bytearray) -> bytes: """ Get bytes from given buffer or file path """ - if not isinstance(in_object, (bytes, bytearray)): - with open(to_string(in_object=in_object), 'rb') as object_data: - object_bytes: bytes = object_data.read() - else: - object_bytes = in_object + if isinstance(in_object, str): + with open(in_object, 'rb') as object_fp: + return object_fp.read() - return object_bytes + return bytes(in_object) def bytes_to_hex(in_buffer: bytes, order: str, data_len: int, slice_len: int | None = None) -> str: diff --git a/biosutilities/dell_pfs_extract.py b/biosutilities/dell_pfs_extract.py index 65c1a33..48d4150 100644 --- a/biosutilities/dell_pfs_extract.py +++ b/biosutilities/dell_pfs_extract.py @@ -226,11 +226,6 @@ class DellPfsExtract(BIOSUtility): TITLE: str = 'Dell PFS Update Extractor' - ARGUMENTS: list[tuple[list[str], dict[str, str]]] = [ - (['-a', '--advanced'], {'help': 'extract signatures and metadata', 'action': 'store_true'}), - (['-s', '--structure'], {'help': 'show PFS structure information', 'action': 'store_true'}) - ] - PFS_HEAD_LEN: Final[int] = ctypes.sizeof(DellPfsHeader) PFS_FOOT_LEN: Final[int] = ctypes.sizeof(DellPfsFooter) PFS_INFO_LEN: Final[int] = ctypes.sizeof(DellPfsInfo) @@ -239,10 +234,17 @@ class DellPfsExtract(BIOSUtility): PFS_PFAT_LEN: Final[int] = ctypes.sizeof(DellPfsPfatMetadata) PFAT_HDR_LEN: Final[int] = ctypes.sizeof(IntelBiosGuardHeader) - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def __init__(self, input_object: str | bytes | bytearray = b'', extract_path: str = '', padding: int = 0, + advanced: bool = False, structure: bool = False) -> None: + super().__init__(input_object=input_object, extract_path=extract_path, padding=padding) + + self.advanced: bool = advanced + self.structure: bool = structure + + def check_format(self) -> bool: """ Check if input is Dell PFS/PKG image """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) if self._is_pfs_pkg(input_object=input_buffer): return True @@ -252,33 +254,34 @@ class DellPfsExtract(BIOSUtility): return False - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ Parse & Extract Dell PFS Update image """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) is_dell_pkg: bool = self._is_pfs_pkg(input_object=input_buffer) if is_dell_pkg: pfs_results: dict[str, bytes] = self._thinos_pkg_extract( - input_object=input_buffer, extract_path=extract_path) + input_object=input_buffer, extract_path=self.extract_path) else: - pfs_results = {path_stem(in_path=input_object) if isinstance(input_object, str) and is_file( - in_path=input_object) else 'Image': input_buffer} + pfs_results = {path_stem(in_path=self.input_object) if isinstance(self.input_object, str) and is_file( + in_path=self.input_object) else 'Image': input_buffer} # Parse each Dell PFS image contained in the input file 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 - pfs_path: str = os.path.join(extract_path, f'{pfs_index} {pfs_name}') if is_dell_pkg else extract_path + pfs_path: str = os.path.join( + self.extract_path, f'{pfs_index} {pfs_name}') if is_dell_pkg else self.extract_path # Parse each PFS ZLIB section for zlib_offset in self._get_section_offsets(buffer=pfs_buffer): # Call the PFS ZLIB section parser function self._pfs_section_parse(zlib_data=pfs_buffer, zlib_start=zlib_offset, extract_path=pfs_path, pfs_name=pfs_name, pfs_index=pfs_index, pfs_count=1, is_rec=False, - padding=padding) + padding=self.padding) return True @@ -496,7 +499,7 @@ class DellPfsExtract(BIOSUtility): """ Parse & Extract Dell PFS Volume """ # Show PFS Volume indicator - if self.arguments.structure: + if self.structure: printer(message='PFS Volume:', padding=padding) # Get PFS Header Structure values @@ -509,7 +512,7 @@ class DellPfsExtract(BIOSUtility): return # Critical error, abort # Show PFS Header Structure info - if self.arguments.structure: + if self.structure: printer(message='PFS Header:\n', padding=padding + 4) pfs_hdr.struct_print(padding=padding + 8) @@ -576,7 +579,7 @@ class DellPfsExtract(BIOSUtility): class_object=DellPfsInfo) # Show PFS Information Header Structure info - if self.arguments.structure: + if self.structure: printer(message='PFS Filename Information Header:\n', padding=padding + 4) filename_info_hdr.struct_print(padding=padding + 8) @@ -610,7 +613,7 @@ class DellPfsExtract(BIOSUtility): entry_name: str = safe_name(in_name=name_data.decode('utf-16').strip()) # Show PFS FileName Structure info - if self.arguments.structure: + if self.structure: printer(message='PFS FileName Entry:\n', padding=padding + 8) entry_info_mod.struct_print(name=entry_name, padding=padding + 12) @@ -639,7 +642,7 @@ class DellPfsExtract(BIOSUtility): entry_info: Any = ctypes_struct(buffer=entry_metadata, start_offset=0, class_object=DellPfsMetadata) # Show Nested PFS Metadata Structure info - if self.arguments.structure: + if self.structure: printer(message='PFS Metadata Information:\n', padding=padding + 4) entry_info.struct_print(padding=padding + 8) @@ -667,7 +670,7 @@ class DellPfsExtract(BIOSUtility): class_object=DellPfsInfo) # Show PFS Information Header Structure info - if self.arguments.structure: + if self.structure: printer(message='PFS Signature Information Header:\n', padding=padding + 4) signature_info_hdr.struct_print(padding=padding + 8) @@ -688,7 +691,7 @@ class DellPfsExtract(BIOSUtility): class_object=pfs_entry_struct) # Show PFS Information Header Structure info - if self.arguments.structure: + if self.structure: printer(message='PFS Information Entry:\n', padding=padding + 8) entry_hdr.struct_print(padding=padding + 12) @@ -703,7 +706,7 @@ class DellPfsExtract(BIOSUtility): sign_data_txt: str = f'{int.from_bytes(sign_data_raw, byteorder="little"):0{sign_size * 2}X}' - if self.arguments.structure: + if self.structure: printer(message='Signature Information:\n', padding=padding + 8) printer(message=f'Signature Size: 0x{sign_size:X}', padding=padding + 12, new_line=False) @@ -757,11 +760,11 @@ class DellPfsExtract(BIOSUtility): class_object=DellPfsHeader) # Show PFS Volume indicator - if self.arguments.structure: + if self.structure: printer(message='PFS Volume:', padding=padding + 4) # Show sub-PFS Header Structure Info - if self.arguments.structure: + if self.structure: printer(message='PFS Header:\n', padding=padding + 8) pfat_pfs_header.struct_print(padding=padding + 12) @@ -824,12 +827,12 @@ class DellPfsExtract(BIOSUtility): elif file_type == 'NAME_INFO': file_name = 'Filename Information' - if not self.arguments.advanced: + if not self.advanced: continue # Don't store Filename Information in non-advanced user mode elif file_type == 'SIG_INFO': file_name = 'Signature Information' - if not self.arguments.advanced: + if not self.advanced: continue # Don't store Signature Information in non-advanced user mode else: file_name = '' @@ -863,13 +866,13 @@ class DellPfsExtract(BIOSUtility): if file_data and not is_zlib: write_files.append([file_data, 'data']) # PFS Entry Data Payload - if file_data_sig and self.arguments.advanced: + if file_data_sig and self.advanced: write_files.append([file_data_sig, 'sign_data']) # PFS Entry Data Signature - if file_meta and (is_zlib or self.arguments.advanced): + if file_meta and (is_zlib or self.advanced): write_files.append([file_meta, 'meta']) # PFS Entry Metadata Payload - if file_meta_sig and self.arguments.advanced: + if file_meta_sig and self.advanced: write_files.append([file_meta_sig, 'sign_meta']) # PFS Entry Metadata Signature # Write/Extract PFS Entry files @@ -896,7 +899,7 @@ class DellPfsExtract(BIOSUtility): pfs_entry: Any = ctypes_struct(buffer=entry_buffer, start_offset=entry_start, class_object=entry_struct) # Show PFS Entry Structure info - if self.arguments.structure: + if self.structure: printer(message='PFS Entry:\n', padding=padding + 4) pfs_entry.struct_print(padding=padding + 8) @@ -981,7 +984,7 @@ class DellPfsExtract(BIOSUtility): pfat_entry_idx_ord: str = to_ordinal(in_number=pfat_entry_index) # Show sub-PFS PFAT Header Structure info - if self.arguments.structure: + if self.structure: printer(message=f'PFAT Block {pfat_entry_idx_ord} - Header:\n', padding=padding + 12) pfat_hdr.struct_print(padding=padding + 16) @@ -1012,7 +1015,7 @@ class DellPfsExtract(BIOSUtility): # Parse sub-PFS PFAT Signature, if applicable (only when PFAT Header > SFAM flag is set) if is_sfam: - if self.arguments.structure: + if self.structure: printer(message=f'PFAT Block {pfat_entry_idx_ord} - Signature:\n', padding=padding + 12) # Get sub-PFS PFAT Signature Size from Header pattern (not necessary for Dell PFS) @@ -1027,7 +1030,7 @@ class DellPfsExtract(BIOSUtility): # Get sub-PFS PFAT Signature Structure values pfat_sign_len: int = ami_pfat_extract.parse_bg_sign( 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.structure, padding=padding + 16) if not len(pfat_payload[pfat_payload_end:pfat_payload_end + pfat_sign_len] ) == pfat_sign_len == _pfat_sign_len: @@ -1035,7 +1038,7 @@ class DellPfsExtract(BIOSUtility): f'Size mismatch!', padding=padding + 12) # Show PFAT Script via BIOS Guard Script Tool - if self.arguments.structure: + if self.structure: printer(message=f'PFAT Block {pfat_entry_idx_ord} - Script:\n', padding=padding + 12) _ = ami_pfat_extract.parse_bg_script(script_data=pfat_script_data, padding=padding + 16) @@ -1066,7 +1069,7 @@ class DellPfsExtract(BIOSUtility): pfat_entry_adr = pfat_met.Address # Show sub-PFS PFAT Metadata Structure info - if self.arguments.structure: + if self.structure: printer(message=f'PFAT Block {pfat_entry_idx_ord} - Metadata:\n', padding=padding + 12) pfat_met.struct_print(padding=padding + 16) @@ -1134,7 +1137,7 @@ class DellPfsExtract(BIOSUtility): block_data_gap: int = block_start - block_start_exp if block_data_gap > 0: - if self.arguments.structure: + if self.structure: printer(message=f'Warning: Filled sub-PFS PFAT {block_index} data gap 0x{block_data_gap:X} ' f'[0x{block_start_exp:X}-0x{block_start:X}]!', padding=padding + 8) @@ -1206,7 +1209,7 @@ class DellPfsExtract(BIOSUtility): # Validate that a PFS Footer was parsed if pfs_ftr.Tag == b'PFS.FTR.': # Show PFS Footer Structure info - if self.arguments.structure: + if self.structure: printer(message='PFS Footer:\n', padding=padding + 4) pfs_ftr.struct_print(padding=padding + 8) @@ -1240,7 +1243,7 @@ class DellPfsExtract(BIOSUtility): return # Skip further processing for Signatures # Store Data/Metadata Payload (simpler Data/Metadata Extension for non-advanced users) - bin_ext: str = f'.{bin_name}.bin' if self.arguments.advanced else '.bin' + bin_ext: str = f'.{bin_name}.bin' if self.advanced else '.bin' # Some Data may be Text or XML files with useful information for non-advanced users final_data, file_ext = self._bin_is_text(buffer=bin_buff, file_type=bin_type, @@ -1265,7 +1268,7 @@ class DellPfsExtract(BIOSUtility): extension: str = '.bin' - if self.arguments.advanced: + if self.advanced: return buffer, extension buffer_text: str = '' @@ -1293,7 +1296,7 @@ class DellPfsExtract(BIOSUtility): # Show Model/PCR XML Information, if applicable # Metadata is shown at initial DellPfsMetadata analysis - if self.arguments.structure and buffer_text and not is_metadata: + if self.structure and buffer_text and not is_metadata: metadata_info_type: str = {".txt": "Model", ".xml": "PCR XML"}[extension] printer(message=f'PFS {metadata_info_type} Information:\n', padding=padding + 8) @@ -1302,7 +1305,3 @@ class DellPfsExtract(BIOSUtility): printer(message=line.strip('\r'), padding=padding + 12, new_line=False) return buffer_text or buffer, extension - - -if __name__ == '__main__': - DellPfsExtract().run_utility() diff --git a/biosutilities/fujitsu_sfx_extract.py b/biosutilities/fujitsu_sfx_extract.py index 4a61f9c..c6c7974 100644 --- a/biosutilities/fujitsu_sfx_extract.py +++ b/biosutilities/fujitsu_sfx_extract.py @@ -23,17 +23,17 @@ class FujitsuSfxExtract(BIOSUtility): TITLE: str = 'Fujitsu SFX BIOS Extractor' - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input is Fujitsu SFX image """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) return bool(PAT_FUJITSU_SFX.search(input_buffer)) - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ Parse & Extract Fujitsu SFX image """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) # Microsoft CAB Header XOR 0xFF match_cab: re.Match[bytes] | None = PAT_FUJITSU_SFX.search(input_buffer) @@ -41,7 +41,7 @@ class FujitsuSfxExtract(BIOSUtility): if not match_cab: return False - printer(message='Detected obfuscated CAB archive!', padding=padding) + printer(message='Detected obfuscated CAB archive!', padding=self.padding) # Microsoft CAB Header XOR 0xFF starts after "FjSfxBinay" signature cab_start: int = match_cab.start() + 0xA @@ -55,7 +55,7 @@ class FujitsuSfxExtract(BIOSUtility): # Perform XOR 0xFF and get actual CAB size cab_size ^= xor_size - printer(message='Removing obfuscation...', padding=padding + 4) + printer(message='Removing obfuscation...', padding=self.padding + 4) # Get BE XOR-ed CAB data cab_data: int = int.from_bytes(input_buffer[cab_start:cab_start + cab_size], byteorder='big') @@ -66,19 +66,19 @@ class FujitsuSfxExtract(BIOSUtility): # Perform XOR 0xFF and get actual CAB data raw_data: bytes = (cab_data ^ xor_data).to_bytes(cab_size, 'big') - printer(message='Extracting archive...', padding=padding + 4) + printer(message='Extracting archive...', padding=self.padding + 4) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) - cab_path: str = os.path.join(extract_path, 'FjSfxBinay.cab') + cab_path: str = os.path.join(self.extract_path, 'FjSfxBinay.cab') # Create temporary CAB archive with open(cab_path, 'wb') as cab_file_object: cab_file_object.write(raw_data) - 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', - padding=padding + 8, check=True): + if is_szip_supported(in_path=cab_path, padding=self.padding + 8, silent=False): + if szip_decompress(in_path=cab_path, out_path=self.extract_path, in_name='FjSfxBinay CAB', + padding=self.padding + 8, check=True): os.remove(cab_path) else: return False @@ -86,7 +86,3 @@ class FujitsuSfxExtract(BIOSUtility): return False return True - - -if __name__ == '__main__': - FujitsuSfxExtract().run_utility() diff --git a/biosutilities/fujitsu_upc_extract.py b/biosutilities/fujitsu_upc_extract.py index 325975a..a86e752 100644 --- a/biosutilities/fujitsu_upc_extract.py +++ b/biosutilities/fujitsu_upc_extract.py @@ -20,49 +20,45 @@ class FujitsuUpcExtract(BIOSUtility): TITLE: str = 'Fujitsu UPC BIOS Extractor' - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input is Fujitsu UPC image """ is_upc: bool = False - if isinstance(input_object, str) and is_file(in_path=input_object): - is_upc = path_suffixes(input_object)[-1].upper() == '.UPC' - elif isinstance(input_object, (bytes, bytearray)): + if isinstance(self.input_object, str) and is_file(in_path=self.input_object): + is_upc = path_suffixes(self.input_object)[-1].upper() == '.UPC' + elif isinstance(self.input_object, (bytes, bytearray)): is_upc = True if is_upc: - is_upc = is_efi_compressed(data=file_to_bytes(in_object=input_object)) + is_upc = is_efi_compressed(data=file_to_bytes(in_object=self.input_object)) return is_upc - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ Parse & Extract Fujitsu UPC image """ - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) - if isinstance(input_object, str) and is_file(in_path=input_object): - input_name: str = path_name(in_path=input_object) + if isinstance(self.input_object, str) and is_file(in_path=self.input_object): + input_name: str = path_name(in_path=self.input_object) - input_path: str = input_object + input_path: str = self.input_object if input_name.upper().endswith('.UPC'): input_name = input_name[:-4] else: input_name = 'Fujitsu_UPC_Image' - input_path = os.path.join(extract_path, f'{input_name}.UPC') + input_path = os.path.join(self.extract_path, f'{input_name}.UPC') with open(input_path, 'wb') as input_path_object: - input_path_object.write(file_to_bytes(in_object=input_object)) + input_path_object.write(file_to_bytes(in_object=self.input_object)) - output_path: str = os.path.join(extract_path, f'{input_name}.bin') + output_path: str = os.path.join(self.extract_path, f'{input_name}.bin') - efi_status: bool = 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=self.padding) - if input_path != input_object: + if input_path != self.input_object: os.remove(input_path) return efi_status - - -if __name__ == '__main__': - FujitsuUpcExtract().run_utility() diff --git a/biosutilities/insyde_ifd_extract.py b/biosutilities/insyde_ifd_extract.py index 66f867d..97f1ab0 100644 --- a/biosutilities/insyde_ifd_extract.py +++ b/biosutilities/insyde_ifd_extract.py @@ -80,10 +80,10 @@ class InsydeIfdExtract(BIOSUtility): # Get common ctypes Structure Sizes INS_IFL_LEN: Final[int] = ctypes.sizeof(IflashHeader) - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input is Insyde iFlash/iFdPacker Update image """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) if bool(self._insyde_iflash_detect(input_buffer=input_buffer)): return True @@ -93,18 +93,18 @@ class InsydeIfdExtract(BIOSUtility): return False - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ 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=self.input_object) - iflash_code: int = self._insyde_iflash_extract(input_buffer=input_buffer, extract_path=extract_path, - padding=padding) + iflash_code: int = self._insyde_iflash_extract(input_buffer=input_buffer, extract_path=self.extract_path, + padding=self.padding) - ifdpack_path: str = os.path.join(extract_path, 'Insyde iFdPacker SFX') + ifdpack_path: str = os.path.join(self.extract_path, 'Insyde iFdPacker SFX') ifdpack_code: int = self._insyde_packer_extract(input_buffer=input_buffer, extract_path=ifdpack_path, - padding=padding) + padding=self.padding) return (iflash_code and ifdpack_code) == 0 @@ -168,8 +168,7 @@ class InsydeIfdExtract(BIOSUtility): ifl_hdr.struct_print(padding=padding + 8) if img_val == [img_tag, img_ext]: - printer(message=f'Note: Detected new Insyde iFlash tag {img_tag}!', - padding=padding + 12, pause=not self.arguments.auto_exit) + printer(message=f'Note: Detected new Insyde iFlash tag {img_tag}!', padding=padding + 12) out_name: str = f'{img_name}.{img_ext}' @@ -231,16 +230,14 @@ class InsydeIfdExtract(BIOSUtility): for sfx_file in path_files(in_path=extract_path): if is_file(in_path=sfx_file) and is_access(in_path=sfx_file): - if self.check_format(input_object=sfx_file): + insyde_ifd_extract: InsydeIfdExtract = InsydeIfdExtract( + input_object=sfx_file, extract_path=extract_folder(sfx_file), padding=padding + 16) + + if insyde_ifd_extract.check_format(): printer(message=path_name(in_path=sfx_file), padding=padding + 12) - ifd_status: int = self.parse_format(input_object=sfx_file, extract_path=extract_folder(sfx_file), - padding=padding + 16) + ifd_status: int = insyde_ifd_extract.parse_format() exit_codes.append(0 if ifd_status else 1) return sum(exit_codes) - - -if __name__ == '__main__': - InsydeIfdExtract().run_utility() diff --git a/biosutilities/panasonic_bios_extract.py b/biosutilities/panasonic_bios_extract.py index bbae1e5..d71088d 100644 --- a/biosutilities/panasonic_bios_extract.py +++ b/biosutilities/panasonic_bios_extract.py @@ -39,10 +39,10 @@ class PanasonicBiosExtract(BIOSUtility): PAN_PE_DESC_UPD: Final[str] = 'BIOS UPDATE' - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input is Panasonic BIOS Package PE """ - pe_file: pefile.PE | None = ms_pe(in_file=input_object, silent=True) + pe_file: pefile.PE | None = ms_pe(in_file=self.input_object, silent=True) if not pe_file: return False @@ -53,26 +53,26 @@ class PanasonicBiosExtract(BIOSUtility): return True - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ 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=self.input_object, padding=self.padding) # type: ignore - upd_pe_name: str = self._panasonic_pkg_name(input_object=input_object) + upd_pe_name: str = self._panasonic_pkg_name(input_object=self.input_object) - printer(message=f'Panasonic BIOS Package > PE ({upd_pe_name})\n'.replace(' ()', ''), padding=padding) + printer(message=f'Panasonic BIOS Package > PE ({upd_pe_name})\n'.replace(' ()', ''), padding=self.padding) - ms_pe_info_show(pe_file=upd_pe_file, padding=padding + 4) + ms_pe_info_show(pe_file=upd_pe_file, padding=self.padding + 4) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) - upd_pe_path: str = self._panasonic_cab_extract(input_object=input_object, - extract_path=extract_path, padding=padding + 8) + upd_pe_path: str = self._panasonic_cab_extract(input_object=self.input_object, + extract_path=self.extract_path, padding=self.padding + 8) - upd_padding: int = padding + upd_padding: int = self.padding if upd_pe_path: - upd_padding = padding + 16 + upd_padding = self.padding + 16 upd_pe_name = self._panasonic_pkg_name(input_object=upd_pe_path) @@ -84,11 +84,11 @@ class PanasonicBiosExtract(BIOSUtility): os.remove(upd_pe_path) - is_upd_extracted: bool = self._panasonic_res_extract(pe_file=upd_pe_file, extract_path=extract_path, + is_upd_extracted: bool = self._panasonic_res_extract(pe_file=upd_pe_file, extract_path=self.extract_path, pe_name=upd_pe_name, padding=upd_padding + 8) if not is_upd_extracted: - 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=self.extract_path, pe_name=upd_pe_name, padding=upd_padding + 8) return is_upd_extracted @@ -184,14 +184,14 @@ class PanasonicBiosExtract(BIOSUtility): printer(message='Successful PE Resource extraction!', padding=padding + 4) - ami_pfat_extract: AmiPfatExtract = AmiPfatExtract() + pfat_dir: str = os.path.join(extract_path, res_tag) + + ami_pfat_extract: AmiPfatExtract = AmiPfatExtract( + input_object=res_raw, extract_path=pfat_dir, padding=padding + 8) # Detect & Unpack AMI BIOS Guard (PFAT) BIOS image - if ami_pfat_extract.check_format(input_object=res_raw): - pfat_dir: str = os.path.join(extract_path, res_tag) - - ami_pfat_extract.parse_format(input_object=res_raw, extract_path=pfat_dir, - padding=padding + 8) + if ami_pfat_extract.check_format(): + ami_pfat_extract.parse_format() else: if is_ms_pe(in_file=res_raw): res_ext: str = 'exe' @@ -242,7 +242,3 @@ class PanasonicBiosExtract(BIOSUtility): printer(message='Successful PE Data extraction!', padding=padding + 4) return bool(img_bin) - - -if __name__ == '__main__': - PanasonicBiosExtract().run_utility() diff --git a/biosutilities/phoenix_tdk_extract.py b/biosutilities/phoenix_tdk_extract.py index 77bb8e1..e59b6eb 100644 --- a/biosutilities/phoenix_tdk_extract.py +++ b/biosutilities/phoenix_tdk_extract.py @@ -103,23 +103,23 @@ class PhoenixTdkExtract(BIOSUtility): TDK_DUMMY_LEN: Final[int] = 0x200 - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input contains valid Phoenix TDK image """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) 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) -> bool: + def parse_format(self) -> bool: """ Parse & Extract Phoenix Tools Development Kit (TDK) Packer """ exit_code: int = 0 - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) - printer(message='Phoenix Tools Development Kit Packer', padding=padding) + printer(message='Phoenix Tools Development Kit Packer', padding=self.padding) base_off, pack_off = self._get_phoenix_tdk(in_buffer=input_buffer) @@ -127,13 +127,13 @@ class PhoenixTdkExtract(BIOSUtility): tdk_hdr: Any = ctypes_struct(buffer=input_buffer, start_offset=pack_off, class_object=PhoenixTdkHeader) # Print TDK Header structure info - printer(message='Phoenix TDK Header:\n', padding=padding + 4) + printer(message='Phoenix TDK Header:\n', padding=self.padding + 4) - tdk_hdr.struct_print(padding=padding + 8) + tdk_hdr.struct_print(padding=self.padding + 8) # Check if reported TDK Header Size matches manual TDK Entry Count calculation if tdk_hdr.Size != self.TDK_HDR_LEN + self.TDK_DUMMY_LEN + tdk_hdr.Count * self.TDK_MOD_LEN: - printer(message='Error: Phoenix TDK Header Size & Entry Count mismatch!\n', padding=padding + 8) + printer(message='Error: Phoenix TDK Header Size & Entry Count mismatch!\n', padding=self.padding + 8) exit_code = 1 @@ -147,16 +147,16 @@ class PhoenixTdkExtract(BIOSUtility): class_object=PhoenixTdkEntry, param_list=[base_off]) # Print TDK Entry structure info - printer(message=f'Phoenix TDK Entry ({entry_index + 1}/{tdk_hdr.Count}):\n', padding=padding + 8) + printer(message=f'Phoenix TDK Entry ({entry_index + 1}/{tdk_hdr.Count}):\n', padding=self.padding + 8) - tdk_mod.struct_print(padding=padding + 12) + tdk_mod.struct_print(padding=self.padding + 12) # Get TDK Entry raw data Offset (TDK Base + Entry Offset) mod_off: int = tdk_mod.get_offset() # Check if TDK Entry raw data Offset is valid if mod_off >= len(input_buffer): - printer(message='Error: Phoenix TDK Entry > Offset is out of bounds!\n', padding=padding + 12) + printer(message='Error: Phoenix TDK Entry > Offset is out of bounds!\n', padding=self.padding + 12) exit_code = 2 @@ -165,13 +165,13 @@ class PhoenixTdkExtract(BIOSUtility): # Check if TDK Entry raw data is complete if len(mod_data) != tdk_mod.Size: - printer(message='Error: Phoenix TDK Entry > Data is truncated!\n', padding=padding + 12) + printer(message='Error: Phoenix TDK Entry > Data is truncated!\n', padding=self.padding + 12) exit_code = 3 # Check if TDK Entry Reserved is present if tdk_mod.Reserved: - printer(message='Error: Phoenix TDK Entry > Reserved is not empty!\n', padding=padding + 12) + printer(message='Error: Phoenix TDK Entry > Reserved is not empty!\n', padding=self.padding + 12) exit_code = 4 @@ -181,7 +181,7 @@ class PhoenixTdkExtract(BIOSUtility): mod_data = lzma.LZMADecompressor().decompress(data=mod_data) except Exception as error: # pylint: disable=broad-except printer(message=f'Error: Phoenix TDK Entry > LZMA decompression failed: {error}!\n', - padding=padding + 12) + padding=self.padding + 12) exit_code = 5 @@ -189,7 +189,7 @@ class PhoenixTdkExtract(BIOSUtility): mod_name: str = tdk_mod.get_name() or f'Unknown_{entry_index + 1:02d}.bin' # Generate TDK Entry file data output path - mod_file: str = os.path.join(extract_path, safe_name(mod_name)) + mod_file: str = os.path.join(self.extract_path, safe_name(mod_name)) # Account for potential duplicate file names if is_file(in_path=mod_file): @@ -280,7 +280,3 @@ class PhoenixTdkExtract(BIOSUtility): tdk_base_off: int | None = self._get_tdk_base(in_buffer, tdk_pack_off) return tdk_base_off, tdk_pack_off - - -if __name__ == '__main__': - PhoenixTdkExtract().run_utility() diff --git a/biosutilities/portwell_efi_extract.py b/biosutilities/portwell_efi_extract.py index f32d843..2a7ad53 100644 --- a/biosutilities/portwell_efi_extract.py +++ b/biosutilities/portwell_efi_extract.py @@ -37,10 +37,10 @@ class PortwellEfiExtract(BIOSUtility): 4: 'SaveDmiData.efi' } - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input is Portwell EFI executable """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) try: pe_buffer: bytes = self._get_portwell_pe(in_buffer=input_buffer)[1] @@ -57,21 +57,21 @@ class PortwellEfiExtract(BIOSUtility): return False - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ Parse & Extract Portwell UEFI Unpacker """ # Initialize EFI Payload file chunks efi_files: list[bytes] = [] - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) pe_file, pe_data = self._get_portwell_pe(in_buffer=input_buffer) efi_title: str = self._get_unpacker_tag(input_buffer=input_buffer, pe_file=pe_file) - printer(message=efi_title, padding=padding) + printer(message=efi_title, padding=self.padding) # Split EFI Payload into file chunks efi_list: list[Match[bytes]] = list(PAT_PORTWELL_EFI.finditer(pe_data)) @@ -82,7 +82,7 @@ class PortwellEfiExtract(BIOSUtility): efi_files.append(pe_data[efi_bgn:efi_end]) - self._parse_efi_files(extract_path=extract_path, efi_files=efi_files, padding=padding) + self._parse_efi_files(extract_path=self.extract_path, efi_files=efi_files, padding=self.padding) return True @@ -145,8 +145,7 @@ class PortwellEfiExtract(BIOSUtility): printer(message=f'[{file_index}] {file_name}', padding=padding + 4) if file_name.startswith('Unknown_'): - printer(message=f'Note: Detected new Portwell EFI file ID {file_index}!', - padding=padding + 8, pause=not self.arguments.auto_exit) + printer(message=f'Note: Detected new Portwell EFI file ID {file_index}!', padding=padding + 8) # Store EFI file output path file_path: str = os.path.join(extract_path, safe_name(in_name=file_name)) @@ -166,7 +165,3 @@ class PortwellEfiExtract(BIOSUtility): # Successful decompression, delete compressed file if efi_decompress(in_path=comp_fname, out_path=file_path, padding=padding + 8): os.remove(comp_fname) - - -if __name__ == '__main__': - PortwellEfiExtract().run_utility() diff --git a/biosutilities/toshiba_com_extract.py b/biosutilities/toshiba_com_extract.py index 39bf2f5..a6b0031 100644 --- a/biosutilities/toshiba_com_extract.py +++ b/biosutilities/toshiba_com_extract.py @@ -23,29 +23,29 @@ class ToshibaComExtract(BIOSUtility): TITLE: str = 'Toshiba BIOS COM Extractor' - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input is Toshiba BIOS COM image """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) return bool(PAT_TOSHIBA_COM.search(input_buffer, 0, 0x100)) - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ Parse & Extract Toshiba BIOS COM image """ - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) - if isinstance(input_object, str) and is_file(in_path=input_object): - input_path: str = input_object + if isinstance(self.input_object, str) and is_file(in_path=self.input_object): + input_path: str = self.input_object else: - input_path = os.path.join(extract_path, 'toshiba_bios.com') + input_path = os.path.join(self.extract_path, 'toshiba_bios.com') with open(input_path, 'wb') as input_buffer: - input_buffer.write(file_to_bytes(in_object=input_object)) + input_buffer.write(file_to_bytes(in_object=self.input_object)) output_name: str = f'{safe_name(in_name=path_stem(in_path=input_path))}_extracted.bin' - output_path: str = os.path.join(extract_path, output_name) + output_path: str = os.path.join(self.extract_path, output_name) try: subprocess.run([comextract_path(), input_path, output_path], check=True, stdout=subprocess.DEVNULL) @@ -53,17 +53,14 @@ class ToshibaComExtract(BIOSUtility): if not is_file(in_path=output_path): raise FileNotFoundError('EXTRACTED_FILE_MISSING') 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=self.padding) return False - if input_path != input_object: + if input_path != self.input_object: os.remove(input_path) - printer(message='Successful extraction via ToshibaComExtractor!', padding=padding) + printer(message='Successful extraction via ToshibaComExtractor!', padding=self.padding) return True - - -if __name__ == '__main__': - ToshibaComExtract().run_utility() diff --git a/biosutilities/vaio_package_extract.py b/biosutilities/vaio_package_extract.py index 2bc78c6..70dba37 100644 --- a/biosutilities/vaio_package_extract.py +++ b/biosutilities/vaio_package_extract.py @@ -24,28 +24,30 @@ class VaioPackageExtract(BIOSUtility): TITLE: str = 'VAIO Packaging Manager Extractor' - def check_format(self, input_object: str | bytes | bytearray) -> bool: + def check_format(self) -> bool: """ Check if input is VAIO Packaging Manager """ - input_buffer: bytes = file_to_bytes(in_object=input_object) + input_buffer: bytes = file_to_bytes(in_object=self.input_object) return bool(PAT_VAIO_CFG.search(input_buffer)) - def parse_format(self, input_object: str | bytes | bytearray, extract_path: str, padding: int = 0) -> bool: + def parse_format(self) -> bool: """ Parse & Extract or Unlock VAIO Packaging Manager """ - input_buffer: bytes = file_to_bytes(input_object) + input_buffer: bytes = file_to_bytes(self.input_object) - input_name: str = os.path.basename(input_object) if isinstance(input_buffer, str) else 'VAIO_Package' + input_name: str = os.path.basename(self.input_object) if isinstance(input_buffer, str) else 'VAIO_Package' - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=self.extract_path, delete=True) - if self._vaio_cabinet(name=input_name, buffer=input_buffer, extract_path=extract_path, padding=padding) == 0: - printer(message='Successfully Extracted!', padding=padding) - elif self._vaio_unlock(name=input_name, buffer=input_buffer, extract_path=extract_path, padding=padding) == 0: - printer(message='Successfully Unlocked!', padding=padding) + if self._vaio_cabinet(name=input_name, buffer=input_buffer, extract_path=self.extract_path, + padding=self.padding) == 0: + printer(message='Successfully Extracted!', padding=self.padding) + elif self._vaio_unlock(name=input_name, buffer=input_buffer, extract_path=self.extract_path, + padding=self.padding) == 0: + printer(message='Successfully Unlocked!', padding=self.padding) else: - printer(message='Error: Failed to Extract or Unlock executable!', padding=padding) + printer(message='Error: Failed to Extract or Unlock executable!', padding=self.padding) return False @@ -174,7 +176,3 @@ class VaioPackageExtract(BIOSUtility): unl_file.write(input_buffer) return 0 - - -if __name__ == '__main__': - VaioPackageExtract().run_utility() diff --git a/main.py b/main.py index 1c5b958..d5b1d5d 100644 --- a/main.py +++ b/main.py @@ -5,8 +5,12 @@ Copyright (C) 2018-2024 Plato Mavropoulos """ +import os + from argparse import ArgumentParser, Namespace -from pathlib import Path +from typing import Any, Final + +from biosutilities import __version__ from biosutilities.ami_pfat_extract import AmiPfatExtract from biosutilities.ami_ucp_extract import AmiUcpExtract @@ -25,39 +29,149 @@ from biosutilities.portwell_efi_extract import PortwellEfiExtract from biosutilities.toshiba_com_extract import ToshibaComExtract from biosutilities.vaio_package_extract import VaioPackageExtract +from biosutilities.common.paths import (delete_dirs, extract_folder, is_access, is_dir, is_empty_dir, is_file, + path_files, path_name, path_parent, real_path, runtime_root) +from biosutilities.common.system import python_version, printer, system_platform +from biosutilities.common.texts import remove_quotes, to_boxed, to_ordinal + + +class BIOSUtilities: + """ Main BIOSUtilities class """ + + MAX_FAT32_ITEMS: Final[int] = 65535 + + MIN_PYTHON_VER: Final[tuple[int, int]] = (3, 10) + + def __init__(self) -> None: + main_argparser: ArgumentParser = ArgumentParser(allow_abbrev=False) + + main_argparser.add_argument('paths', nargs='*') + main_argparser.add_argument('-e', '--auto-exit', help='do not pause on exit', action='store_true') + main_argparser.add_argument('-o', '--output-dir', help='extraction directory') + + self.main_arguments: Namespace = main_argparser.parse_args() + + self._input_files: list[str] = [] + + self._output_path: str = '' + + def _setup_input_files(self, padding: int = 0) -> None: + self._input_files = [] + + input_paths: list[str] = self.main_arguments.paths + + if not input_paths: + input_paths = [remove_quotes(in_text=input(f'\n{" " * padding}Enter input file or directory path: '))] + + for input_path in [input_path for input_path in input_paths if input_path]: + input_path_real: str = real_path(in_path=input_path) + + if is_dir(in_path=input_path_real): + for input_file in path_files(in_path=input_path_real): + if is_file(in_path=input_file) and is_access(in_path=input_file): + self._input_files.append(input_file) + elif is_file(in_path=input_path_real) and is_access(in_path=input_path_real): + self._input_files.append(input_path_real) + + def _setup_output_dir(self, padding: int = 0) -> None: + self._output_path = '' + + output_path: str = self.main_arguments.output_dir + + if not output_path: + output_path = remove_quotes(in_text=input(f'\n{" " * padding}Enter output directory path: ')) + + if not output_path and self._input_files: + output_path = str(path_parent(in_path=self._input_files[0])) + + if output_path and is_dir(in_path=output_path) and is_access(in_path=output_path): + self._output_path = output_path + else: + self._output_path = runtime_root() + + def _check_sys_py(self) -> None: + """ Check Python Version """ + + sys_py: tuple = python_version() + + if sys_py < self.MIN_PYTHON_VER: + min_py_str: str = '.'.join(map(str, self.MIN_PYTHON_VER)) + sys_py_str: str = '.'.join(map(str, sys_py[:2])) + + raise RuntimeError(f'Python >= {min_py_str} required, not {sys_py_str}') + + @staticmethod + def _check_sys_os() -> None: + """ Check OS Platform """ + + os_tag, is_win, is_lnx = system_platform() + + if not (is_win or is_lnx): + raise OSError(f'Unsupported operating system: {os_tag}') + + def run_main(self, padding: int = 0) -> bool: + """ Run main """ + + self._check_sys_py() + + self._check_sys_os() + + self._setup_input_files(padding=padding) + + self._setup_output_dir(padding=padding) + + exit_code: int = len(self._input_files) + + utilities_classes: list[Any] = [ + AmiUcpExtract, AmiPfatExtract, InsydeIfdExtract, DellPfsExtract, PhoenixTdkExtract, PanasonicBiosExtract, + VaioPackageExtract, PortwellEfiExtract, ToshibaComExtract, FujitsuSfxExtract, FujitsuUpcExtract, + AwardBiosExtract, AppleEfiPkgExtract, AppleEfiPbzxExtract, AppleEfiIm4pSplit, AppleEfiIdentify + ] + + for input_file in self._input_files: + input_name: str = path_name(in_path=input_file, limit=True) + + printer(message=f'{input_name}\n', padding=padding) + + for utility_class in utilities_classes: + extract_path: str = os.path.join(self._output_path, extract_folder(in_path=input_name)) + + if is_dir(in_path=extract_path): + for suffix in range(2, self.MAX_FAT32_ITEMS): + renamed_path: str = f'{os.path.normpath(extract_path)}_{to_ordinal(in_number=suffix)}' + + if not is_dir(in_path=renamed_path): + extract_path = renamed_path + + break + + utility: Any = utility_class(input_object=input_file, extract_path=extract_path, padding=padding + 8) + + if not utility.check_format(): + continue + + printer(message=to_boxed(in_text=f'{utility.TITLE} v{__version__}'), + new_line=False, padding=padding + 4) + + is_parsed_format: bool = utility.parse_format() + + is_empty_output: bool = is_empty_dir(in_path=extract_path) + + if is_empty_output: + delete_dirs(in_path=extract_path) + + if is_parsed_format and not is_empty_output: + exit_code -= 1 + + break + + printer(message=None, new_line=False) + + if not self.main_arguments.auto_exit: + input('Press any key to exit...') + + return exit_code == 0 + if __name__ == '__main__': - main_argparser: ArgumentParser = ArgumentParser(allow_abbrev=False) - - main_argparser.add_argument('paths', nargs='+') - main_argparser.add_argument('-e', '--auto-exit', help='do not pause on exit', action='store_true') - main_argparser.add_argument('-o', '--output-dir', help='extraction directory') - - main_arguments: Namespace = main_argparser.parse_args() - - if main_arguments.output_dir: - output_folder: Path = Path(main_arguments.output_dir) - else: - output_folder = Path(main_arguments.paths[0]).parent - - util_arguments: list[str] = [*main_arguments.paths, '-e', '-o', str(output_folder.absolute())] - - AmiUcpExtract(arguments=util_arguments).run_utility() - AmiPfatExtract(arguments=util_arguments).run_utility() - InsydeIfdExtract(arguments=util_arguments).run_utility() - DellPfsExtract(arguments=util_arguments).run_utility() - PhoenixTdkExtract(arguments=util_arguments).run_utility() - PanasonicBiosExtract(arguments=util_arguments).run_utility() - VaioPackageExtract(arguments=util_arguments).run_utility() - PortwellEfiExtract(arguments=util_arguments).run_utility() - ToshibaComExtract(arguments=util_arguments).run_utility() - FujitsuSfxExtract(arguments=util_arguments).run_utility() - FujitsuUpcExtract(arguments=util_arguments).run_utility() - AwardBiosExtract(arguments=util_arguments).run_utility() - AppleEfiPkgExtract(arguments=util_arguments).run_utility() - AppleEfiPbzxExtract(arguments=util_arguments).run_utility() - AppleEfiIm4pSplit(arguments=util_arguments).run_utility() - AppleEfiIdentify(arguments=util_arguments).run_utility() - - if not main_arguments.auto_exit: - input('Press any key to exit...') + BIOSUtilities().run_main() diff --git a/requirements-dev.txt b/requirements-dev.txt index bd8229c..b0299a4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,2 @@ -mypy==1.12.0 +mypy==1.13.0 pylint==3.3.1