mirror of
https://github.com/platomav/BIOSUtilities.git
synced 2025-05-09 13:52:00 -04:00
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
This commit is contained in:
parent
d8e23f9ef3
commit
f895fc208c
24 changed files with 327 additions and 380 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
Copyright (C) 2018-2024 Plato Mavropoulos
|
||||
"""
|
||||
|
||||
__version__ = '24.10.23'
|
||||
__version__ = '24.10.29'
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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('<II', len(ucp_buffer), len(ucp_buffer)) # Generate UafModule Structure
|
||||
# Generate UafModule Structure
|
||||
fake: bytes = struct.pack('<II', len(ucp_buffer), len(ucp_buffer))
|
||||
|
||||
# Parse @UAF|@HPU Module EFI Structure
|
||||
uaf_mod: Any = ctypes_struct(buffer=fake, start_offset=0, class_object=UafModule)
|
||||
|
||||
uaf_name = self.UAF_TAG_DICT[ucp_tag][0] # Get @UAF|@HPU Module Filename
|
||||
# Get @UAF|@HPU Module Filename
|
||||
uaf_name = self.UAF_TAG_DICT[ucp_tag][0]
|
||||
|
||||
uaf_desc = self.UAF_TAG_DICT[ucp_tag][1] # Get @UAF|@HPU Module Description
|
||||
# Get @UAF|@HPU Module Description
|
||||
uaf_desc = self.UAF_TAG_DICT[ucp_tag][1]
|
||||
|
||||
# Print @UAF|@HPU Module EFI Info
|
||||
uaf_mod.struct_print(filename=uaf_name, description=uaf_desc, padding=self.padding + 8)
|
||||
|
@ -316,23 +315,21 @@ class AmiUcpExtract(BIOSUtility):
|
|||
else:
|
||||
printer(message=f'Checksum of UCP Module {tag} is valid!', padding=padding)
|
||||
|
||||
@staticmethod
|
||||
def _get_ami_ucp(input_object: str | bytes | bytearray) -> 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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 """
|
||||
|
||||
|
|
|
@ -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 """
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 <UU>
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
41
main.py
41
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__':
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue