BIOSUtilities/main.py
Plato Mavropoulos 35564f31b7 BIOSUtilities v24.11.10
Re-written public attributes of Apple EFI ID
Improved memory consumption for all utilities
Adjusted README with consolidated requirements
2024-11-11 01:18:34 +02:00

188 lines
6.9 KiB
Python

#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import os
import sys
import traceback
from argparse import ArgumentParser, Namespace
from typing import Any, Final
from biosutilities import __version__
from biosutilities.ami_pfat_extract import AmiPfatExtract
from biosutilities.ami_ucp_extract import AmiUcpExtract
from biosutilities.apple_efi_id import AppleEfiIdentify
from biosutilities.apple_efi_im4p import AppleEfiIm4pSplit
from biosutilities.apple_efi_pbzx import AppleEfiPbzxExtract
from biosutilities.apple_efi_pkg import AppleEfiPkgExtract
from biosutilities.award_bios_extract import AwardBiosExtract
from biosutilities.dell_pfs_extract import DellPfsExtract
from biosutilities.fujitsu_sfx_extract import FujitsuSfxExtract
from biosutilities.fujitsu_upc_extract import FujitsuUpcExtract
from biosutilities.insyde_ifd_extract import InsydeIfdExtract
from biosutilities.panasonic_bios_extract import PanasonicBiosExtract
from biosutilities.phoenix_tdk_extract import PhoenixTdkExtract
from biosutilities.portwell_efi_extract import PortwellEfiExtract
from biosutilities.toshiba_com_extract import ToshibaComExtract
from biosutilities.vaio_package_extract import VaioPackageExtract
from biosutilities.common.paths import (delete_dirs, extract_folder, is_dir_read, is_empty_dir, is_file_read,
path_files, path_name, path_parent, real_path, runtime_root)
from biosutilities.common.system import python_version, printer, system_platform
from biosutilities.common.texts import remove_quotes, to_boxed, to_ordinal
class BIOSUtilities:
""" Main BIOSUtilities class """
MAX_FAT32_ITEMS: Final[int] = 65535
MIN_PYTHON_VER: Final[tuple[int, int]] = (3, 10)
def __init__(self) -> None:
main_argparser: ArgumentParser = ArgumentParser(allow_abbrev=False)
main_argparser.add_argument('paths', nargs='*')
main_argparser.add_argument('-e', '--auto-exit', help='do not pause on exit', action='store_true')
main_argparser.add_argument('-o', '--output-dir', help='extraction directory')
self.main_arguments: Namespace = main_argparser.parse_args()
self._input_files: list[str] = []
self._output_path: str = ''
def _setup_input_files(self, padding: int = 0) -> None:
self._input_files = []
input_paths: list[str] = self.main_arguments.paths
if not input_paths:
input_paths = [remove_quotes(in_text=input(f'\n{" " * padding}Enter input file or directory path: '))]
for input_path in [input_path for input_path in input_paths if input_path]:
input_path_real: str = real_path(in_path=input_path)
if is_dir_read(in_path=input_path_real):
for input_file in path_files(in_path=input_path_real):
if is_file_read(in_path=input_file):
self._input_files.append(input_file)
elif is_file_read(in_path=input_path_real):
self._input_files.append(input_path_real)
def _setup_output_dir(self, padding: int = 0) -> None:
self._output_path = ''
output_path: str = self.main_arguments.output_dir
if not output_path:
output_path = remove_quotes(in_text=input(f'\n{" " * padding}Enter output directory path: '))
if not output_path and self._input_files:
output_path = str(path_parent(in_path=self._input_files[0]))
if output_path and is_dir_read(in_path=output_path):
self._output_path = output_path
else:
self._output_path = runtime_root()
def _check_system_support(self) -> None:
""" Check Python Version and OS Platform """
sys_py: tuple = python_version()
if sys_py < self.MIN_PYTHON_VER:
min_py_str: str = '.'.join(map(str, self.MIN_PYTHON_VER))
sys_py_str: str = '.'.join(map(str, sys_py[:2]))
raise RuntimeError(f'Python >= {min_py_str} required, not {sys_py_str}')
os_tag, is_win, is_lnx = system_platform()
if not (is_win or is_lnx):
raise OSError(f'Unsupported operating system: {os_tag}')
def _exit_main(self, exit_code: int = 0) -> None:
if not self.main_arguments.auto_exit:
input('\nPress any key to exit...')
sys.exit(exit_code)
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)
self._setup_output_dir(padding=padding)
exit_code: int = len(self._input_files)
utilities_classes: list[Any] = [
AppleEfiPkgExtract, AmiUcpExtract, AmiPfatExtract, InsydeIfdExtract, DellPfsExtract, PhoenixTdkExtract,
PanasonicBiosExtract, VaioPackageExtract, PortwellEfiExtract, ToshibaComExtract, FujitsuSfxExtract,
FujitsuUpcExtract, AwardBiosExtract, AppleEfiPbzxExtract, AppleEfiIm4pSplit, AppleEfiIdentify
]
for input_file in self._input_files:
input_name: str = path_name(in_path=input_file, limit=True)
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))
if is_dir_read(in_path=extract_path):
for suffix in range(2, self.MAX_FAT32_ITEMS):
renamed_path: str = f'{os.path.normpath(extract_path)}_{to_ordinal(in_number=suffix)}'
if not is_dir_read(in_path=renamed_path):
extract_path = renamed_path
break
utility: Any = utility_class(input_object=input_file, extract_path=extract_path, padding=padding + 8)
if not utility.check_format():
continue
printer(message=to_boxed(in_text=f'{utility.TITLE} v{__version__}'),
new_line=False, padding=padding + 4)
is_parsed_format: bool = utility.parse_format()
is_empty_output: bool = is_empty_dir(in_path=extract_path)
if is_empty_output:
delete_dirs(in_path=extract_path)
if is_parsed_format and not is_empty_output:
exit_code -= 1
break
printer(message=None, new_line=False)
self._exit_main(exit_code=exit_code)
if __name__ == '__main__':
BIOSUtilities().run_main()