Added AMI PFAT RSA 3K signed blocks support

Added AMI PFAT nested detection at each file

Added Award BIOS payload naming at each file

Switched Panasonic BIOS LZNT1 external library

Improved Panasonic LZNT1 detection and length

Improved Dell PFS code structure and fixed bugs

Improved code exception handling (raise, catch)

Improved code definitions (PEP8, docs, types)

Fixed some arguments missing from help screens
This commit is contained in:
Plato Mavropoulos 2024-04-24 01:22:53 +03:00
parent 03ae0cf070
commit d85a7f82dc
37 changed files with 2897 additions and 2174 deletions

6
common/__init__.py Normal file
View file

@ -0,0 +1,6 @@
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2019-2024 Plato Mavropoulos
"""

View file

@ -1,25 +1,31 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
# Get Checksum 16-bit
def get_chk_16(data, value=0, order='little'):
""" Calculate Checksum-16 of data, controlling IV and Endianess """
for idx in range(0, len(data), 2):
# noinspection PyTypeChecker
value += int.from_bytes(data[idx:idx + 2], order)
value += int.from_bytes(data[idx:idx + 2], byteorder=order)
value &= 0xFFFF
return value
# Get Checksum 8-bit XOR
def get_chk_8_xor(data, value=0):
""" Calculate Checksum-8 XOR of data, controlling IV """
for byte in data:
value ^= byte
value ^= 0x0
return value

View file

@ -1,57 +1,60 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import os
import subprocess
from common.path_ops import project_root, safe_path
from common.system import get_os_ver, printer
from common.externals import get_tiano_path
from common.system import printer
def get_compress_sizes(data):
""" Get EFI compression sizes """
def get_compress_sizes(data):
size_compress = int.from_bytes(data[0x0:0x4], 'little')
size_original = int.from_bytes(data[0x4:0x8], 'little')
return size_compress, size_original
def is_efi_compressed(data, strict=True):
size_comp,size_orig = get_compress_sizes(data)
""" Check if data is EFI compressed, controlling EOF padding """
size_comp, size_orig = get_compress_sizes(data)
check_diff = size_comp < size_orig
if strict:
check_size = size_comp + 0x8 == len(data)
else:
check_size = size_comp + 0x8 <= len(data)
return check_diff and check_size
# Get TianoCompress path
def get_tiano_path():
exec_name = f'TianoCompress{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external',exec_name])
# EFI/Tiano Decompression via TianoCompress
def efi_decompress(in_path, out_path, padding=0, silent=False, comp_type='--uefi'):
""" EFI/Tiano Decompression via TianoCompress """
try:
subprocess.run([get_tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type], check=True, stdout=subprocess.DEVNULL)
subprocess.run([get_tiano_path(), '-d', in_path, '-o', out_path, '-q', comp_type],
check=True, stdout=subprocess.DEVNULL)
with open(in_path, 'rb') as file:
_,size_orig = get_compress_sizes(file.read())
_, size_orig = get_compress_sizes(file.read())
if os.path.getsize(out_path) != size_orig:
raise Exception('EFI_DECOMPRESS_ERROR')
except Exception:
raise OSError('EFI decompressed file & header size mismatch!')
except Exception as error: # pylint: disable=broad-except
if not silent:
printer(f'Error: TianoCompress could not extract file {in_path}!', padding)
printer(f'Error: TianoCompress could not extract file {in_path}: {error}!', padding)
return 1
if not silent:
printer('Succesfull EFI decompression via TianoCompress!', padding)
return 0

View file

@ -1,72 +1,72 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import os
import subprocess
from common.path_ops import project_root, safe_path
from common.system import get_os_ver, printer
from common.externals import get_szip_path
from common.system import printer
# Get 7-Zip path
def get_szip_path():
exec_name = '7z.exe' if get_os_ver()[1] else '7zzs'
return safe_path(project_root(), ['external',exec_name])
# Check 7-Zip bad exit codes (0 OK, 1 Warning)
def check_bad_exit_code(exit_code):
if exit_code not in (0,1):
raise Exception(f'BAD_EXIT_CODE_{exit_code}')
""" Check 7-Zip bad exit codes (0 OK, 1 Warning) """
if exit_code not in (0, 1):
raise ValueError(f'Bad exit code: {exit_code}')
# Check if file is 7-Zip supported
def is_szip_supported(in_path, padding=0, args=None, check=False, silent=False):
""" Check if file is 7-Zip supported """
try:
if args is None:
args = []
szip_c = [get_szip_path(), 't', in_path, *args, '-bso0', '-bse0', '-bsp0']
szip_t = subprocess.run(szip_c, check=False)
if check:
check_bad_exit_code(szip_t.returncode)
except Exception:
except Exception as error: # pylint: disable=broad-except
if not silent:
printer(f'Error: 7-Zip could not check support for file {in_path}!', padding)
printer(f'Error: 7-Zip could not check support for file {in_path}: {error}!', padding)
return False
return True
# Archive decompression via 7-Zip
def szip_decompress(in_path, out_path, in_name, padding=0, args=None, check=False, silent=False):
""" Archive decompression via 7-Zip """
if not in_name:
in_name = 'archive'
try:
if args is None:
args = []
szip_c = [get_szip_path(), 'x', *args, '-aou', '-bso0', '-bse0', '-bsp0', f'-o{out_path}', in_path]
szip_x = subprocess.run(szip_c, check=False)
if check:
check_bad_exit_code(szip_x.returncode)
if not os.path.isdir(out_path):
raise Exception('EXTRACT_DIR_MISSING')
except Exception:
raise OSError(f'Extraction directory not found: {out_path}')
except Exception as error: # pylint: disable=broad-except
if not silent:
printer(f'Error: 7-Zip could not extract {in_name} file {in_path}!', padding)
printer(f'Error: 7-Zip could not extract {in_name} file {in_path}: {error}!', padding)
return 1
if not silent:
printer(f'Succesfull {in_name} decompression via 7-Zip!', padding)
return 0

View file

@ -1,38 +1,66 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
from common.path_ops import project_root, safe_path
from common.system import get_os_ver
# https://github.com/allowitsme/big-tool by Dmitry Frolov
# https://github.com/platomav/BGScriptTool by Plato Mavropoulos
def get_bgs_tool():
"""
https://github.com/allowitsme/big-tool by Dmitry Frolov
https://github.com/platomav/BGScriptTool by Plato Mavropoulos
"""
try:
# noinspection PyUnresolvedReferences
from external.big_script_tool import BigScript # pylint: disable=E0401,E0611
except Exception:
BigScript = None
return BigScript
from external.big_script_tool import BigScript # pylint: disable=C0415
# Get UEFIFind path
def get_uefifind_path():
exec_name = f'UEFIFind{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external', exec_name])
return BigScript
except ModuleNotFoundError:
pass
# Get UEFIExtract path
def get_uefiextract_path():
exec_name = f'UEFIExtract{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external', exec_name])
return None
def get_comextract_path() -> str:
""" Get ToshibaComExtractor path """
# Get ToshibaComExtractor path
def get_comextract_path():
exec_name = f'comextract{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external', exec_name])
def get_szip_path() -> str:
""" Get 7-Zip path """
exec_name = '7z.exe' if get_os_ver()[1] else '7zzs'
return safe_path(project_root(), ['external', exec_name])
def get_tiano_path() -> str:
""" Get TianoCompress path """
exec_name = f'TianoCompress{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external', exec_name])
def get_uefifind_path() -> str:
""" Get UEFIFind path """
exec_name = f'UEFIFind{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external', exec_name])
def get_uefiextract_path() -> str:
""" Get UEFIExtract path """
exec_name = f'UEFIExtract{".exe" if get_os_ver()[1] else ""}'
return safe_path(project_root(), ['external', exec_name])

View file

@ -1,14 +1,19 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
# https://leancrew.com/all-this/2020/06/ordinals-in-python/ by Dr. Drang
def get_ordinal(number):
s = ('th', 'st', 'nd', 'rd') + ('th',) * 10
v = number % 100
return f'{number}{s[v % 10]}' if v > 13 else f'{number}{s[v]}'
"""
Get ordinal (textual) representation of input numerical value
https://leancrew.com/all-this/2020/06/ordinals-in-python/ by Dr. Drang
"""
txt = ('th', 'st', 'nd', 'rd') + ('th',) * 10
val = number % 100
return f'{number}{txt[val % 10]}' if val > 13 else f'{number}{txt[val]}'

View file

@ -1,154 +1,213 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import os
import re
import sys
import stat
import shutil
import stat
import sys
from pathlib import Path, PurePath
from common.system import get_os_ver
from common.text_ops import is_encased, to_string
# Fix illegal/reserved Windows characters
MAX_WIN_COMP_LEN = 255
def safe_name(in_name):
"""
Fix illegal/reserved Windows characters
Can also be used to nuke dangerous paths
"""
name_repr = repr(in_name).strip("'")
return re.sub(r'[\\/:"*?<>|]+', '_', name_repr)
# Check and attempt to fix illegal/unsafe OS path traversals
def safe_path(base_path, user_paths):
""" Check and attempt to fix illegal/unsafe OS path traversals """
# Convert base path to absolute path
base_path = real_path(base_path)
# Merge user path(s) to string with OS separators
user_path = to_string(user_paths, os.sep)
# Create target path from base + requested user path
target_path = norm_path(base_path, user_path)
# Check if target path is OS illegal/unsafe
if is_safe_path(base_path, target_path):
return target_path
# Re-create target path from base + leveled/safe illegal "path" (now file)
nuked_path = norm_path(base_path, safe_name(user_path))
# Check if illegal path leveling worked
if is_safe_path(base_path, nuked_path):
return nuked_path
# Still illegal, raise exception to halt execution
raise Exception(f'ILLEGAL_PATH_TRAVERSAL: {user_path}')
# Check for illegal/unsafe OS path traversal
# Still illegal, raise exception to halt execution
raise OSError(f'Encountered illegal path traversal: {user_path}')
def is_safe_path(base_path, target_path):
""" Check for illegal/unsafe OS path traversal """
base_path = real_path(base_path)
target_path = real_path(target_path)
common_path = os.path.commonpath((base_path, target_path))
return base_path == common_path
# Create normalized base path + OS separator + user path
def norm_path(base_path, user_path):
""" Create normalized base path + OS separator + user path """
return os.path.normpath(base_path + os.sep + user_path)
# Get absolute path, resolving any symlinks
def real_path(in_path):
""" Get absolute path, resolving any symlinks """
return os.path.realpath(in_path)
# Get Windows/Posix OS agnostic path
def agnostic_path(in_path):
""" Get Windows/Posix OS agnostic path """
return PurePath(in_path.replace('\\', os.sep))
# Get absolute parent of path
def path_parent(in_path):
""" Get absolute parent of path """
return Path(in_path).parent.absolute()
# Get final path component, with suffix
def path_name(in_path):
return PurePath(in_path).name
# Get final path component, w/o suffix
def path_name(in_path, limit=False):
""" Get final path component, with suffix """
comp_name = PurePath(in_path).name
if limit and get_os_ver()[1]:
comp_name = comp_name[:MAX_WIN_COMP_LEN - len(extract_suffix())]
return comp_name
def path_stem(in_path):
""" Get final path component, w/o suffix """
return PurePath(in_path).stem
# Get list of path file extensions
def path_suffixes(in_path):
""" Get list of path file extensions """
return PurePath(in_path).suffixes or ['']
# Check if path is absolute
def is_path_absolute(in_path):
""" Check if path is absolute """
return Path(in_path).is_absolute()
# Create folder(s), controlling parents, existence and prior deletion
def make_dirs(in_path, parents=True, exist_ok=False, delete=False):
""" Create folder(s), controlling parents, existence and prior deletion """
if delete:
del_dirs(in_path)
Path.mkdir(Path(in_path), parents=parents, exist_ok=exist_ok)
# Delete folder(s), if present
def del_dirs(in_path):
if Path(in_path).is_dir():
shutil.rmtree(in_path, onerror=clear_readonly)
# Copy file to path with or w/o metadata
def del_dirs(in_path):
""" Delete folder(s), if present """
if Path(in_path).is_dir():
shutil.rmtree(in_path, onerror=clear_readonly_callback)
def copy_file(in_path, out_path, meta=False):
""" Copy file to path with or w/o metadata """
if meta:
shutil.copy2(in_path, out_path)
else:
shutil.copy(in_path, out_path)
# Clear read-only file attribute (on shutil.rmtree error)
def clear_readonly(in_func, in_path, _):
def clear_readonly(in_path):
""" Clear read-only file attribute """
os.chmod(in_path, stat.S_IWRITE)
def clear_readonly_callback(in_func, in_path, _):
""" Clear read-only file attribute (on shutil.rmtree error) """
clear_readonly(in_path)
in_func(in_path)
# Walk path to get all files
def get_path_files(in_path):
""" Walk path to get all files """
path_files = []
for root, _, files in os.walk(in_path):
for name in files:
path_files.append(os.path.join(root, name))
return path_files
# Get path without leading/trailing quotes
def get_dequoted_path(in_path):
""" Get path without leading/trailing quotes """
out_path = to_string(in_path).strip()
if len(out_path) >= 2 and is_encased(out_path, ("'",'"')):
if len(out_path) >= 2 and is_encased(out_path, ("'", '"')):
out_path = out_path[1:-1]
return out_path
# Set utility extraction stem
def extract_suffix():
""" Set utility extraction stem """
return '_extracted'
# Get utility extraction path
def get_extract_path(in_path, suffix=extract_suffix()):
""" Get utility extraction path """
return f'{in_path}{suffix}'
# Get project's root directory
def project_root():
root = Path(__file__).parent.parent
return real_path(root)
# Get runtime's root directory
def project_root():
""" Get project's root directory """
return real_path(Path(__file__).parent.parent)
def runtime_root():
""" Get runtime's root directory """
if getattr(sys, 'frozen', False):
root = Path(sys.executable).parent
else:
root = project_root()
return real_path(root)

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import re

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import pefile
@ -10,40 +10,57 @@ import pefile
from common.system import printer
from common.text_ops import file_to_bytes
# Check if input is a PE file
def is_pe_file(in_file):
return bool(get_pe_file(in_file))
# Get pefile object from PE file
def get_pe_file(in_file, fast=True):
def is_pe_file(in_file: str | bytes) -> bool:
""" Check if input is a PE file """
return bool(get_pe_file(in_file, silent=True))
def get_pe_file(in_file: str | bytes, padding: int = 0, fast: bool = True, silent: bool = False) -> pefile.PE | None:
""" Get pefile object from PE file """
in_buffer = file_to_bytes(in_file)
pe_file = None
try:
# Analyze detected MZ > PE image buffer
pe_file = pefile.PE(data=in_buffer, fast_load=fast)
except Exception:
pe_file = None
except Exception as error: # pylint: disable=broad-except
if not silent:
_filename = in_file if type(in_file).__name__ == 'string' else 'buffer'
printer(f'Error: Could not get pefile object from {_filename}: {error}!', padding)
return pe_file
# Get PE info from pefile object
def get_pe_info(pe_file):
def get_pe_info(pe_file: pefile.PE, padding: int = 0, silent: bool = False) -> dict:
""" Get PE info from pefile object """
pe_info = {}
try:
# When fast_load is used, IMAGE_DIRECTORY_ENTRY_RESOURCE must be parsed prior to FileInfo > StringTable
pe_file.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_RESOURCE']])
# Retrieve MZ > PE > FileInfo > StringTable information
pe_info = pe_file.FileInfo[0][0].StringTable[0].entries
except Exception:
pe_info = {}
except Exception as error: # pylint: disable=broad-except
if not silent:
printer(f'Error: Could not get PE info from pefile object: {error}!', padding)
return pe_info
# Print PE info from pefile StringTable
def show_pe_info(pe_info, padding=0):
if type(pe_info).__name__ == 'dict':
for title,value in pe_info.items():
info_title = title.decode('utf-8','ignore').strip()
info_value = value.decode('utf-8','ignore').strip()
def show_pe_info(pe_info: dict, padding: int = 0) -> None:
""" Print PE info from pefile StringTable """
if isinstance(pe_info, dict):
for title, value in pe_info.items():
info_title = title.decode('utf-8', 'ignore').strip()
info_value = value.decode('utf-8', 'ignore').strip()
if info_title and info_value:
printer(f'{info_title}: {info_value}', padding, new_line=False)

View file

@ -1,26 +1,32 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import ctypes
char = ctypes.c_char
uint8_t = ctypes.c_ubyte
uint16_t = ctypes.c_ushort
uint32_t = ctypes.c_uint
uint64_t = ctypes.c_uint64
Char: type[ctypes.c_char] | int = ctypes.c_char
UInt8: type[ctypes.c_ubyte] | int = ctypes.c_ubyte
UInt16: type[ctypes.c_ushort] | int = ctypes.c_ushort
UInt32: type[ctypes.c_uint] | int = ctypes.c_uint
UInt64: type[ctypes.c_uint64] | int = ctypes.c_uint64
# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
def get_struct(buffer, start_offset, class_name, param_list=None):
if param_list is None:
param_list = []
"""
https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky
"""
parameters = [] if param_list is None else param_list
structure = class_name(*parameters) # Unpack parameter list
structure = class_name(*param_list) # Unpack parameter list
struct_len = ctypes.sizeof(structure)
struct_data = buffer[start_offset:start_offset + struct_len]
fit_len = min(len(struct_data), struct_len)
ctypes.memmove(ctypes.addressof(structure), struct_data, fit_len)

View file

@ -1,68 +1,84 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import sys
from common.text_ops import padder, to_string
# Get Python Version (tuple)
def get_py_ver():
""" Get Python Version (tuple) """
return sys.version_info
# Get OS Platform (string)
def get_os_ver():
""" Get OS Platform (string) """
sys_os = sys.platform
is_win = sys_os == 'win32'
is_lnx = sys_os.startswith('linux') or sys_os == 'darwin' or sys_os.find('bsd') != -1
return sys_os, is_win, is_win or is_lnx
# Check for --auto-exit|-e
def is_auto_exit():
""" Check for --auto-exit|-e """
return bool('--auto-exit' in sys.argv or '-e' in sys.argv)
# Check Python Version
def check_sys_py():
""" # Check Python Version """
sys_py = get_py_ver()
if sys_py < (3,10):
if sys_py < (3, 10):
sys.stdout.write(f'\nError: Python >= 3.10 required, not {sys_py[0]}.{sys_py[1]}!')
if not is_auto_exit():
# noinspection PyUnresolvedReferences
(raw_input if sys_py[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602
(raw_input if sys_py[0] <= 2 else input)('\nPress enter to exit') # pylint: disable=E0602
sys.exit(125)
# Check OS Platform
def check_sys_os():
os_tag,os_win,os_sup = get_os_ver()
""" Check OS Platform """
os_tag, os_win, os_sup = get_os_ver()
if not os_sup:
printer(f'Error: Unsupported platform "{os_tag}"!')
if not is_auto_exit():
input('\nPress enter to exit')
sys.exit(126)
sys.exit(126)
# Fix Windows Unicode console redirection
if os_win:
# noinspection PyUnresolvedReferences
sys.stdout.reconfigure(encoding='utf-8')
# Show message(s) while controlling padding, newline, pausing & separator
def printer(in_message='', padd_count=0, new_line=True, pause=False, sep_char=' '):
message = to_string(in_message, sep_char)
padding = padder(padd_count)
def printer(message=None, padd=0, new_line=True, pause=False, sep_char=' '):
""" Show message(s), controlling padding, newline, pausing & separator """
message_input = '' if message is None else message
string = to_string(message_input, sep_char)
padding = padder(padd)
newline = '\n' if new_line else ''
output = newline + padding + message
(input if pause and not is_auto_exit() else print)(output)
message_output = newline + padding + string
(input if pause and not is_auto_exit() else print)(message_output)

View file

@ -1,111 +1,122 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
import argparse
import ctypes
import os
import sys
import ctypes
import argparse
import traceback
from common.num_ops import get_ordinal
from common.path_ops import get_dequoted_path, get_extract_path, get_path_files, is_path_absolute, path_parent, runtime_root, safe_path
from common.path_ops import (get_dequoted_path, get_extract_path, get_path_files,
is_path_absolute, path_name, path_parent, real_path, runtime_root)
from common.system import check_sys_os, check_sys_py, get_os_ver, is_auto_exit, printer
class BIOSUtility:
""" Template utility class for BIOSUtilities """
MAX_FAT32_ITEMS = 65535
def __init__(self, title, check, main, padding=0):
def __init__(self, title, check, main, args=None, padding=0):
self._title = title
self._main = main
self._check = check
self._arg_defs = args if args is not None else []
self._padding = padding
self._arguments_kw = {}
self._arguments_kw_dest = []
# Initialize argparse argument parser
self._argparser = argparse.ArgumentParser()
self._argparser.add_argument('files', type=argparse.FileType('r', encoding='utf-8'), nargs='*')
self._argparser.add_argument('-e', '--auto-exit', help='skip all user action prompts', action='store_true')
self._argparser.add_argument('-v', '--version', help='show utility name and version', action='store_true')
self._argparser.add_argument('-o', '--output-dir', help='extract in given output directory')
self._argparser.add_argument('-i', '--input-dir', help='extract from given input directory')
self._arguments,self._arguments_unk = self._argparser.parse_known_args()
for _arg_def in self._arg_defs:
_action = self._argparser.add_argument(*_arg_def[0], **_arg_def[1])
self._arguments_kw_dest.append(_action.dest)
self._arguments, _ = self._argparser.parse_known_args()
for _arg_dest in self._arguments_kw_dest:
self._arguments_kw.update({_arg_dest: self._arguments.__dict__[_arg_dest]})
# Managed Python exception handler
sys.excepthook = self._exception_handler
# Check Python Version
check_sys_py()
# Check OS Platform
check_sys_os()
# Show Script Title
printer(self._title, new_line=False)
# Show Utility Version on demand
if self._arguments.version:
sys.exit(0)
# Set console/terminal window title (Windows only)
if get_os_ver()[1]:
ctypes.windll.kernel32.SetConsoleTitleW(self._title)
# Process input files and generate output path
self._process_input_files()
# Count input files for exit code
self._exit_code = len(self._input_files)
def parse_argument(self, *args, **kwargs):
_dest = self._argparser.add_argument(*args, **kwargs).dest
self._arguments = self._argparser.parse_known_args(self._arguments_unk)[0]
self._arguments_kw.update({_dest: self._arguments.__dict__[_dest]})
def run_utility(self):
""" Run utility after checking for supported format """
for _input_file in self._input_files:
_input_name = os.path.basename(_input_file)
_input_name = path_name(_input_file, limit=True)
printer(['***', _input_name], self._padding)
if not self._check(_input_file):
printer('Error: This is not a supported input!', self._padding + 4)
continue # Next input file
continue # Next input file
_extract_path = os.path.join(self._output_path, get_extract_path(_input_name))
if os.path.isdir(_extract_path):
for _suffix in range(2, self.MAX_FAT32_ITEMS):
_renamed_path = f'{os.path.normpath(_extract_path)}_{get_ordinal(_suffix)}'
if not os.path.isdir(_renamed_path):
_extract_path = _renamed_path
break # Extract path is now unique
break # Extract path is now unique
if self._main(_input_file, _extract_path, self._padding + 4, **self._arguments_kw) in [0, None]:
self._exit_code -= 1
printer('Done!', pause=True)
sys.exit(self._exit_code)
# Process input files
def _process_input_files(self):
self._input_files = []
if len(sys.argv) >= 2:
# Drag & Drop or CLI
if self._arguments.input_dir:
_input_path_user = self._arguments.input_dir
_input_path_full = self._get_input_path(_input_path_user) if _input_path_user else ''
_input_path_full = self._get_user_path(_input_path_user) if _input_path_user else ''
self._input_files = get_path_files(_input_path_full)
else:
# Parse list of input files (i.e. argparse FileType objects)
@ -114,25 +125,25 @@ class BIOSUtility:
self._input_files.append(_file_object.name)
# Close each argparse FileType object (i.e. allow input file changes)
_file_object.close()
# Set output fallback value for missing argparse Output and Input Path
_output_fallback = path_parent(self._input_files[0]) if self._input_files else None
# Set output path via argparse Output path or argparse Input path or first input file path
_output_path = self._arguments.output_dir or self._arguments.input_dir or _output_fallback
else:
# Script w/o parameters
_input_path_user = get_dequoted_path(input('\nEnter input directory path: '))
_input_path_full = self._get_input_path(_input_path_user) if _input_path_user else ''
_input_path_full = self._get_user_path(_input_path_user) if _input_path_user else ''
self._input_files = get_path_files(_input_path_full)
_output_path = get_dequoted_path(input('\nEnter output directory path: '))
self._output_path = self._get_input_path(_output_path)
# Get absolute input file path
self._output_path = self._get_user_path(_output_path)
# Get absolute user file path
@staticmethod
def _get_input_path(input_path):
def _get_user_path(input_path):
if not input_path:
# Use runtime directory if no user path is specified
absolute_path = runtime_root()
@ -142,8 +153,8 @@ class BIOSUtility:
absolute_path = input_path
# Otherwise, make it runtime directory relative
else:
absolute_path = safe_path(runtime_root(), input_path)
absolute_path = real_path(input_path)
return absolute_path
# https://stackoverflow.com/a/781074 by Torsten Marek
@ -153,7 +164,7 @@ class BIOSUtility:
printer('')
else:
printer('Error: Utility crashed, please report the following:\n')
traceback.print_exception(exc_type, exc_value, exc_traceback)
if not is_auto_exit():

View file

@ -1,33 +1,48 @@
#!/usr/bin/env python3
#coding=utf-8
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Copyright (C) 2022 Plato Mavropoulos
Copyright (C) 2022-2024 Plato Mavropoulos
"""
# Generate padding (spaces or tabs)
def padder(padd_count, tab=False):
""" Generate padding (spaces or tabs) """
return ('\t' if tab else ' ') * padd_count
# Get String from given input object
def to_string(in_object, sep_char=''):
if type(in_object).__name__ in ('list','tuple'):
""" Get String from given input object """
if type(in_object).__name__ in ('list', 'tuple'):
out_string = sep_char.join(map(str, in_object))
else:
out_string = str(in_object)
return out_string
# Get Bytes from given buffer or file path
def file_to_bytes(in_object):
""" Get Bytes from given buffer or file path """
object_bytes = in_object
if type(in_object).__name__ not in ('bytes','bytearray'):
if type(in_object).__name__ not in ('bytes', 'bytearray'):
with open(to_string(in_object), 'rb') as object_data:
object_bytes = object_data.read()
return object_bytes
# Check if string starts and ends with given character(s)
def bytes_to_hex(buffer: bytes, order: str, data_len: int, slice_len: int | None = None) -> str:
""" Converts bytes to hex string, controlling endianess, data size and string slicing """
# noinspection PyTypeChecker
return f'{int.from_bytes(buffer, order):0{data_len * 2}X}'[:slice_len]
def is_encased(in_string, chars):
""" Check if string starts and ends with given character(s) """
return in_string.startswith(chars) and in_string.endswith(chars)