#!/usr/bin/env python3
"""This module is the Python-ized implementation of cap.exe"""
import base64 # storage
import binascii # to hex and back again
import os # path work
from bbarchivist import bbconstants # versions/constants
from bbarchivist import utilities # finding cap
__author__ = "Thurask"
__license__ = "WTFPL v2"
__copyright__ = "2015-2018 Thurask"
[docs]def ghetto_convert(intsize):
"""
Convert from decimal integer to little endian
hexadecimal string, padded to 16 characters with zeros.
:param intsize: Integer you wish to convert.
:type intsize: int
"""
intsize = int(intsize) if not isinstance(intsize, int) else intsize
hexsize = format(intsize, '08x') # '00AABBCC'
newlist = [hexsize[i:i + 2] for i in range(0, len(hexsize), 2)] # ['00', 'AA','BB','CC']
newlist.reverse()
ghetto_hex = "".join(newlist) # 'CCBBAA'
ghetto_hex = ghetto_hex.rjust(16, '0')
return binascii.unhexlify(bytes(ghetto_hex.upper()[:16], 'ascii'))
[docs]def make_sizes(filelist):
"""
Get sizes of list of signed files.
:param filelist: List of 1-6 signed files.
:type filelist: list(str)
"""
return [os.path.getsize(x) if x else 0 for x in filelist]
[docs]def make_starts(beginlength, capsize, pad, sizes):
"""
Get list of starting positions for each signed file.
:param beginlength: Length of beginning offset.
:type beginlength: int
:param capsize: Size of cap executable.
:type capsize: int
:param pad: Padding character.
:type pad: bytes
:param sizes: List of signed file sizes.
:type sizes: list(int)
"""
starts = [pad*8, pad*8, pad*8, pad*8, pad*8, pad*8]
offsets = [0, 0, 0, 0, 0, 0]
for idx in range(len(sizes)):
offsets[idx] = beginlength+capsize if idx == 0 else offsets[idx-1] + sizes[idx-1]
starts[idx] = ghetto_convert(offsets[idx])
return starts
[docs]def make_offset(files, folder=None):
"""
Create magic offset for use in autoloader creation.
Cap.exe MUST match separator version.
Version defined in :data:`bbarchivist.bbconstants.CAP.version`.
:param files: List of 1-6 signed files.
:type files: list(str)
:param folder: Working folder. Optional. Default is local.
:type folder: str
"""
folder = utilities.dirhandler(folder, os.getcwd())
capfile = utilities.grab_cap()
filelist = [file for file in files if file]
fcount = b'0' + bytes(str(len(filelist)), 'ascii')
# immutable things
scaff = b'at9dFE5LTEdOT0hHR0lTCxcKDR4MFFMtPiU6LT0zPjs6Ui88U05GTVFOSUdRTlFOT3BwcJzVxZec1cWXnNXFlw=='
separator = base64.b64decode(scaff)
password = binascii.unhexlify(b'0' * 160)
pad = b'\x00' # 1x, 2x or 8x
filepad = binascii.unhexlify(fcount) # 01-06
trailers = binascii.unhexlify(b'00' * (7 - len(filelist))) # 00, 1-6x
capsize = os.path.getsize(capfile)
if not filelist: # we need at least one file
raise SystemExit
sizes = make_sizes(filelist)
# start of first file; length of cap + length of offset
beginlength = len(separator) + len(password) + 64
starts = make_starts(beginlength, capsize, pad, sizes)
makeuplen = 64 - 6 * len(pad * 8) - 2 * len(pad * 2) - 2 * len(pad) - len(trailers) - len(filepad)
makeup = b'\x00' * makeuplen # pad to match offset begin
magicoffset = [separator, password, filepad, pad, pad, pad, starts[0], starts[1], starts[2], starts[3], starts[4], starts[5], pad, pad, pad, trailers, makeup]
return b"".join(magicoffset)
[docs]def write_offset(inbytes, outfile):
"""
Write to a file from the offset bytestring.
:param inbytes: Bytestring.
:type inbytes: bytes
:param outfile: Open (!!!) file handle. Output file.
:type outfile: str
"""
print("WRITING MAGIC OFFSET")
outfile.write(inbytes)
[docs]def write_4k(infile, outfile, text="FILE"):
"""
Write to a file from another file, 4k bytes at a time.
:param infile: Filename. Input file.
:type infile: str
:param outfile: Open (!!!) file handle. Output file.
:type outfile: str
:param text: Writing <text>...
:type text: str
"""
with open(os.path.abspath(infile), "rb") as afile:
print("WRITING {1}...\n{0}".format(os.path.basename(infile), text))
while True:
chunk = afile.read(4096) # 4k chunks
if not chunk:
break
outfile.write(chunk)
[docs]def make_autoloader(filename, files, folder=None):
"""
Prepare for creation of autoloader.
:param filename: Name of autoloader.
:type filename: str
:param files: List of 1-6 signed files to add into autoloader.
:type files: list(str)
:param folder: Working folder. Optional, default is local.
:type folder: str
"""
folder = utilities.dirhandler(folder, os.getcwd())
offset = make_offset(files, folder)
filelist = [os.path.abspath(file) for file in files if file]
print("CREATING: {0}".format(filename))
write_autoloader_guard(filename, folder, offset, filelist)
[docs]def write_autoloader_guard(filename, folder, offset, filelist):
"""
Try/except guard for writing autoloader.
:param filename: Name of autoloader.
:type filename: str
:param folder: Working folder.
:type folder: str
:param offset: Offset bytestring.
:type offset: bytes
:param filelist: List of absolute filepaths to write into autoloader.
:type filelist: list(str)
"""
try:
write_autoloader(filename, folder, offset, filelist)
except IOError as exc:
print("Operation failed: {0}".format(exc.strerror))
else:
print("{0} FINISHED!".format(filename))
[docs]def write_autoloader(filename, folder, offset, filelist):
"""
Write cap.exe, magic offset, and signed files to a .exe file.
:param filename: Name of autoloader.
:type filename: str
:param folder: Working folder.
:type folder: str
:param offset: Offset bytestring.
:type offset: bytes
:param filelist: List of absolute filepaths to write into autoloader.
:type filelist: list(str)
"""
with open(os.path.join(os.path.abspath(folder), filename), "wb") as autoloader:
capskel = "CAP VERSION {0}".format(bbconstants.CAP.version)
write_4k(os.path.normpath(utilities.grab_cap()), autoloader, capskel)
write_offset(offset, autoloader)
for file in filelist:
write_4k(file, autoloader)