diff --git a/AMI_PFAT_Extract.py b/AMI_PFAT_Extract.py index 8a0bbc5..1ce55f9 100644 --- a/AMI_PFAT_Extract.py +++ b/AMI_PFAT_Extract.py @@ -7,12 +7,11 @@ AMI BIOS Guard Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'AMI BIOS Guard Extractor v4.0_a6' +title = 'AMI BIOS Guard Extractor v4.0_a7' import os import re import sys -import shutil import ctypes # Stop __pycache__ generation @@ -20,7 +19,7 @@ sys.dont_write_bytecode = True from common.externals import get_bgs_tool from common.num_ops import get_ordinal -from common.path_ops import get_safe_name +from common.path_ops import safe_name, make_dirs from common.patterns import PAT_AMI_PFAT from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t from common.system import script_init, argparse_init, printer @@ -139,7 +138,7 @@ def get_ami_pfat(input_buffer): return match, buffer def get_file_name(index, title): - return get_safe_name('%0.2d -- %s' % (index, title)) + return safe_name('%0.2d -- %s' % (index, title)) def parse_bg_script(script_data, padding): is_opcode_div = len(script_data) % 8 == 0 @@ -226,9 +225,7 @@ def parse_pfat_file(buffer, output_path, padding): extract_path = os.path.join(output_path + '_extracted') - if os.path.isdir(extract_path): shutil.rmtree(extract_path) - - os.mkdir(extract_path) + make_dirs(extract_path, delete=True) block_all,block_off,file_count = parse_pfat_hdr(buffer, padding) diff --git a/AMI_UCP_Extract.py b/AMI_UCP_Extract.py index 32c4ae2..ae2587e 100644 --- a/AMI_UCP_Extract.py +++ b/AMI_UCP_Extract.py @@ -7,16 +7,14 @@ AMI UCP BIOS Extractor Copyright (C) 2021-2022 Plato Mavropoulos """ -title = 'AMI UCP BIOS Extractor v2.0_a8' +title = 'AMI UCP BIOS Extractor v2.0_a9' import os import re import sys -import shutil import struct import ctypes import contextlib -from pathlib import Path, PurePath # Stop __pycache__ generation sys.dont_write_bytecode = True @@ -24,7 +22,7 @@ sys.dont_write_bytecode = True from common.a7z_comp import a7z_decompress, is_7z_supported from common.checksums import get_chk_16 from common.efi_comp import efi_decompress, is_efi_compressed -from common.path_ops import get_comp_path, get_safe_name, get_safe_path +from common.path_ops import agnostic_path, safe_name, safe_path, make_dirs from common.patterns import PAT_AMI_UCP, PAT_INTEL_ENG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t from common.system import script_init, argparse_init, printer @@ -215,9 +213,7 @@ def ucp_extract(buffer, out_path, ucp_tag='@UAF', padding=0, is_checksum=False): extract_path = os.path.join(out_path + '_extracted') - if os.path.isdir(extract_path): shutil.rmtree(extract_path) - - os.mkdir(extract_path) + make_dirs(extract_path, delete=True) uaf_hdr = get_struct(buffer, 0, UafHeader) # Parse @UAF|@HPU Header Structure @@ -283,13 +279,13 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na printer('Note: Detected new AMI UCP Module %s (%s) in @NAL!' % (uaf_tag, nal_dict[uaf_tag][1]), padding + 4, pause=True) # Generate @UAF|@HPU Module File name, depending on whether decompression will be required - uaf_sname = get_safe_name(uaf_name + ('.temp' if is_comp else uaf_fext)) + uaf_sname = safe_name(uaf_name + ('.temp' if is_comp else uaf_fext)) if uaf_tag in nal_dict: - uaf_npath = get_safe_path(extract_path, nal_dict[uaf_tag][0]) - Path.mkdir(Path(uaf_npath), parents=True, exist_ok=True) - uaf_fname = get_safe_path(uaf_npath, uaf_sname) + uaf_npath = safe_path(extract_path, nal_dict[uaf_tag][0]) + make_dirs(uaf_npath, exist_ok=True) + uaf_fname = safe_path(uaf_npath, uaf_sname) else: - uaf_fname = get_safe_path(extract_path, uaf_sname) + uaf_fname = safe_path(extract_path, uaf_sname) if is_checksum: chk16_validate(uaf_data_all, uaf_tag, padding + 4) @@ -387,15 +383,15 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na printer(info_tag + ' : ' + info_value, padding + 8, False) # Print @NAL Module Tag-Path Info - info_part = PurePath(get_comp_path(info_value)).parts # Split OS agnostic path in parts + info_part = agnostic_path(info_value).parts # Split OS agnostic path in parts info_path = to_string(info_part[1:-1], os.sep) # Get path without drive/root or file info_name = info_part[-1] # Get file from last path part nal_dict[info_tag] = (info_path,info_name) # Assign a file path & name to each Tag # Parse Insyde BIOS @UAF|@HPU Module (@INS) - if uaf_tag == '@INS' and is_7z_supported(uaf_fname): - ins_dir = os.path.join(extract_path, get_safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory + if uaf_tag == '@INS' and is_7z_supported(uaf_fname, padding + 4): + ins_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-SFX')) # Generate extraction directory printer('Insyde BIOS 7z SFX Archive:', padding + 4) @@ -406,7 +402,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na pfat_match,pfat_buffer = get_ami_pfat(uaf_data_raw) if pfat_match: - pfat_dir = os.path.join(extract_path, get_safe_name(uaf_name)) + pfat_dir = os.path.join(extract_path, safe_name(uaf_name)) parse_pfat_file(pfat_buffer, pfat_dir, padding + 4) @@ -422,7 +418,7 @@ def uaf_extract(buffer, extract_path, mod_info, padding=0, is_checksum=False, na # Parse Nested AMI UCP Structure if nested_uaf_off: - uaf_dir = os.path.join(extract_path, get_safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory + uaf_dir = os.path.join(extract_path, safe_name(uaf_tag + '_nested-UCP')) # Generate extraction directory ucp_extract(nested_uaf_bin, uaf_dir, nested_uaf_tag, padding + 4, is_checksum) # Call recursively diff --git a/Dell_PFS_Extract.py b/Dell_PFS_Extract.py index b679fdb..2e0610f 100644 --- a/Dell_PFS_Extract.py +++ b/Dell_PFS_Extract.py @@ -7,14 +7,13 @@ Dell PFS Update Extractor Copyright (C) 2018-2022 Plato Mavropoulos """ -title = 'Dell PFS Update Extractor v6.0_a5' +title = 'Dell PFS Update Extractor v6.0_a6' import os import io import sys import lzma import zlib -import shutil import ctypes import contextlib @@ -22,7 +21,7 @@ import contextlib sys.dont_write_bytecode = True from common.checksums import get_chk_8_xor -from common.path_ops import get_safe_name +from common.path_ops import safe_name, make_dirs from common.patterns import PAT_DELL_HDR, PAT_DELL_FTR, PAT_DELL_PKG from common.struct_ops import get_struct, char, uint8_t, uint16_t, uint32_t, uint64_t from common.system import script_init, argparse_init, printer @@ -243,13 +242,10 @@ def pfs_section_parse(zlib_data, zlib_start, output_path, pfs_name, pfs_index, p printer('Extracting Dell PFS %d >%s > %s' % (pfs_index, pfs_name, section_name), padding) # Set PFS ZLIB Section extraction sub-directory path - section_path = os.path.join(output_path, get_safe_name(section_name)) + section_path = os.path.join(output_path, safe_name(section_name)) - # Delete existing extraction sub-directory (not in recursions) - if os.path.isdir(section_path) and not is_rec: shutil.rmtree(section_path) - - # Create extraction sub-directory - if not os.path.isdir(section_path): os.makedirs(section_path) + # Create extraction sub-directory and delete old (if present, not in recursions) + make_dirs(section_path, delete=(not is_rec), parents=True, exist_ok=True) # Store the compressed zlib stream start offset compressed_start = zlib_start + 0xB @@ -406,7 +402,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i name_start = info_start + PFS_INFO_LEN + PFS_NAME_LEN # PFS Entry's FileName start offset name_size = entry_info_mod.CharacterCount * 2 # PFS Entry's FileName buffer total size name_data = filename_info[name_start:name_start + name_size] # PFS Entry's FileName buffer - entry_name = get_safe_name(name_data.decode('utf-16').strip()) # PFS Entry's FileName value + entry_name = safe_name(name_data.decode('utf-16').strip()) # PFS Entry's FileName value # Show PFS FileName Structure info if is_structure: @@ -443,7 +439,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i # As Nested PFS Entry Name, we'll use the actual PFS File Name # Replace common Windows reserved/illegal filename characters - entry_name = get_safe_name(entry_info.FileName.decode('utf-8').strip('.exe')) + entry_name = safe_name(entry_info.FileName.decode('utf-8').strip('.exe')) # As Nested PFS Entry Version, we'll use the actual PFS File Version entry_version = entry_info.FileVersion.decode('utf-8') @@ -537,7 +533,7 @@ def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, output_path, pfs_padd, i sub_pfs_name = ' %s v%s' % (info_all[pfs_count - 2][1], info_all[pfs_count - 2][2]) if info_all else ' UNKNOWN' # Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections) - sub_pfs_path = os.path.join(output_path, str(pfs_count) + get_safe_name(sub_pfs_name)) + sub_pfs_path = os.path.join(output_path, str(pfs_count) + safe_name(sub_pfs_name)) # Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count) pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count, True, pfs_padd + 4, is_structure, is_advanced) @@ -844,7 +840,7 @@ def chk_pfs_ftr(footer_buffer, data_buffer, data_size, text, padding, is_structu def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, is_structure=True, is_advanced=True): # Store Data/Metadata Signature (advanced users only) if bin_name.startswith('sign'): - final_name = '%s.%s.sig' % (get_safe_name(full_name), bin_name.split('_')[1]) + final_name = '%s.%s.sig' % (safe_name(full_name), bin_name.split('_')[1]) final_path = os.path.join(out_path, final_name) with open(final_path, 'wb') as pfs_out: pfs_out.write(bin_buff) # Write final Data/Metadata Signature @@ -857,7 +853,7 @@ def pfs_file_write(bin_buff, bin_name, bin_type, full_name, out_path, padding, i # Some Data may be Text or XML files with useful information for non-advanced users is_text,final_data,file_ext,write_mode = bin_is_text(bin_buff, bin_type, bin_name == 'meta', padding, is_structure, is_advanced) - final_name = '%s%s' % (get_safe_name(full_name), bin_ext[:-4] + file_ext if is_text else bin_ext) + final_name = '%s%s' % (safe_name(full_name), bin_ext[:-4] + file_ext if is_text else bin_ext) final_path = os.path.join(out_path, final_name) with open(final_path, write_mode) as pfs_out: pfs_out.write(final_data) # Write final Data/Metadata Payload diff --git a/README.md b/README.md index 8873988..2748655 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 4. Build/Freeze/Compile: -> pyinstaller --noupx --onefile \\/Dell_PFS_Extract.py +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/Dell_PFS_Extract.py You should find the final utility executable at "dist" folder @@ -121,7 +121,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 4. Build/Freeze/Compile: -> pyinstaller --noupx --onefile \\/AMI_UCP_Extract.py +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_UCP_Extract.py You should find the final utility executable at "dist" folder @@ -181,7 +181,7 @@ PyInstaller can build/freeze/compile the utility at all three supported platform 4. Build/Freeze/Compile: -> pyinstaller --noupx --onefile \\/AMI_PFAT_Extract.py +> pyinstaller --add-data="external/*;external/" --noupx --onefile \\/AMI_PFAT_Extract.py You should find the final utility executable at "dist" folder diff --git a/common/a7z_comp.py b/common/a7z_comp.py index 7226015..99de48a 100644 --- a/common/a7z_comp.py +++ b/common/a7z_comp.py @@ -1,10 +1,14 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + import os import subprocess -from common.path_ops import get_script_dir +from common.path_ops import project_root, safe_path from common.system import get_os_ver from common.system import printer @@ -12,29 +16,27 @@ from common.system import printer def get_7z_path(static=False): exec_name = '7z.exe' if get_os_ver()[1] else ('7zzs' if static else '7zz') - exec_path = os.path.join(get_script_dir(), '..', 'external', exec_name) - - return exec_path + return safe_path(project_root(), ['external',exec_name]) # Check if file is 7z supported -def is_7z_supported(in_path, static=False): +def is_7z_supported(in_path, padding=0, static=False): try: subprocess.run([get_7z_path(static), 't', in_path, '-bso0', '-bse0', '-bsp0'], check=True) - except: + printer('Error: 7-Zip could not check support for file %s!' % in_path, padding) + return False return True # Archive decompression via 7-Zip -def a7z_decompress(in_path, out_path, in_name, padding, static=False): +def a7z_decompress(in_path, out_path, in_name, padding=0, static=False): if not in_name: in_name = 'archive' try: subprocess.run([get_7z_path(static), 'x', '-aou', '-bso0', '-bse0', '-bsp0', '-o' + out_path, in_path], check=True) if not os.path.isdir(out_path): raise Exception('EXTRACT_DIR_MISSING') - except: printer('Error: 7-Zip could not extract %s file %s!' % (in_name, in_path), padding) diff --git a/common/checksums.py b/common/checksums.py index ea150e9..3e958ab 100644 --- a/common/checksums.py +++ b/common/checksums.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + # Get Checksum 16-bit def get_chk_16(data, value=0, order='little'): for idx in range(0, len(data), 2): diff --git a/common/efi_comp.py b/common/efi_comp.py index ed18cf5..97c05f9 100644 --- a/common/efi_comp.py +++ b/common/efi_comp.py @@ -1,10 +1,14 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + import os import subprocess -from common.path_ops import get_script_dir +from common.path_ops import project_root, safe_path from common.system import get_os_ver from common.system import printer @@ -25,22 +29,19 @@ def is_efi_compressed(data, strict=True): return check_diff and check_size # Get TianoCompress path -def tianocompress_path(): +def get_tiano_path(): exec_name = 'TianoCompress' + ('.exe' if get_os_ver()[1] else '') - exec_path = os.path.join(get_script_dir(), '..', 'external', exec_name) - - return exec_path + return safe_path(project_root(), ['external',exec_name]) # EFI/Tiano Decompression via TianoCompress -def efi_decompress(in_path, out_path, padding, comp_type='--uefi'): +def efi_decompress(in_path, out_path, padding=0, comp_type='--uefi'): try: - subprocess.run([tianocompress_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()) if os.path.getsize(out_path) != size_orig: raise Exception('EFI_DECOMPRESS_ERROR') - except: printer('Error: TianoCompress could not extract file %s!' % in_path, padding) diff --git a/common/externals.py b/common/externals.py index 674b65f..89d6a9e 100644 --- a/common/externals.py +++ b/common/externals.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + # https://github.com/allowitsme/big-tool by Dmitry Frolov def get_bgs_tool(): try: diff --git a/common/num_ops.py b/common/num_ops.py index 35e8044..c37e4d7 100644 --- a/common/num_ops.py +++ b/common/num_ops.py @@ -1,8 +1,11 @@ #!/usr/bin/env python3 #coding=utf-8 -# https://leancrew.com/all-this/2020/06/ordinals-in-python/ by Dr. Drang +""" +Copyright (C) 2022 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 diff --git a/common/path_ops.py b/common/path_ops.py index 0254a1c..d08cfb1 100644 --- a/common/path_ops.py +++ b/common/path_ops.py @@ -1,68 +1,89 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + import os import re import sys -import inspect -from pathlib import Path +import shutil +from pathlib import Path, PurePath from common.text_ops import to_string # Fix illegal/reserved Windows characters -def get_safe_name(in_name): +def safe_name(in_name): name_repr = repr(in_name).strip("'") return re.sub(r'[\\/:"*?<>|]+', '_', name_repr) # Check and attempt to fix illegal/unsafe OS path traversals -def get_safe_path(base_path, user_paths): +def safe_path(base_path, user_paths): # Convert base path to absolute path - base_path = get_real_path(base_path) + base_path = real_path(base_path) - # Convert user path(s) to absolute path with OS separators + # 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 = get_norm_path(base_path, 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 = get_norm_path(base_path, get_safe_name(user_path)) + 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, create fallback base path + placeholder file - failed_path = get_norm_path(base_path, 'illegal_path_traversal') - - return failed_path + # Still illegal, raise exception to halt execution + raise Exception(f'ILLEGAL_PATH_TRAVERSAL: {user_path}') # Check for illegal/unsafe OS path traversal def is_safe_path(base_path, target_path): - base_path = get_real_path(base_path) + base_path = real_path(base_path) - target_path = get_real_path(target_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 get_norm_path(base_path, user_path): +def norm_path(base_path, user_path): return os.path.normpath(base_path + os.sep + user_path) # Get absolute path, resolving any symlinks -def get_real_path(in_path): - return str(Path(in_path).resolve()) +def real_path(in_path): + return os.path.realpath(in_path) -# Get Windows/Posix OS compatible path -def get_comp_path(in_path): - return in_path.replace('\\', os.sep) +# Get Windows/Posix OS agnostic path +def agnostic_path(in_path): + return PurePath(in_path.replace('\\', os.sep)) + +# Get absolute parent of path +def path_parent(in_path): + return Path(in_path).parent.absolute() + +# Check if path is absolute +def is_path_absolute(in_path): + 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): + 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) # Walk path to get all files def get_path_files(in_path): @@ -74,20 +95,18 @@ def get_path_files(in_path): return path_files -# Get parent of path -def get_path_parent(in_path): - return Path(in_path).parent.absolute() - # Get absolute file path of argparse object def get_argparse_path(argparse_path): - script_dir = get_path_parent(get_script_dir()) - if not argparse_path: - absolute_path = script_dir # Use input file directory if no user path is specified + # Use runtime directory if no user path is specified + absolute_path = runtime_root() else: - # Check if user specified path is absolute, otherwise convert it to input file relative - if Path(argparse_path).is_absolute(): absolute_path = argparse_path - else: absolute_path = os.path.join(script_dir, argparse_path) + # Check if user specified path is absolute + if is_path_absolute(argparse_path): + absolute_path = argparse_path + # Otherwise, make it runtime directory relative + else: + absolute_path = safe_path(runtime_root(), argparse_path) return absolute_path @@ -104,22 +123,31 @@ def process_input_files(argparse_args, sys_argv=None): else: input_files = [file.name for file in argparse_args.files] - output_path = get_argparse_path(argparse_args.output_dir or argparse_args.input_dir) + # Set output path via argparse Output Path or argparse Input Path or first input file Path + output_path = argparse_args.output_dir or argparse_args.input_dir or path_parent(input_files[0]) else: # Script w/o parameters input_path_user = input('\nEnter input directory path: ') input_path_full = get_argparse_path(input_path_user) if input_path_user else '' input_files = get_path_files(input_path_full) - output_path = get_argparse_path(input('\nEnter output directory path: ')) + output_path = input('\nEnter output directory path: ') - return input_files, output_path + output_path_final = get_argparse_path(output_path) + + return input_files, output_path_final -# https://stackoverflow.com/a/22881871 by jfs -def get_script_dir(): +# Get project's root directory +def project_root(): + root = Path(__file__).parent.parent + + return real_path(root) + +# Get runtime's root directory +def runtime_root(): if getattr(sys, 'frozen', False): - path = sys.executable + root = Path(sys.executable).parent else: - path = inspect.getabsfile(get_script_dir) - - return os.path.dirname(get_real_path(path)) + root = project_root() + + return real_path(root) diff --git a/common/patterns.py b/common/patterns.py index 2e416f8..d66f268 100644 --- a/common/patterns.py +++ b/common/patterns.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + import re PAT_AMI_PFAT = re.compile(b'_AMIPFAT.AMI_BIOS_GUARD_FLASH_CONFIGURATIONS', re.DOTALL) diff --git a/common/struct_ops.py b/common/struct_ops.py index bd418da..31c6e5b 100644 --- a/common/struct_ops.py +++ b/common/struct_ops.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 #coding=utf-8 -# https://github.com/skochinsky/me-tools/blob/master/me_unpack.py by Igor Skochinsky +""" +Copyright (C) 2022 Plato Mavropoulos +""" import ctypes @@ -11,6 +13,7 @@ uint16_t = ctypes.c_ushort uint32_t = ctypes.c_uint uint64_t = 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 = [] diff --git a/common/system.py b/common/system.py index 8fcf156..3094ea7 100644 --- a/common/system.py +++ b/common/system.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + import sys import ctypes import argparse diff --git a/common/text_ops.py b/common/text_ops.py index dbb40a4..4a8ea41 100644 --- a/common/text_ops.py +++ b/common/text_ops.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 #coding=utf-8 +""" +Copyright (C) 2022 Plato Mavropoulos +""" + # Generate padding (spaces or tabs) def padder(padd_count, tab=False): return ('\t' if tab else ' ') * padd_count