From f895fc208c28a4c12aaef74169081ac539ca0c7a Mon Sep 17 00:00:00 2001 From: Plato Mavropoulos Date: Wed, 30 Oct 2024 00:47:41 +0200 Subject: [PATCH] BIOSUtilities v24.10.29 Added graceful exception hanlding during "main" flow Improved and cleaned 7-Zip and EFI compression logic Improved too aggressive extraction directory handling Fixed input name detection at VAIO Package Extractor Fixed Intel IBIOSI detection at Apple EFI Identifier --- CHANGELOG | 8 ++ README.md | 2 + biosutilities/__init__.py | 2 +- biosutilities/ami_pfat_extract.py | 10 +-- biosutilities/ami_ucp_extract.py | 49 +++++------ biosutilities/apple_efi_id.py | 97 ++++++++++---------- biosutilities/apple_efi_im4p.py | 19 ++-- biosutilities/apple_efi_pbzx.py | 21 ++--- biosutilities/apple_efi_pkg.py | 98 +++++++++------------ biosutilities/award_bios_extract.py | 21 ++--- biosutilities/common/compression.py | 112 ++++++++++-------------- biosutilities/common/paths.py | 26 +++++- biosutilities/common/templates.py | 6 +- biosutilities/dell_pfs_extract.py | 24 +++-- biosutilities/fujitsu_sfx_extract.py | 21 ++--- biosutilities/fujitsu_upc_extract.py | 11 ++- biosutilities/insyde_ifd_extract.py | 21 ++--- biosutilities/panasonic_bios_extract.py | 10 +-- biosutilities/phoenix_tdk_extract.py | 23 +++-- biosutilities/portwell_efi_extract.py | 27 +++--- biosutilities/toshiba_com_extract.py | 36 +++----- biosutilities/vaio_package_extract.py | 21 ++--- main.py | 41 +++++---- pyproject.toml | 1 - 24 files changed, 327 insertions(+), 380 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0d208c5..695bed1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +24.10.29 + +Added graceful exception hanlding during "main" flow +Improved and cleaned 7-Zip and EFI compression logic +Improved too aggressive extraction directory handling +Fixed input name detection at VAIO Package Extractor +Fixed Intel IBIOSI detection at Apple EFI Identifier + 24.10.23 New "package" flow, arguments now provided during utility call (README) diff --git a/README.md b/README.md index eb7b5ff..5a64775 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,8 @@ No additional optional arguments are provided for this utility. To run the utility, you must have the following 3rd party tools at PATH or "external": * [7-Zip Console](https://www.7-zip.org/) (i.e. 7z.exe for Windows or 7zz for macOS or 7zz, 7zzs for Linux) +* [UEFIFind](https://github.com/LongSoft/UEFITool/) (i.e. [UEFIFind.exe for Windows or UEFIFind for Linux/macOS](https://github.com/LongSoft/UEFITool/releases)) +* [UEFIExtract](https://github.com/LongSoft/UEFITool/) (i.e. [UEFIExtract.exe for Windows or UEFIExtract for Linux/macOS](https://github.com/LongSoft/UEFITool/releases)) ### Apple EFI PBZX Extractor diff --git a/biosutilities/__init__.py b/biosutilities/__init__.py index ac2b219..76e7175 100644 --- a/biosutilities/__init__.py +++ b/biosutilities/__init__.py @@ -5,4 +5,4 @@ Copyright (C) 2018-2024 Plato Mavropoulos """ -__version__ = '24.10.23' +__version__ = '24.10.29' diff --git a/biosutilities/ami_pfat_extract.py b/biosutilities/ami_pfat_extract.py index 2324461..c8ab2b9 100644 --- a/biosutilities/ami_pfat_extract.py +++ b/biosutilities/ami_pfat_extract.py @@ -216,16 +216,12 @@ class AmiPfatExtract(BIOSUtility): def check_format(self) -> bool: """ Check if input is AMI BIOS Guard """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - - return bool(self._get_ami_pfat(input_object=input_buffer)) + return bool(self._get_ami_pfat(input_object=self.input_buffer)) def parse_format(self) -> bool: """ Process and store AMI BIOS Guard output file """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - - pfat_buffer: bytes = self._get_ami_pfat(input_object=input_buffer) + pfat_buffer: bytes = self._get_ami_pfat(input_object=self.input_buffer) file_path: str = '' @@ -235,7 +231,7 @@ class AmiPfatExtract(BIOSUtility): extract_name: str = path_name(in_path=self.extract_path).removesuffix(extract_suffix()) - make_dirs(in_path=self.extract_path, delete=True) + make_dirs(in_path=self.extract_path) block_all, block_off, file_count = self._parse_pfat_hdr(buffer=pfat_buffer, padding=self.padding) diff --git a/biosutilities/ami_ucp_extract.py b/biosutilities/ami_ucp_extract.py index 782bbb7..f54e5ac 100644 --- a/biosutilities/ami_ucp_extract.py +++ b/biosutilities/ami_ucp_extract.py @@ -17,12 +17,12 @@ from typing import Any, Final from biosutilities.common.checksums import checksum_16 from biosutilities.common.compression import efi_decompress, is_efi_compressed -from biosutilities.common.paths import agnostic_path, extract_folder, make_dirs, safe_name, safe_path +from biosutilities.common.paths import agnostic_path, delete_file, extract_folder, make_dirs, safe_name, safe_path from biosutilities.common.patterns import PAT_AMI_UCP, PAT_INTEL_ENGINE from biosutilities.common.structs import CHAR, ctypes_struct, UINT8, UINT16, UINT32 from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes, to_string +from biosutilities.common.texts import to_string from biosutilities.ami_pfat_extract import AmiPfatExtract from biosutilities.insyde_ifd_extract import InsydeIfdExtract @@ -259,23 +259,19 @@ class AmiUcpExtract(BIOSUtility): def check_format(self) -> bool: """ Check if input is AMI UCP image """ - buffer: bytes = file_to_bytes(in_object=self.input_object) - - return bool(self._get_ami_ucp(input_object=buffer)[0]) + return bool(self._get_ami_ucp()[0]) def parse_format(self) -> bool: """ Parse & Extract AMI UCP structures """ - 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=self.padding) - make_dirs(in_path=self.extract_path, delete=True) + make_dirs(in_path=self.extract_path) # Get best AMI UCP Pattern match based on @UAF|@HPU Size - ucp_buffer, ucp_tag = self._get_ami_ucp(input_object=input_buffer) + ucp_buffer, ucp_tag = self._get_ami_ucp() # Parse @UAF|@HPU Header Structure uaf_hdr: Any = ctypes_struct(buffer=ucp_buffer, start_offset=0, class_object=UafHeader) @@ -284,14 +280,17 @@ class AmiUcpExtract(BIOSUtility): uaf_hdr.struct_print(padding=self.padding + 8) - fake = struct.pack(' tuple[bytes, str]: + def _get_ami_ucp(self) -> tuple[bytes, str]: """ Get all input file AMI UCP patterns """ - buffer: bytes = file_to_bytes(in_object=input_object) - uaf_len_max: int = 0x0 # Length of largest detected @UAF|@HPU uaf_buf_bin: bytes = b'' # Buffer of largest detected @UAF|@HPU uaf_buf_tag: str = '@UAF' # Tag of largest detected @UAF|@HPU - for uaf in PAT_AMI_UCP.finditer(buffer): - uaf_len_cur: int = int.from_bytes(buffer[uaf.start() + 0x4:uaf.start() + 0x8], byteorder='little') + for uaf in PAT_AMI_UCP.finditer(self.input_buffer): + uaf_len_cur: int = int.from_bytes( + self.input_buffer[uaf.start() + 0x4:uaf.start() + 0x8], byteorder='little') if uaf_len_cur > uaf_len_max: uaf_len_max = uaf_len_cur - uaf_buf_bin = buffer[uaf.start():uaf.start() + uaf_len_max] + uaf_buf_bin = self.input_buffer[uaf.start():uaf.start() + uaf_len_max] uaf_buf_tag = uaf.group(0)[:4].decode('utf-8', 'ignore') @@ -431,7 +428,7 @@ class AmiUcpExtract(BIOSUtility): if uaf_tag in nal_dict: uaf_npath: str = safe_path(base_path=extract_path, user_paths=nal_dict[uaf_tag][0]) - make_dirs(in_path=uaf_npath, exist_ok=True) + make_dirs(in_path=uaf_npath) uaf_fname: str = safe_path(base_path=uaf_npath, user_paths=uaf_sname) else: @@ -484,7 +481,7 @@ class AmiUcpExtract(BIOSUtility): uaf_out.write(uaf_data_raw) # @UAF|@HPU Module EFI/Tiano Decompression - if is_comp and is_efi_compressed(data=uaf_data_raw, strict=False): + if is_comp and is_efi_compressed(in_object=uaf_data_raw, strict=False): # Decompressed @UAF|@HPU Module file path dec_fname: str = uaf_fname.replace('.temp', uaf_fext) @@ -492,7 +489,7 @@ class AmiUcpExtract(BIOSUtility): with open(dec_fname, 'rb') as dec: uaf_data_raw = dec.read() # Read back the @UAF|@HPU Module decompressed Raw data - os.remove(uaf_fname) # Successful decompression, delete compressed @UAF|@HPU Module file + delete_file(in_path=uaf_fname) # Successful decompression, delete compressed @UAF|@HPU Module file uaf_fname = dec_fname # Adjust @UAF|@HPU Module file path to the decompressed one @@ -536,7 +533,7 @@ class AmiUcpExtract(BIOSUtility): dis_mod.struct_print(padding=4) # Store @DIS Module Entry Info - os.remove(uaf_fname) # Delete @DIS Module binary, info exported as text + delete_file(in_path=uaf_fname) # Delete @DIS Module binary, info exported as text # Parse Name List @UAF|@HPU Module (@NAL) if len(uaf_data_raw) >= 5 and (uaf_tag, uaf_data_raw[0], uaf_data_raw[4]) == ('@NAL', 0x40, 0x3A): @@ -573,7 +570,7 @@ class AmiUcpExtract(BIOSUtility): if insyde_ifd_extract.check_format(): if insyde_ifd_extract.parse_format(): - os.remove(uaf_fname) # Delete raw nested Insyde IFD image after successful extraction + delete_file(in_path=uaf_fname) # Delete raw nested Insyde IFD image after successful extraction pfat_dir: str = os.path.join(extract_path, safe_name(in_name=uaf_name)) @@ -584,7 +581,7 @@ class AmiUcpExtract(BIOSUtility): if ami_pfat_extract.check_format(): ami_pfat_extract.parse_format() - os.remove(uaf_fname) # Delete raw PFAT BIOS image after successful extraction + delete_file(in_path=uaf_fname) # Delete raw PFAT BIOS image after successful extraction # Detect Intel Engine firmware image and show ME Analyzer advice if uaf_tag.startswith('@ME') and PAT_INTEL_ENGINE.search(uaf_data_raw): @@ -601,6 +598,6 @@ class AmiUcpExtract(BIOSUtility): if ami_ucp_extract.check_format(): ami_ucp_extract.parse_format() - os.remove(uaf_fname) # Delete raw nested AMI UCP image after successful extraction + delete_file(in_path=uaf_fname) # Delete raw nested AMI UCP image after successful extraction return nal_dict diff --git a/biosutilities/apple_efi_id.py b/biosutilities/apple_efi_id.py index 31c3329..3b95532 100644 --- a/biosutilities/apple_efi_id.py +++ b/biosutilities/apple_efi_id.py @@ -8,7 +8,6 @@ Copyright (C) 2018-2024 Plato Mavropoulos """ import ctypes -import logging import os import struct import subprocess @@ -19,12 +18,13 @@ from re import Match from typing import Any, Final from biosutilities.common.externals import uefiextract_path, uefifind_path -from biosutilities.common.paths import delete_dirs, delete_file, is_access, is_file, path_suffixes, runtime_root +from biosutilities.common.paths import delete_dirs, delete_file, is_file, make_dirs, path_suffixes, runtime_root from biosutilities.common.patterns import PAT_INTEL_IBIOSI, PAT_APPLE_ROM_VER from biosutilities.common.structs import CHAR, ctypes_struct, UINT8 from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes + +EFI_EXTENSIONS: Final[list[str]] = ['.fd', '.scap', '.im4p'] class IntelBiosId(ctypes.LittleEndianStructure): @@ -131,87 +131,82 @@ class AppleEfiIdentify(BIOSUtility): def check_format(self) -> bool: """ Check if input is Apple EFI image """ - 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'): + if isinstance(self.input_object, str) and is_file(in_path=self.input_object): + if path_suffixes(in_path=self.input_object)[-1].lower() not in EFI_EXTENSIONS: return False input_path: str = self.input_object - input_buffer: bytes = file_to_bytes(in_object=input_path) - elif isinstance(self.input_object, (bytes, bytearray)): + else: input_path = os.path.join(runtime_root(), 'APPLE_EFI_ID_INPUT_BUFFER_CHECK.tmp') - input_buffer = self.input_object with open(input_path, 'wb') as check_out: - check_out.write(input_buffer) - else: - return False + check_out.write(self.input_buffer) - try: - if PAT_INTEL_IBIOSI.search(input_buffer): - return True - - _ = subprocess.run([uefifind_path(), input_path, 'body', 'list', self.PAT_UEFIFIND], - check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + input_buffer: bytes = self.input_buffer + if PAT_INTEL_IBIOSI.search(input_buffer): return True - except Exception as error: # pylint: disable=broad-except - logging.debug('Error: Could not check if input is Apple EFI image: %s', error) - return False - finally: - if input_path != self.input_object: - delete_file(in_path=input_path) + uefifind_cmd: list[str] = [uefifind_path(), input_path, 'body', 'list', self.PAT_UEFIFIND] + + uefifind_res: subprocess.CompletedProcess[bytes] = subprocess.run( + uefifind_cmd, check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + if input_path != self.input_object: + delete_file(in_path=input_path) + + if uefifind_res.returncode == 0: + return True + + return False def parse_format(self) -> bool: """ Parse & Identify (or Rename) Apple EFI image """ - input_buffer: bytes = file_to_bytes(in_object=self.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') + input_path = os.path.join(runtime_root(), 'APPLE_EFI_ID_INPUT_BUFFER_PARSE.tmp') with open(input_path, 'wb') as parse_out: - parse_out.write(input_buffer) + parse_out.write(self.input_buffer) - bios_id_match: Match[bytes] | None = PAT_INTEL_IBIOSI.search(input_buffer) + bios_id_match: Match[bytes] | None = PAT_INTEL_IBIOSI.search(self.input_buffer) if bios_id_match: bios_id_res: str = f'0x{bios_id_match.start():X}' - bios_id_hdr: Any = ctypes_struct(buffer=input_buffer, start_offset=bios_id_match.start(), + bios_id_hdr: Any = ctypes_struct(buffer=self.input_buffer, start_offset=bios_id_match.start(), class_object=IntelBiosId) else: # The $IBIOSI$ pattern is within EFI compressed modules so we need to use UEFIFind and UEFIExtract - try: - bios_id_res = subprocess.check_output([uefifind_path(), input_path, 'body', 'list', - self.PAT_UEFIFIND], text=True)[:36] - # UEFIExtract must create its output folder itself - delete_dirs(in_path=self.extract_path) + bios_id_res = subprocess.check_output([uefifind_path(), input_path, 'body', 'list', + self.PAT_UEFIFIND], text=True)[:36] - _ = subprocess.run([uefiextract_path(), input_path, bios_id_res, '-o', self.extract_path, '-m', 'body'], - check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + make_dirs(in_path=self.extract_path) - with open(os.path.join(self.extract_path, 'body.bin'), 'rb') as raw_body: - body_buffer: bytes = raw_body.read() + uefiextract_dir: str = os.path.join(self.extract_path, 'uefiextract_temp') - # Detect decompressed $IBIOSI$ pattern - bios_id_match = PAT_INTEL_IBIOSI.search(body_buffer) + # UEFIExtract must create its output folder itself + delete_dirs(in_path=uefiextract_dir) - if not bios_id_match: - raise RuntimeError('Failed to detect decompressed $IBIOSI$ pattern!') + _ = subprocess.run([uefiextract_path(), input_path, bios_id_res, '-o', uefiextract_dir, '-m', 'body'], + check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - bios_id_hdr = ctypes_struct(buffer=body_buffer, start_offset=bios_id_match.start(), - class_object=IntelBiosId) + with open(os.path.join(uefiextract_dir, 'body.bin'), 'rb') as raw_body: + body_buffer: bytes = raw_body.read() - 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=self.padding) + # Detect decompressed $IBIOSI$ pattern + bios_id_match = PAT_INTEL_IBIOSI.search(body_buffer) - return False + if not bios_id_match: + raise RuntimeError('Failed to detect decompressed $IBIOSI$ pattern!') + + bios_id_hdr = ctypes_struct(buffer=body_buffer, start_offset=bios_id_match.start(), + class_object=IntelBiosId) + + delete_dirs(in_path=uefiextract_dir) # Successful UEFIExtract extraction, remove its output folder if not self.silent: printer(message=f'Detected Intel BIOS Info at {bios_id_res}\n', padding=self.padding) @@ -220,10 +215,10 @@ class AppleEfiIdentify(BIOSUtility): 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}' + self.efi_file_name = (f'{self.intel_bios_info["efi_name_id"]}_{zlib.adler32(self.input_buffer):08X}' f'{path_suffixes(in_path=input_path)[-1]}') - _ = self._apple_rom_version(input_buffer=input_buffer, padding=self.padding) + _ = self._apple_rom_version(input_buffer=self.input_buffer, padding=self.padding) if input_path != self.input_object: delete_file(in_path=input_path) diff --git a/biosutilities/apple_efi_im4p.py b/biosutilities/apple_efi_im4p.py index fb9ae59..d145117 100644 --- a/biosutilities/apple_efi_im4p.py +++ b/biosutilities/apple_efi_im4p.py @@ -16,7 +16,6 @@ from biosutilities.common.paths import make_dirs, path_stem from biosutilities.common.patterns import PAT_APPLE_IM4P, PAT_INTEL_FD from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes class AppleEfiIm4pSplit(BIOSUtility): @@ -33,9 +32,7 @@ class AppleEfiIm4pSplit(BIOSUtility): if isinstance(self.input_object, str) and not self.input_object.lower().endswith('.im4p'): return False - 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): + if PAT_APPLE_IM4P.search(self.input_buffer) and PAT_INTEL_FD.search(self.input_buffer): return True return False @@ -45,12 +42,10 @@ class AppleEfiIm4pSplit(BIOSUtility): parse_success: bool = True - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - - make_dirs(in_path=self.extract_path, delete=True) + make_dirs(in_path=self.extract_path) # Detect IM4P EFI pattern - im4p_match: Match[bytes] | None = PAT_APPLE_IM4P.search(input_buffer) + im4p_match: Match[bytes] | None = PAT_APPLE_IM4P.search(self.input_buffer) if not im4p_match: return False @@ -59,14 +54,14 @@ class AppleEfiIm4pSplit(BIOSUtility): # However, _MEFIBIN is not required for splitting SPI images due to Intel Flash Descriptor Components Density. # IM4P mefi payload start offset - mefi_data_bgn: int = im4p_match.start() + input_buffer[im4p_match.start() - 0x1] + mefi_data_bgn: int = im4p_match.start() + self.input_buffer[im4p_match.start() - 0x1] # IM4P mefi payload size - mefi_data_len: int = int.from_bytes(input_buffer[im4p_match.end() + 0x5:im4p_match.end() + 0x9], + mefi_data_len: int = int.from_bytes(self.input_buffer[im4p_match.end() + 0x5:im4p_match.end() + 0x9], byteorder='big') # Check if mefi is followed by _MEFIBIN - mefibin_exist: bool = input_buffer[mefi_data_bgn:mefi_data_bgn + 0x8] == b'_MEFIBIN' + mefibin_exist: bool = self.input_buffer[mefi_data_bgn:mefi_data_bgn + 0x8] == b'_MEFIBIN' # Actual multi EFI payloads start after _MEFIBIN efi_data_bgn: int = mefi_data_bgn + 0x100 if mefibin_exist else mefi_data_bgn @@ -75,7 +70,7 @@ class AppleEfiIm4pSplit(BIOSUtility): efi_data_len: int = mefi_data_len - 0x100 if mefibin_exist else mefi_data_len # Adjust input file buffer to actual multi EFI payloads data - input_buffer = input_buffer[efi_data_bgn:efi_data_bgn + efi_data_len] + input_buffer = self.input_buffer[efi_data_bgn:efi_data_bgn + efi_data_len] # Parse Intel Flash Descriptor pattern matches for ifd in PAT_INTEL_FD.finditer(input_buffer): diff --git a/biosutilities/apple_efi_pbzx.py b/biosutilities/apple_efi_pbzx.py index 78bf196..82867ae 100644 --- a/biosutilities/apple_efi_pbzx.py +++ b/biosutilities/apple_efi_pbzx.py @@ -15,12 +15,11 @@ import os from typing import Any, Final from biosutilities.common.compression import is_szip_supported, szip_decompress -from biosutilities.common.paths import make_dirs, path_stem +from biosutilities.common.paths import delete_file, make_dirs, path_stem from biosutilities.common.patterns import PAT_APPLE_PBZX from biosutilities.common.structs import ctypes_struct, UINT32 from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes class PbzxChunk(ctypes.BigEndianStructure): @@ -54,16 +53,12 @@ class AppleEfiPbzxExtract(BIOSUtility): def check_format(self) -> bool: """ Check if input is Apple PBZX image """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - - return bool(PAT_APPLE_PBZX.search(input_buffer, 0, 4)) + return bool(PAT_APPLE_PBZX.search(self.input_buffer, 0, 4)) def parse_format(self) -> bool: """ Parse & Extract Apple PBZX image """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - - make_dirs(in_path=self.extract_path, delete=True) + make_dirs(in_path=self.extract_path) cpio_bin: bytes = b'' # Initialize PBZX > CPIO Buffer @@ -71,8 +66,8 @@ class AppleEfiPbzxExtract(BIOSUtility): chunk_off: int = 0xC # First PBZX Chunk starts at 0xC - while chunk_off < len(input_buffer): - chunk_hdr: Any = ctypes_struct(buffer=input_buffer, start_offset=chunk_off, class_object=PbzxChunk) + while chunk_off < len(self.input_buffer): + chunk_hdr: Any = ctypes_struct(buffer=self.input_buffer, start_offset=chunk_off, class_object=PbzxChunk) printer(message=f'PBZX Chunk at 0x{chunk_off:08X}\n', padding=self.padding) @@ -84,7 +79,7 @@ class AppleEfiPbzxExtract(BIOSUtility): # To avoid a potential infinite loop, double-check Compressed Size comp_end: int = comp_bgn + max(chunk_hdr.CompSize, self.PBZX_CHUNK_HDR_LEN) - comp_bin: bytes = input_buffer[comp_bgn:comp_end] + comp_bin: bytes = self.input_buffer[comp_bgn:comp_end] try: # Attempt XZ decompression, if applicable to Chunk data @@ -117,10 +112,10 @@ class AppleEfiPbzxExtract(BIOSUtility): cpio_object.write(cpio_bin) # Decompress PBZX > CPIO archive with 7-Zip - if is_szip_supported(in_path=cpio_path, padding=self.padding, args=['-tCPIO'], silent=False): + if is_szip_supported(in_path=cpio_path, args=['-tCPIO']): 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 + delete_file(in_path=cpio_path) # Successful extraction, delete PBZX > CPIO archive else: return False else: diff --git a/biosutilities/apple_efi_pkg.py b/biosutilities/apple_efi_pkg.py index 733be10..6324ee1 100644 --- a/biosutilities/apple_efi_pkg.py +++ b/biosutilities/apple_efi_pkg.py @@ -10,13 +10,12 @@ Copyright (C) 2019-2024 Plato Mavropoulos import os from biosutilities.common.compression import is_szip_supported, szip_decompress -from biosutilities.common.paths import (copy_file, delete_dirs, extract_folder, is_access, is_file, make_dirs, - path_files, path_name, path_parent, path_suffixes, runtime_root) +from biosutilities.common.paths import (copy_file, delete_dirs, delete_file, extract_folder, is_access, is_dir, + is_file, make_dirs, path_files, path_name, path_suffixes, runtime_root) from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes -from biosutilities.apple_efi_id import AppleEfiIdentify +from biosutilities.apple_efi_id import AppleEfiIdentify, EFI_EXTENSIONS from biosutilities.apple_efi_im4p import AppleEfiIm4pSplit from biosutilities.apple_efi_pbzx import AppleEfiPbzxExtract @@ -31,15 +30,13 @@ class AppleEfiPkgExtract(BIOSUtility): is_apple_efi_pkg: bool = False - input_buffer: bytes = file_to_bytes(in_object=self.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') with open(input_path, 'wb') as input_path_object: - input_path_object.write(input_buffer) + input_path_object.write(self.input_buffer) for pkg_type in ('XAR', 'TAR', 'DMG'): if is_szip_supported(in_path=input_path, args=[f'-t{pkg_type}:s0']): @@ -48,7 +45,7 @@ class AppleEfiPkgExtract(BIOSUtility): break if input_path != self.input_object: - os.remove(input_path) + delete_file(in_path=input_path) return is_apple_efi_pkg @@ -61,16 +58,14 @@ class AppleEfiPkgExtract(BIOSUtility): 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=self.input_object)) - - make_dirs(in_path=self.extract_path, delete=True) + input_path_object.write(self.input_buffer) 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=self.padding, args=[f'-t{pkg_type}']): + if is_szip_supported(in_path=input_path, 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 @@ -78,52 +73,52 @@ class AppleEfiPkgExtract(BIOSUtility): return False if input_path != self.input_object: - os.remove(input_path) + delete_file(in_path=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=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) + self._pbzx_zip(input_path=work_file, padding=self.padding + 4) + self._gzip_cpio(input_path=work_file, padding=self.padding + 4) + self._dmg_zip(input_path=work_file, padding=self.padding + 4) + self._xar_gzip(input_path=work_file, padding=self.padding + 4) delete_dirs(in_path=working_dir) return True - def _xar_gzip(self, input_path: str, extract_path: str, padding: int = 0) -> None: + def _xar_gzip(self, input_path: str, padding: int = 0) -> None: """ XAR/TAR > GZIP """ for pkg_type in ('XAR', 'TAR'): - if is_szip_supported(in_path=input_path, padding=padding, args=[f'-t{pkg_type}']): + if is_szip_supported(in_path=input_path, args=[f'-t{pkg_type}']): pkg_path: str = extract_folder(in_path=input_path, suffix=f'_{pkg_type.lower()}_gzip') if szip_decompress(in_path=input_path, out_path=pkg_path, in_name=pkg_type, padding=padding, args=[f'-t{pkg_type}']): for pkg_file in path_files(in_path=pkg_path): if is_file(in_path=pkg_file) and is_access(in_path=pkg_file): - self._gzip_cpio(input_path=pkg_file, extract_path=extract_path, padding=padding + 4) + self._gzip_cpio(input_path=pkg_file, padding=padding + 4) break - def _dmg_zip(self, input_path: str, extract_path: str, padding: int = 0) -> None: + def _dmg_zip(self, input_path: str, padding: int = 0) -> None: """ DMG > ZIP """ - if is_szip_supported(in_path=input_path, padding=padding, args=['-tDMG']): + if is_szip_supported(in_path=input_path, args=['-tDMG']): dmg_path: str = extract_folder(in_path=input_path, suffix='_dmg_zip') if szip_decompress(in_path=input_path, out_path=dmg_path, in_name='DMG', padding=padding, args=None): for dmg_file in path_files(in_path=dmg_path): if is_file(in_path=dmg_file) and is_access(in_path=dmg_file): - if is_szip_supported(in_path=dmg_file, padding=padding + 4, args=['-tZIP']): + if is_szip_supported(in_path=dmg_file, args=['-tZIP']): zip_path: str = extract_folder(in_path=dmg_file) if szip_decompress(in_path=dmg_file, out_path=zip_path, in_name='ZIP', padding=padding + 4, args=['-tZIP']): for zip_file in path_files(in_path=zip_path): - self._im4p_id(input_path=zip_file, output_path=extract_path, padding=padding + 8) + self._im4p_id(input_path=zip_file, padding=padding + 8) - def _pbzx_zip(self, input_path: str, extract_path: str, padding: int = 0) -> None: + def _pbzx_zip(self, input_path: str, padding: int = 0) -> None: """ PBZX > ZIP """ pbzx_path: str = extract_folder(in_path=input_path, suffix='_pbzx_zip') @@ -139,51 +134,48 @@ class AppleEfiPkgExtract(BIOSUtility): for pbzx_file in path_files(in_path=pbzx_path): if is_file(in_path=pbzx_file) and is_access(in_path=pbzx_file): - if is_szip_supported(in_path=pbzx_file, padding=padding + 4, args=['-tZIP']): + if is_szip_supported(in_path=pbzx_file, args=['-tZIP']): zip_path: str = extract_folder(in_path=pbzx_file) if szip_decompress(in_path=pbzx_file, out_path=zip_path, in_name='ZIP', padding=padding + 4, args=['-tZIP']): for zip_file in path_files(in_path=zip_path): - self._im4p_id(input_path=zip_file, output_path=extract_path, padding=padding + 8) + self._im4p_id(input_path=zip_file, padding=padding + 8) - def _gzip_cpio(self, input_path: str, extract_path: str, padding: int = 0) -> None: + def _gzip_cpio(self, input_path: str, padding: int = 0) -> None: """ GZIP > CPIO """ - if is_szip_supported(in_path=input_path, padding=padding, args=['-tGZIP']): + if is_szip_supported(in_path=input_path, args=['-tGZIP']): gzip_path: str = extract_folder(in_path=input_path, suffix='_gzip_cpio') if szip_decompress(in_path=input_path, out_path=gzip_path, in_name='GZIP', padding=padding, args=['-tGZIP']): for gzip_file in path_files(in_path=gzip_path): if is_file(in_path=gzip_file) and is_access(in_path=gzip_file): - if is_szip_supported(in_path=gzip_file, padding=padding + 4, args=['-tCPIO']): + if is_szip_supported(in_path=gzip_file, args=['-tCPIO']): cpio_path: str = extract_folder(in_path=gzip_file) if szip_decompress(in_path=gzip_file, out_path=cpio_path, in_name='CPIO', padding=padding + 4, args=['-tCPIO']): for cpio_file in path_files(in_path=cpio_path): - self._im4p_id(input_path=cpio_file, output_path=extract_path, padding=padding + 8) + self._im4p_id(input_path=cpio_file, padding=padding + 8) - @staticmethod - def _im4p_id(input_path: str, output_path: str, padding: int = 0) -> None: - """ Split IM4P (if applicable), identify and rename EFI """ + def _im4p_id(self, input_path: str, padding: int = 0) -> None: + """ Split IM4P (if applicable), identify and copy EFI """ + + if path_suffixes(in_path=input_path)[-1].lower() not in EFI_EXTENSIONS: + return None if not (is_file(in_path=input_path) and is_access(in_path=input_path)): return None - if path_suffixes(in_path=input_path)[-1].lower() not in ('.fd', '.scap', '.im4p'): - return None - - if not AppleEfiIdentify(input_object=input_path).check_format(): - return None - - input_name: str = path_name(in_path=input_path) - - printer(message=input_name, padding=padding) - working_dir: str = extract_folder(in_path=input_path) + if not AppleEfiIdentify(input_object=input_path, extract_path=working_dir).check_format(): + return None + + printer(message=path_name(in_path=input_path), padding=padding) + im4p_module: AppleEfiIm4pSplit = AppleEfiIm4pSplit( input_object=input_path, extract_path=working_dir, padding=padding + 8) @@ -191,27 +183,19 @@ class AppleEfiPkgExtract(BIOSUtility): printer(message=f'Splitting IM4P via {im4p_module.TITLE}', padding=padding + 4) im4p_module.parse_format() - else: - make_dirs(in_path=working_dir, delete=True) - copy_file(in_path=input_path, out_path=working_dir, metadata=True) - - for efi_source in path_files(in_path=working_dir): - if is_file(in_path=efi_source) and is_access(in_path=efi_source): + for efi_path in path_files(in_path=working_dir) if is_dir(in_path=working_dir) else [input_path]: + if is_file(in_path=efi_path) and is_access(in_path=efi_path): efi_id_module: AppleEfiIdentify = AppleEfiIdentify( - input_object=efi_source, extract_path=extract_folder(in_path=efi_source), padding=padding + 8) + input_object=efi_path, extract_path=extract_folder(in_path=efi_path), padding=padding + 8) 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(): - efi_dest: str = os.path.join(path_parent(in_path=efi_source), efi_id_module.efi_file_name) + efi_path_final: str = os.path.join(self.extract_path, efi_id_module.efi_file_name) - os.replace(efi_source, efi_dest) - - for efi_final in path_files(in_path=working_dir): - if is_file(in_path=efi_final) and is_access(in_path=efi_final): - copy_file(in_path=efi_final, out_path=output_path, metadata=True) + copy_file(in_path=efi_path, out_path=efi_path_final, metadata=True) delete_dirs(in_path=working_dir) diff --git a/biosutilities/award_bios_extract.py b/biosutilities/award_bios_extract.py index 08094ea..e44c131 100644 --- a/biosutilities/award_bios_extract.py +++ b/biosutilities/award_bios_extract.py @@ -10,11 +10,10 @@ Copyright (C) 2018-2024 Plato Mavropoulos import os from biosutilities.common.compression import szip_decompress -from biosutilities.common.paths import clear_readonly, extract_folder, is_file, make_dirs, safe_name +from biosutilities.common.paths import clear_readonly, delete_file, extract_folder, is_file, make_dirs, safe_name from biosutilities.common.patterns import PAT_AWARD_LZH from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes class AwardBiosExtract(BIOSUtility): @@ -25,18 +24,14 @@ class AwardBiosExtract(BIOSUtility): def check_format(self) -> bool: """ Check if input is Award BIOS image """ - in_buffer: bytes = file_to_bytes(in_object=self.input_object) - - return bool(PAT_AWARD_LZH.search(in_buffer)) + return bool(PAT_AWARD_LZH.search(self.input_buffer)) def parse_format(self) -> bool: """ Parse & Extract Award BIOS image """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) + make_dirs(in_path=self.extract_path) - make_dirs(in_path=self.extract_path, delete=True) - - for lzh_match in PAT_AWARD_LZH.finditer(input_buffer): + for lzh_match in PAT_AWARD_LZH.finditer(self.input_buffer): lzh_type: str = lzh_match.group(0).decode('utf-8') lzh_text: str = f'LZH-{lzh_type.strip("-").upper()}' @@ -44,11 +39,11 @@ class AwardBiosExtract(BIOSUtility): lzh_bgn: int = lzh_match.start() mod_bgn: int = lzh_bgn - 0x2 - hdr_len: int = input_buffer[mod_bgn] - mod_len: int = int.from_bytes(input_buffer[mod_bgn + 0x7:mod_bgn + 0xB], byteorder='little') + hdr_len: int = self.input_buffer[mod_bgn] + mod_len: int = int.from_bytes(self.input_buffer[mod_bgn + 0x7:mod_bgn + 0xB], byteorder='little') mod_end: int = lzh_bgn + hdr_len + mod_len - mod_bin: bytes = input_buffer[mod_bgn:mod_end] + mod_bin: bytes = self.input_buffer[mod_bgn:mod_end] if len(mod_bin) != 0x2 + hdr_len + mod_len: printer(message=f'Error: Skipped incomplete LZH stream at 0x{mod_bgn:X}!', @@ -78,7 +73,7 @@ class AwardBiosExtract(BIOSUtility): if is_file(in_path=mod_path): clear_readonly(in_path=lzh_path) - os.remove(lzh_path) # Successful extraction, delete LZH archive + delete_file(in_path=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) diff --git a/biosutilities/common/compression.py b/biosutilities/common/compression.py index 0f9a635..ae48e86 100644 --- a/biosutilities/common/compression.py +++ b/biosutilities/common/compression.py @@ -5,18 +5,18 @@ Copyright (C) 2022-2024 Plato Mavropoulos """ -import os import subprocess from typing import Final from biosutilities.common.externals import szip_path, tiano_path -from biosutilities.common.paths import is_dir, is_empty_dir +from biosutilities.common.paths import is_access, is_dir, is_file, is_empty_dir, path_size from biosutilities.common.system import printer +from biosutilities.common.texts import file_to_bytes # 7-Zip switches to auto rename, ignore passwords, ignore prompts, ignore wildcards, # eliminate root duplication, set UTF-8 charset, suppress stdout, suppress stderr, -# suppress progress, disable headers, disable progress, disable output logs +# suppress progress, disable headers, disable progress, disable output logging SZIP_COMMON: Final[list[str]] = ['-aou', '-p', '-y', '-spd', '-spe', '-sccUTF-8', '-bso0', '-bse0', '-bsp0', '-ba', '-bd', '-bb0'] @@ -24,13 +24,6 @@ SZIP_COMMON: Final[list[str]] = ['-aou', '-p', '-y', '-spd', '-spe', '-sccUTF-8' SZIP_SUCCESS: Final[list[int]] = [0, 1] -def szip_code_assert(exit_code: int) -> None: - """ Check 7-Zip bad exit codes (0 OK, 1 Warning) """ - - if exit_code not in SZIP_SUCCESS: - raise ValueError(f'Bad exit code: {exit_code}') - - def szip_switches(in_switches: list[str]) -> list[str]: """ Generate 7-Zip command line switches """ @@ -46,77 +39,73 @@ def szip_switches(in_switches: list[str]) -> list[str]: return [*set(common_switches + in_switches), '--'] -def is_szip_supported(in_path: str, padding: int = 0, args: list | None = None, silent: bool = True) -> bool: +def is_szip_successful(exit_code: int) -> bool: + """ Check 7-Zip success exit codes """ + + if exit_code in SZIP_SUCCESS: + return True + + return False + + +def is_szip_supported(in_path: str, args: list | None = None) -> bool: """ Check if file is 7-Zip supported """ - try: - if args is None: - args = [] + szip_a: list[str] = [] if args is None else args - szip_c: list[str] = [szip_path(), 't', *szip_switches(in_switches=[*args]), in_path] + szip_c: list[str] = [szip_path(), 't', *szip_switches(in_switches=[*szip_a]), in_path] - szip_t: subprocess.CompletedProcess[bytes] = subprocess.run(szip_c, check=False) + szip_t: subprocess.CompletedProcess[bytes] = subprocess.run(szip_c, check=False, stdout=subprocess.DEVNULL) - szip_code_assert(exit_code=szip_t.returncode) - except Exception as error: # pylint: disable=broad-except - if not silent: - printer(message=f'Error: 7-Zip could not check support for file {in_path}: {error}!', padding=padding) - - return False - - return True + return is_szip_successful(exit_code=szip_t.returncode) def szip_decompress(in_path: str, out_path: str, in_name: str = 'archive', padding: int = 0, args: list | None = None, check: bool = False, silent: bool = False) -> bool: """ Archive decompression via 7-Zip """ - try: - if args is None: - args = [] + szip_a: list[str] = [] if args is None else args - szip_c: list[str] = [szip_path(), 'x', *szip_switches(in_switches=[*args, f'-o{out_path}']), in_path] + szip_c: list[str] = [szip_path(), 'x', *szip_switches(in_switches=[*szip_a, f'-o{out_path}']), in_path] - szip_x: subprocess.CompletedProcess[bytes] = subprocess.run(szip_c, check=False) + szip_x: subprocess.CompletedProcess[bytes] = subprocess.run(szip_c, check=False, stdout=subprocess.DEVNULL) - if check: - szip_code_assert(exit_code=szip_x.returncode) + szip_s: bool = is_szip_successful(exit_code=szip_x.returncode) if check else True - if not (is_dir(in_path=out_path) and not is_empty_dir(in_path=out_path)): - raise OSError(f'Extraction directory is empty or missing: {out_path}') - except Exception as error: # pylint: disable=broad-except + if szip_s and is_dir(in_path=out_path) and not is_empty_dir(in_path=out_path): if not silent: - printer(message=f'Error: 7-Zip could not extract {in_name} file {in_path}: {error}!', padding=padding) + printer(message=f'Successful {in_name} decompression via 7-Zip!', padding=padding) - return False + return True - if not silent: - printer(message=f'Successful {in_name} decompression via 7-Zip!', padding=padding) - - return True + return False -def efi_compress_sizes(data: bytes | bytearray) -> tuple[int, int]: - """ Get EFI compression sizes """ +def efi_header_info(in_object: str | bytes | bytearray) -> dict[str, int]: + """ Get EFI compression sizes from header """ - size_compress: int = int.from_bytes(data[0x0:0x4], byteorder='little') + efi_data: bytes = file_to_bytes(in_object=in_object) - size_original: int = int.from_bytes(data[0x4:0x8], byteorder='little') + size_compressed: int = int.from_bytes(efi_data[0x0:0x4], byteorder='little') - return size_compress, size_original + size_decompressed: int = int.from_bytes(efi_data[0x4:0x8], byteorder='little') + + return {'size_compressed': size_compressed, 'size_decompressed': size_decompressed} -def is_efi_compressed(data: bytes | bytearray, strict: bool = True) -> bool: +def is_efi_compressed(in_object: str | bytes | bytearray, strict: bool = True) -> bool: """ Check if data is EFI compressed, controlling EOF padding """ - size_comp, size_orig = efi_compress_sizes(data=data) + efi_data: bytes = file_to_bytes(in_object=in_object) - check_diff: bool = size_comp < size_orig + efi_sizes: dict[str, int] = efi_header_info(in_object=efi_data) + + check_diff: bool = efi_sizes['size_compressed'] < efi_sizes['size_decompressed'] if strict: - check_size: bool = size_comp + 0x8 == len(data) + check_size: bool = efi_sizes['size_compressed'] + 0x8 == len(efi_data) else: - check_size = size_comp + 0x8 <= len(data) + check_size = efi_sizes['size_compressed'] + 0x8 <= len(efi_data) return check_diff and check_size @@ -125,22 +114,15 @@ def efi_decompress(in_path: str, out_path: str, padding: int = 0, silent: bool = comp_type: str = '--uefi') -> bool: """ EFI/Tiano Decompression via TianoCompress """ - try: - subprocess.run([tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type], - check=True, stdout=subprocess.DEVNULL) + tiano_c: list[str] = [tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type] - with open(in_path, 'rb') as file: - _, size_orig = efi_compress_sizes(data=file.read()) + tiano_x: subprocess.CompletedProcess[bytes] = subprocess.run(tiano_c, check=False, stdout=subprocess.DEVNULL) - if os.path.getsize(out_path) != size_orig: - raise OSError('EFI decompressed file & header size mismatch!') - except Exception as error: # pylint: disable=broad-except - if not silent: - printer(message=f'Error: TianoCompress could not extract file {in_path}: {error}!', padding=padding) + if tiano_x.returncode == 0 and is_file(in_path=out_path) and is_access(in_path=out_path): + if efi_header_info(in_object=in_path)['size_decompressed'] == path_size(in_path=out_path): + if not silent: + printer(message='Successful EFI decompression via TianoCompress!', padding=padding) - return False + return True - if not silent: - printer(message='Successful EFI decompression via TianoCompress!', padding=padding) - - return True + return False diff --git a/biosutilities/common/paths.py b/biosutilities/common/paths.py index ba25eb4..21d1ca0 100644 --- a/biosutilities/common/paths.py +++ b/biosutilities/common/paths.py @@ -113,13 +113,19 @@ def path_stem(in_path: str) -> str: return PurePath(in_path).stem +def path_size(in_path: str) -> int: + """ Get path size (bytes) """ + + return os.stat(in_path).st_size + + def path_suffixes(in_path: str) -> list[str]: """ Get list of path file extensions """ return PurePath(in_path).suffixes or [''] -def make_dirs(in_path: str, parents: bool = True, exist_ok: bool = False, delete: bool = False): +def make_dirs(in_path: str, parents: bool = True, exist_ok: bool = True, delete: bool = False): """ Create folder(s), controlling parents, existence and prior deletion """ if delete: @@ -138,12 +144,28 @@ def delete_dirs(in_path: str) -> None: def delete_file(in_path: str) -> None: """ Delete file, if present """ - if Path(in_path).is_file(): + if is_file(in_path=in_path): clear_readonly(in_path=in_path) os.remove(in_path) +def rename_file(in_path: str, in_dest: str) -> None: + """ Rename file with path or name destination, if present """ + + if is_file(in_path=in_path): + clear_readonly(in_path=in_path) + + if is_file(in_path=in_dest, allow_broken_links=True): + clear_readonly(in_path=in_dest) + + out_path: str = in_dest + else: + out_path = os.path.join(path_parent(in_path=in_path), in_dest) + + os.replace(in_path, out_path) + + def copy_file(in_path: str, out_path: str, metadata: bool = False) -> None: """ Copy file to path with or w/o metadata """ diff --git a/biosutilities/common/templates.py b/biosutilities/common/templates.py index 224e17a..178262c 100644 --- a/biosutilities/common/templates.py +++ b/biosutilities/common/templates.py @@ -5,9 +5,11 @@ Copyright (C) 2022-2024 Plato Mavropoulos """ +from biosutilities.common.texts import file_to_bytes + class BIOSUtility: - """ Base utility class for BIOSUtilities """ + """ Base class for BIOSUtilities """ TITLE: str = 'BIOS Utility' @@ -16,6 +18,8 @@ class BIOSUtility: self.extract_path: str = extract_path self.padding: int = padding + self.input_buffer: bytes = file_to_bytes(in_object=self.input_object) + def check_format(self) -> bool: """ Check if input object is of specific supported format """ diff --git a/biosutilities/dell_pfs_extract.py b/biosutilities/dell_pfs_extract.py index 48d4150..3877e23 100644 --- a/biosutilities/dell_pfs_extract.py +++ b/biosutilities/dell_pfs_extract.py @@ -19,7 +19,7 @@ from typing import Any, Final from biosutilities.common.checksums import checksum_8_xor from biosutilities.common.compression import is_szip_supported, szip_decompress -from biosutilities.common.paths import (delete_dirs, path_files, is_access, is_file, make_dirs, +from biosutilities.common.paths import (delete_dirs, delete_file, path_files, is_access, is_file, make_dirs, path_name, path_parent, path_stem, safe_name) from biosutilities.common.patterns import PAT_DELL_FTR, PAT_DELL_HDR, PAT_DELL_PKG from biosutilities.common.structs import CHAR, ctypes_struct, UINT8, UINT16, UINT32, UINT64 @@ -244,12 +244,10 @@ class DellPfsExtract(BIOSUtility): def check_format(self) -> bool: """ Check if input is Dell PFS/PKG image """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - - if self._is_pfs_pkg(input_object=input_buffer): + if self._is_pfs_pkg(input_object=self.input_buffer): return True - if self._is_pfs_hdr(input_object=input_buffer) and self._is_pfs_ftr(input_object=input_buffer): + if self._is_pfs_hdr(input_object=self.input_buffer) and self._is_pfs_ftr(input_object=self.input_buffer): return True return False @@ -257,18 +255,16 @@ class DellPfsExtract(BIOSUtility): def parse_format(self) -> bool: """ Parse & Extract Dell PFS Update image """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) + make_dirs(in_path=self.extract_path) - make_dirs(in_path=self.extract_path, delete=True) - - is_dell_pkg: bool = self._is_pfs_pkg(input_object=input_buffer) + is_dell_pkg: bool = self._is_pfs_pkg(input_object=self.input_buffer) if is_dell_pkg: pfs_results: dict[str, bytes] = self._thinos_pkg_extract( - input_object=input_buffer, extract_path=self.extract_path) + input_object=self.input_buffer, extract_path=self.extract_path) else: 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} + in_path=self.input_object) else 'Image': self.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): @@ -356,10 +352,10 @@ class DellPfsExtract(BIOSUtility): with open(pkg_tar_path, 'wb') as pkg_payload: pkg_payload.write(lzma.decompress(lzma_bin_dat)) - if is_szip_supported(in_path=pkg_tar_path, padding=0, args=['-tTAR']): + if is_szip_supported(in_path=pkg_tar_path, args=['-tTAR']): if szip_decompress(in_path=pkg_tar_path, out_path=working_path, in_name='TAR', padding=0, args=['-tTAR'], check=True, silent=True): - os.remove(pkg_tar_path) + delete_file(in_path=pkg_tar_path) else: return pfs_results else: @@ -423,7 +419,7 @@ class DellPfsExtract(BIOSUtility): section_path: str = os.path.join(extract_path, safe_name(in_name=section_name)) # Create extraction subdirectory and delete old (if present, not in recursions) - make_dirs(in_path=section_path, delete=(not is_rec), parents=True, exist_ok=True) + make_dirs(in_path=section_path, delete=not is_rec) # Store the compressed zlib stream start offset compressed_start: int = zlib_start + 0xB diff --git a/biosutilities/fujitsu_sfx_extract.py b/biosutilities/fujitsu_sfx_extract.py index c6c7974..93b0e1d 100644 --- a/biosutilities/fujitsu_sfx_extract.py +++ b/biosutilities/fujitsu_sfx_extract.py @@ -11,11 +11,10 @@ import os import re from biosutilities.common.compression import is_szip_supported, szip_decompress -from biosutilities.common.paths import make_dirs +from biosutilities.common.paths import delete_file, make_dirs from biosutilities.common.patterns import PAT_FUJITSU_SFX from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes class FujitsuSfxExtract(BIOSUtility): @@ -26,17 +25,13 @@ class FujitsuSfxExtract(BIOSUtility): def check_format(self) -> bool: """ Check if input is Fujitsu SFX image """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - - return bool(PAT_FUJITSU_SFX.search(input_buffer)) + return bool(PAT_FUJITSU_SFX.search(self.input_buffer)) def parse_format(self) -> bool: """ Parse & Extract Fujitsu SFX image """ - 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) + match_cab: re.Match[bytes] | None = PAT_FUJITSU_SFX.search(self.input_buffer) if not match_cab: return False @@ -47,7 +42,7 @@ class FujitsuSfxExtract(BIOSUtility): cab_start: int = match_cab.start() + 0xA # Get LE XOR-ed CAB size - cab_size: int = int.from_bytes(input_buffer[cab_start + 0x8:cab_start + 0xC], byteorder='little') + cab_size: int = int.from_bytes(self.input_buffer[cab_start + 0x8:cab_start + 0xC], byteorder='little') # Create CAB size XOR value xor_size: int = int.from_bytes(b'\xFF' * 0x4, byteorder='little') @@ -58,7 +53,7 @@ class FujitsuSfxExtract(BIOSUtility): 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') + cab_data: int = int.from_bytes(self.input_buffer[cab_start:cab_start + cab_size], byteorder='big') # Create CAB data XOR value xor_data: int = int.from_bytes(b'\xFF' * cab_size, byteorder='big') @@ -68,7 +63,7 @@ class FujitsuSfxExtract(BIOSUtility): printer(message='Extracting archive...', padding=self.padding + 4) - make_dirs(in_path=self.extract_path, delete=True) + make_dirs(in_path=self.extract_path) cab_path: str = os.path.join(self.extract_path, 'FjSfxBinay.cab') @@ -76,10 +71,10 @@ class FujitsuSfxExtract(BIOSUtility): with open(cab_path, 'wb') as cab_file_object: cab_file_object.write(raw_data) - if is_szip_supported(in_path=cab_path, padding=self.padding + 8, silent=False): + if is_szip_supported(in_path=cab_path): 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) + delete_file(in_path=cab_path) else: return False else: diff --git a/biosutilities/fujitsu_upc_extract.py b/biosutilities/fujitsu_upc_extract.py index a86e752..79d63cf 100644 --- a/biosutilities/fujitsu_upc_extract.py +++ b/biosutilities/fujitsu_upc_extract.py @@ -10,9 +10,8 @@ Copyright (C) 2021-2024 Plato Mavropoulos import os from biosutilities.common.compression import efi_decompress, is_efi_compressed -from biosutilities.common.paths import make_dirs, is_file, path_name, path_suffixes +from biosutilities.common.paths import delete_file, make_dirs, is_file, path_name, path_suffixes from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes class FujitsuUpcExtract(BIOSUtility): @@ -30,14 +29,14 @@ class FujitsuUpcExtract(BIOSUtility): is_upc = True if is_upc: - is_upc = is_efi_compressed(data=file_to_bytes(in_object=self.input_object)) + is_upc = is_efi_compressed(in_object=self.input_object) return is_upc def parse_format(self) -> bool: """ Parse & Extract Fujitsu UPC image """ - make_dirs(in_path=self.extract_path, delete=True) + make_dirs(in_path=self.extract_path) if isinstance(self.input_object, str) and is_file(in_path=self.input_object): input_name: str = path_name(in_path=self.input_object) @@ -52,13 +51,13 @@ class FujitsuUpcExtract(BIOSUtility): 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=self.input_object)) + input_path_object.write(self.input_buffer) 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=self.padding) if input_path != self.input_object: - os.remove(input_path) + delete_file(in_path=input_path) return efi_status diff --git a/biosutilities/insyde_ifd_extract.py b/biosutilities/insyde_ifd_extract.py index 97f1ab0..0ba32ed 100644 --- a/biosutilities/insyde_ifd_extract.py +++ b/biosutilities/insyde_ifd_extract.py @@ -14,13 +14,12 @@ import re from typing import Any, Final from biosutilities.common.compression import is_szip_supported, szip_decompress -from biosutilities.common.paths import (extract_folder, is_access, is_file, path_files, +from biosutilities.common.paths import (delete_file, extract_folder, is_access, is_file, path_files, make_dirs, path_name, safe_name) from biosutilities.common.patterns import PAT_INSYDE_IFL, PAT_INSYDE_SFX from biosutilities.common.structs import CHAR, ctypes_struct, UINT32 from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes class IflashHeader(ctypes.LittleEndianStructure): @@ -83,12 +82,10 @@ class InsydeIfdExtract(BIOSUtility): def check_format(self) -> bool: """ Check if input is Insyde iFlash/iFdPacker Update image """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - - if bool(self._insyde_iflash_detect(input_buffer=input_buffer)): + if bool(self._insyde_iflash_detect(input_buffer=self.input_buffer)): return True - if bool(PAT_INSYDE_SFX.search(input_buffer)): + if bool(PAT_INSYDE_SFX.search(self.input_buffer)): return True return False @@ -96,14 +93,12 @@ class InsydeIfdExtract(BIOSUtility): def parse_format(self) -> bool: """ Parse & Extract Insyde iFlash/iFdPacker Update images """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - - iflash_code: int = self._insyde_iflash_extract(input_buffer=input_buffer, extract_path=self.extract_path, + iflash_code: int = self._insyde_iflash_extract(input_buffer=self.input_buffer, extract_path=self.extract_path, padding=self.padding) 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, + ifdpack_code: int = self._insyde_packer_extract(input_buffer=self.input_buffer, extract_path=ifdpack_path, padding=self.padding) return (iflash_code and ifdpack_code) == 0 @@ -142,7 +137,7 @@ class InsydeIfdExtract(BIOSUtility): printer(message='Detected Insyde iFlash Update image!', padding=padding) - make_dirs(in_path=extract_path, delete=True) + make_dirs(in_path=extract_path) exit_codes: list = [] @@ -217,10 +212,10 @@ class InsydeIfdExtract(BIOSUtility): with open(sfx_path, 'wb') as sfx_file_object: sfx_file_object.write(sfx_buffer) - if is_szip_supported(in_path=sfx_path, padding=padding + 8, args=[f'-p{self.INS_SFX_PWD}'], silent=False): + if is_szip_supported(in_path=sfx_path, args=[f'-p{self.INS_SFX_PWD}']): if szip_decompress(in_path=sfx_path, out_path=extract_path, in_name='Insyde iFdPacker > 7-Zip SFX', padding=padding + 8, args=[f'-p{self.INS_SFX_PWD}'], check=True): - os.remove(sfx_path) + delete_file(in_path=sfx_path) else: return 125 else: diff --git a/biosutilities/panasonic_bios_extract.py b/biosutilities/panasonic_bios_extract.py index d71088d..08bfc11 100644 --- a/biosutilities/panasonic_bios_extract.py +++ b/biosutilities/panasonic_bios_extract.py @@ -20,7 +20,7 @@ import pefile from dissect.util.compression import lznt1 from biosutilities.common.compression import is_szip_supported, szip_decompress -from biosutilities.common.paths import is_access, is_file, path_files, make_dirs, path_stem, safe_name +from biosutilities.common.paths import delete_file, is_access, is_file, path_files, make_dirs, path_stem, safe_name from biosutilities.common.executables import ms_pe_desc, ms_pe, is_ms_pe, ms_pe_info_show from biosutilities.common.patterns import PAT_MICROSOFT_CAB from biosutilities.common.system import printer @@ -64,7 +64,7 @@ class PanasonicBiosExtract(BIOSUtility): ms_pe_info_show(pe_file=upd_pe_file, padding=self.padding + 4) - make_dirs(in_path=self.extract_path, delete=True) + make_dirs(in_path=self.extract_path) upd_pe_path: str = self._panasonic_cab_extract(input_object=self.input_object, extract_path=self.extract_path, padding=self.padding + 8) @@ -82,7 +82,7 @@ class PanasonicBiosExtract(BIOSUtility): ms_pe_info_show(pe_file=upd_pe_file, padding=upd_padding + 4) - os.remove(upd_pe_path) + delete_file(in_path=upd_pe_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) @@ -121,12 +121,12 @@ class PanasonicBiosExtract(BIOSUtility): with open(cab_path, 'wb') as cab_file_object: cab_file_object.write(input_data[cab_bgn:cab_end]) - if is_szip_supported(in_path=cab_path, padding=padding, silent=False): + if is_szip_supported(in_path=cab_path): printer(message=f'Panasonic BIOS Package > PE > CAB {cab_tag}', padding=padding) if szip_decompress(in_path=cab_path, out_path=extract_path, in_name='CAB', padding=padding + 4, check=True): - os.remove(cab_path) # Successful extraction, delete CAB archive + delete_file(in_path=cab_path) # Successful extraction, delete CAB archive for extracted_file_path in path_files(in_path=extract_path): if is_file(in_path=extracted_file_path) and is_access(in_path=extracted_file_path): diff --git a/biosutilities/phoenix_tdk_extract.py b/biosutilities/phoenix_tdk_extract.py index e59b6eb..7994dc1 100644 --- a/biosutilities/phoenix_tdk_extract.py +++ b/biosutilities/phoenix_tdk_extract.py @@ -23,7 +23,6 @@ from biosutilities.common.patterns import PAT_MICROSOFT_MZ, PAT_MICROSOFT_PE, PA from biosutilities.common.structs import CHAR, ctypes_struct, UINT32 from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes class PhoenixTdkHeader(ctypes.LittleEndianStructure): @@ -106,25 +105,21 @@ class PhoenixTdkExtract(BIOSUtility): def check_format(self) -> bool: """ Check if input contains valid Phoenix TDK image """ - 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) + return bool(self._get_phoenix_tdk(in_buffer=self.input_buffer)[1] is not None) 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=self.input_object) - - make_dirs(in_path=self.extract_path, delete=True) + make_dirs(in_path=self.extract_path) printer(message='Phoenix Tools Development Kit Packer', padding=self.padding) - base_off, pack_off = self._get_phoenix_tdk(in_buffer=input_buffer) + base_off, pack_off = self._get_phoenix_tdk(in_buffer=self.input_buffer) # Parse TDK Header structure - tdk_hdr: Any = ctypes_struct(buffer=input_buffer, start_offset=pack_off, class_object=PhoenixTdkHeader) + tdk_hdr: Any = ctypes_struct(buffer=self.input_buffer, start_offset=pack_off, class_object=PhoenixTdkHeader) # Print TDK Header structure info printer(message='Phoenix TDK Header:\n', padding=self.padding + 4) @@ -143,8 +138,10 @@ class PhoenixTdkExtract(BIOSUtility): # Parse and extract each TDK Header Entry for entry_index in range(tdk_hdr.Count): # Parse TDK Entry structure - tdk_mod: Any = ctypes_struct(buffer=input_buffer, start_offset=entries_off + entry_index * self.TDK_MOD_LEN, - class_object=PhoenixTdkEntry, param_list=[base_off]) + tdk_mod: Any = ctypes_struct( + buffer=self.input_buffer, start_offset=entries_off + entry_index * self.TDK_MOD_LEN, + 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=self.padding + 8) @@ -155,13 +152,13 @@ class PhoenixTdkExtract(BIOSUtility): mod_off: int = tdk_mod.get_offset() # Check if TDK Entry raw data Offset is valid - if mod_off >= len(input_buffer): + if mod_off >= len(self.input_buffer): printer(message='Error: Phoenix TDK Entry > Offset is out of bounds!\n', padding=self.padding + 12) exit_code = 2 # Store TDK Entry raw data (relative to TDK Base, not TDK Header) - mod_data: bytes = input_buffer[mod_off:mod_off + tdk_mod.Size] + mod_data: bytes = self.input_buffer[mod_off:mod_off + tdk_mod.Size] # Check if TDK Entry raw data is complete if len(mod_data) != tdk_mod.Size: diff --git a/biosutilities/portwell_efi_extract.py b/biosutilities/portwell_efi_extract.py index 2a7ad53..3f0116a 100644 --- a/biosutilities/portwell_efi_extract.py +++ b/biosutilities/portwell_efi_extract.py @@ -16,12 +16,11 @@ from typing import Final from pefile import PE from biosutilities.common.compression import efi_decompress, is_efi_compressed -from biosutilities.common.paths import make_dirs, safe_name +from biosutilities.common.paths import delete_file, make_dirs, rename_file, safe_name from biosutilities.common.executables import ms_pe from biosutilities.common.patterns import PAT_MICROSOFT_MZ, PAT_PORTWELL_EFI from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes class PortwellEfiExtract(BIOSUtility): @@ -40,17 +39,15 @@ class PortwellEfiExtract(BIOSUtility): def check_format(self) -> bool: """ Check if input is Portwell EFI executable """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - try: - pe_buffer: bytes = self._get_portwell_pe(in_buffer=input_buffer)[1] + pe_buffer: bytes = self._get_portwell_pe(in_buffer=self.input_buffer)[1] except Exception as error: # pylint: disable=broad-except logging.debug('Error: Could not check if input is Portwell EFI executable: %s', error) return False # EFI images start with PE Header MZ - if PAT_MICROSOFT_MZ.search(input_buffer[:0x2]): + if PAT_MICROSOFT_MZ.search(self.input_buffer[:0x2]): # Portwell EFI files start with if PAT_PORTWELL_EFI.search(pe_buffer[:0x4]): return True @@ -63,13 +60,11 @@ class PortwellEfiExtract(BIOSUtility): # Initialize EFI Payload file chunks efi_files: list[bytes] = [] - input_buffer: bytes = file_to_bytes(in_object=self.input_object) + make_dirs(in_path=self.extract_path) - make_dirs(in_path=self.extract_path, delete=True) + pe_file, pe_data = self._get_portwell_pe(in_buffer=self.input_buffer) - 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) + efi_title: str = self._get_unpacker_tag(input_buffer=self.input_buffer, pe_file=pe_file) printer(message=efi_title, padding=self.padding) @@ -155,13 +150,13 @@ class PortwellEfiExtract(BIOSUtility): out_file.write(file_data) # Attempt to detect EFI compression & decompress when applicable - if is_efi_compressed(data=file_data): + if is_efi_compressed(in_object=file_data): # Store temporary compressed file name - comp_fname: str = file_path + '.temp' + file_path_temp: str = f'{file_path}.temp' # Rename initial/compressed file - os.replace(file_path, comp_fname) + rename_file(in_path=file_path, in_dest=file_path_temp) # Successful decompression, delete compressed file - if efi_decompress(in_path=comp_fname, out_path=file_path, padding=padding + 8): - os.remove(comp_fname) + if efi_decompress(in_path=file_path_temp, out_path=file_path, padding=padding + 8): + delete_file(in_path=file_path_temp) diff --git a/biosutilities/toshiba_com_extract.py b/biosutilities/toshiba_com_extract.py index a6b0031..a06e519 100644 --- a/biosutilities/toshiba_com_extract.py +++ b/biosutilities/toshiba_com_extract.py @@ -11,11 +11,10 @@ import os import subprocess from biosutilities.common.externals import comextract_path -from biosutilities.common.paths import is_file, make_dirs, path_stem, safe_name +from biosutilities.common.paths import delete_file, is_file, make_dirs, path_stem from biosutilities.common.patterns import PAT_TOSHIBA_COM from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes class ToshibaComExtract(BIOSUtility): @@ -26,14 +25,12 @@ class ToshibaComExtract(BIOSUtility): def check_format(self) -> bool: """ Check if input is Toshiba BIOS COM image """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - - return bool(PAT_TOSHIBA_COM.search(input_buffer, 0, 0x100)) + return bool(PAT_TOSHIBA_COM.search(self.input_buffer, 0, 0x100)) def parse_format(self) -> bool: """ Parse & Extract Toshiba BIOS COM image """ - make_dirs(in_path=self.extract_path, delete=True) + make_dirs(in_path=self.extract_path) if isinstance(self.input_object, str) and is_file(in_path=self.input_object): input_path: str = self.input_object @@ -41,26 +38,19 @@ class ToshibaComExtract(BIOSUtility): 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=self.input_object)) + input_buffer.write(self.input_buffer) - output_name: str = f'{safe_name(in_name=path_stem(in_path=input_path))}_extracted.bin' + output_path: str = os.path.join(self.extract_path, f'{path_stem(in_path=input_path)}_extracted.bin') - 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) - - 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=self.padding) - - return False + comextract_res: subprocess.CompletedProcess[bytes] = subprocess.run( + [comextract_path(), input_path, output_path], check=False, stdout=subprocess.DEVNULL) if input_path != self.input_object: - os.remove(input_path) + delete_file(in_path=input_path) - printer(message='Successful extraction via ToshibaComExtractor!', padding=self.padding) + if comextract_res.returncode == 0 and is_file(in_path=output_path): + printer(message='Successful extraction via ToshibaComExtractor!', padding=self.padding) - return True + return True + + return False diff --git a/biosutilities/vaio_package_extract.py b/biosutilities/vaio_package_extract.py index 70dba37..a96f0d6 100644 --- a/biosutilities/vaio_package_extract.py +++ b/biosutilities/vaio_package_extract.py @@ -12,11 +12,10 @@ import os from re import Match from biosutilities.common.compression import is_szip_supported, szip_decompress -from biosutilities.common.paths import make_dirs +from biosutilities.common.paths import delete_file, make_dirs, path_name from biosutilities.common.patterns import PAT_VAIO_CAB, PAT_VAIO_CFG, PAT_VAIO_CHK, PAT_VAIO_EXT from biosutilities.common.system import printer from biosutilities.common.templates import BIOSUtility -from biosutilities.common.texts import file_to_bytes class VaioPackageExtract(BIOSUtility): @@ -27,23 +26,19 @@ class VaioPackageExtract(BIOSUtility): def check_format(self) -> bool: """ Check if input is VAIO Packaging Manager """ - input_buffer: bytes = file_to_bytes(in_object=self.input_object) - - return bool(PAT_VAIO_CFG.search(input_buffer)) + return bool(PAT_VAIO_CFG.search(self.input_buffer)) def parse_format(self) -> bool: """ Parse & Extract or Unlock VAIO Packaging Manager """ - input_buffer: bytes = file_to_bytes(self.input_object) + input_name: str = path_name(self.input_object) if isinstance(self.input_object, 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=self.extract_path) - make_dirs(in_path=self.extract_path, delete=True) - - if self._vaio_cabinet(name=input_name, buffer=input_buffer, extract_path=self.extract_path, + if self._vaio_cabinet(name=input_name, buffer=self.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, + elif self._vaio_unlock(name=input_name, buffer=self.input_buffer, extract_path=self.extract_path, padding=self.padding) == 0: printer(message='Successfully Unlocked!', padding=self.padding) else: @@ -93,10 +88,10 @@ class VaioPackageExtract(BIOSUtility): with open(cab_path, 'wb') as cab_file: cab_file.write(raw_data) - if is_szip_supported(in_path=cab_path, padding=padding + 8, silent=False): + if is_szip_supported(in_path=cab_path): if szip_decompress(in_path=cab_path, out_path=extract_path, in_name='VAIO CAB', padding=padding + 8, check=True): - os.remove(cab_path) + delete_file(in_path=cab_path) else: return 3 else: diff --git a/main.py b/main.py index d5b1d5d..968c280 100644 --- a/main.py +++ b/main.py @@ -6,6 +6,8 @@ Copyright (C) 2018-2024 Plato Mavropoulos """ import os +import sys +import traceback from argparse import ArgumentParser, Namespace from typing import Any, Final @@ -89,8 +91,8 @@ class BIOSUtilities: else: self._output_path = runtime_root() - def _check_sys_py(self) -> None: - """ Check Python Version """ + def _check_system_support(self) -> None: + """ Check Python Version and OS Platform """ sys_py: tuple = python_version() @@ -100,21 +102,33 @@ class BIOSUtilities: 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 """ + def _exit_main(self, exit_code: int = 0) -> None: + if not self.main_arguments.auto_exit: + input('\nPress any key to exit...') - self._check_sys_py() + sys.exit(exit_code) - self._check_sys_os() + def _show_exception_and_exit(self, exc_type, exc_value, tb) -> None: + if exc_type is KeyboardInterrupt: + print('\n') + else: + print(f'\nError: BIOSUtilities v{__version__} crashed:\n') + + traceback.print_exception(exc_type, exc_value, tb) + + self._exit_main(exit_code=1) + + def run_main(self, padding: int = 0) -> None: + """ Run main flow """ + + sys.excepthook = self._show_exception_and_exit + + self._check_system_support() self._setup_input_files(padding=padding) @@ -131,7 +145,7 @@ class BIOSUtilities: 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) + printer(message=f'{to_boxed(in_text=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)) @@ -167,10 +181,7 @@ class BIOSUtilities: printer(message=None, new_line=False) - if not self.main_arguments.auto_exit: - input('Press any key to exit...') - - return exit_code == 0 + self._exit_main(exit_code=exit_code) if __name__ == '__main__': diff --git a/pyproject.toml b/pyproject.toml index 165fde6..bb95aa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,6 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", - "Operating System :: POSIX :: BSD", "Operating System :: MacOS", "Topic :: Security", "Topic :: Scientific/Engineering :: Information Analysis"