Completed
Push — master ( ff3ed3...d80a34 )
by John
03:11
created

make_autoloader()   B

Complexity

Conditions 6

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
c 1
b 0
f 0
dl 0
loc 23
ccs 10
cts 10
cp 1
crap 6
rs 7.6949
1
#!/usr/bin/env python3
2 4
"""This module is the Python-ized implementation of cap.exe"""
3
4 4
import os  # path work
5 4
import binascii  # to hex and back again
6 4
import glob  # filename matching
7 4
import base64  # storage
8 4
from bbarchivist import bbconstants  # versions/constants
9 4
from bbarchivist import utilities  # finding cap
10
11 4
__author__ = "Thurask"
12 4
__license__ = "WTFPL v2"
13 4
__copyright__ = "Copyright 2015-2016 Thurask"
14
15
16 4
def ghetto_convert(intsize):
17
    """
18
    Convert from decimal integer to little endian
19
    hexadecimal string, padded to 16 characters with zeros.
20
21
    :param intsize: Integer you wish to convert.
22
    :type intsize: int
23
    """
24 4
    if not isinstance(intsize, int):
25 4
        intsize = int(intsize)
26 4
    hexsize = format(intsize, '08x')  # '00AABBCC'
27 4
    newlist = [hexsize[i:i + 2] for i in range(0, len(hexsize), 2)]  # ['00', 'AA','BB','CC']
28 4
    newlist.reverse()
29 4
    ghetto_hex = "".join(newlist)  # 'CCBBAA'
30 4
    ghetto_hex = ghetto_hex.rjust(16, '0')
31 4
    if len(ghetto_hex) == 16:
32 4
        return binascii.unhexlify(bytes(ghetto_hex.upper(), 'ascii'))
33
34
35 4
def make_sizes(filelist):
36
    """
37
    Get sizes of list of signed files.
38
39
    :param filelist: List of 1-6 signed files.
40
    :type filelist: list(str)
41
    """
42 4
    first = str(glob.glob(filelist[0])[0])
43 4
    size1 = os.path.getsize(first)  # required
44 4
    size2 = size3 = size4 = size5 = 0
45 4
    if len(filelist) >= 2:
46 4
        second = str(glob.glob(filelist[1])[0])
47 4
        size2 = os.path.getsize(second)
48 4
    if len(filelist) >= 3:
49 4
        third = str(glob.glob(filelist[2])[0])
50 4
        size3 = os.path.getsize(third)
51 4
    if len(filelist) >= 4:
52 4
        fourth = str(glob.glob(filelist[3])[0])
53 4
        size4 = os.path.getsize(fourth)
54 4
    if len(filelist) >= 5:
55 4
        fifth = str(glob.glob(filelist[4])[0])
56 4
        size5 = os.path.getsize(fifth)
57 4
    return [size1, size2, size3, size4, size5]
58
59
60 4
def make_starts(beginlength, capsize, pad, sizes, filecount):
61
    """
62
    Get list of starting positions for each signed file.
63
64
    :param beginlength: Length of beginning offset.
65
    :type beginlength: int
66
67
    :param capsize: Size of cap executable.
68
    :type capsize: int
69
70
    :param pad: Padding character.
71
    :type pad: bytes
72
73
    :param sizes: List of signed file sizes.
74
    :type sizes: list(int)
75
76
    :param filecount: Number of signed files.
77
    :type filecount: int
78
    """
79 4
    firstoffset = beginlength + capsize
80 4
    start1 = ghetto_convert(firstoffset)
81 4
    start2 = start3 = start4 = start5 = start6 = pad * 8
82 4
    if filecount >= 2:
83 4
        secondoffset = firstoffset + sizes[0]  # start of second file
84 4
        start2 = ghetto_convert(secondoffset)
85 4
    if filecount >= 3:
86 4
        thirdoffset = secondoffset + sizes[1]  # start of third file
87 4
        start3 = ghetto_convert(thirdoffset)
88 4
    if filecount >= 4:
89 4
        fourthoffset = thirdoffset + sizes[2]  # start of fourth file
90 4
        start4 = ghetto_convert(fourthoffset)
91 4
    if filecount >= 5:
92 4
        fifthoffset = fourthoffset + sizes[3]  # start of fifth file
93 4
        start5 = ghetto_convert(fifthoffset)
94 4
    if filecount == 6:
95 4
        sixthoffset = fifthoffset + sizes[4]  # start of sixth file
96 4
        start6 = ghetto_convert(sixthoffset)
97 4
    return [start1, start2, start3, start4, start5, start6]
98
99
100 4
def make_offset(files, folder=None):
101
    """
102
    Create magic offset for use in autoloader creation.
103
    Cap.exe MUST match separator version.
104
    Version defined in :data:`bbarchivist.bbconstants.CAP.version`.
105
106
    :param files: List of 1-6 signed files.
107
    :type files: list(str)
108
109
    :param folder: Working folder. Optional. Default is local.
110
    :type folder: str
111
    """
112 4
    folder = os.getcwd() if folder is None else folder
113 4
    capfile = utilities.grab_cap()
114 4
    filelist = [file for file in files if file]
115 4
    filecount = len(filelist)
116 4
    fcount = b'0' + bytes(str(filecount), 'ascii')
117
    # immutable things
118 4
    scaff = b'at9dFE5LTEdOT0hHR0lTCxcKDR4MFFMtPiU6LT0zPjs6Ui88U05GTVFOSUdRTlFOT3BwcJzVxZec1cWXnNXFlw=='
119 4
    separator = base64.b64decode(scaff)
120 4
    password = binascii.unhexlify(b'0' * 160)
121 4
    pad = b'\x00'  # 1x, 2x or 8x
122 4
    filepad = binascii.unhexlify(fcount)  # 01-06
123 4
    trailers = binascii.unhexlify(b'00' * (7 - filecount))  # 00, 1-6x
124 4
    capsize = os.path.getsize(capfile)
125 4
    if not filecount:  # we need at least one file
126 4
        raise SystemExit
127 4
    sizes = make_sizes(filelist)
128
    # start of first file; length of cap + length of offset
129 4
    beginlength = len(separator) + len(password) + 64
130 4
    starts = make_starts(beginlength, capsize, pad, sizes, filecount)
131 4
    makeuplen = 64 - 6 * len(pad * 8) - 2 * len(pad * 2) - 2 * \
132
        len(pad) - len(trailers) - len(filepad)
133 4
    makeup = b'\x00' * makeuplen  # pad to match offset begin
134 4
    magicoffset = [separator, password, filepad, pad, pad, pad, starts[0], starts[1], starts[2], starts[3], starts[4], starts[5], pad, pad, pad, trailers, makeup]
135 4
    return b"".join(magicoffset)
136
137
138 4
def write_offset(inbytes, outfile):
139
    """
140
    Write to a file from the offset bytestring.
141
142
    :param inbytes: Bytestring.
143
    :type inbytes: bytes
144
145
    :param outfile: Open (!!!) file handle. Output file.
146
    :type outfile: str
147
    """
148 4
    print("WRITING MAGIC OFFSET")
149 4
    outfile.write(inbytes)
150
151
152 4
def write_4k(infile, outfile, text="FILE"):
153
    """
154
    Write to a file from another file, 4k bytes at a time.
155
156
    :param infile: Filename. Input file.
157
    :type infile: str
158
159
    :param outfile: Open (!!!) file handle. Output file.
160
    :type outfile: str
161
162
    :param text: Writing <text>...
163
    :type text: str
164
    """
165 4
    with open(os.path.abspath(infile), "rb") as afile:
166 4
        print("WRITING {1}...\n{0}".format(os.path.basename(infile), text))
167 4
        while True:
168 4
            chunk = afile.read(4096)  # 4k chunks
169 4
            if not chunk:
170 4
                break
171 4
            outfile.write(chunk)
172
173
174 4
def make_autoloader(filename, files, folder=None):
175
    """
176
    Prepare for creation of autoloader.
177
178
    :param filename: Name of autoloader.
179
    :type filename: str
180
181
    :param files: List of 1-6 signed files to add into autoloader.
182
    :type files: list(str)
183
184
    :param folder: Working folder. Optional, default is local.
185
    :type folder: str
186
    """
187 4
    folder = os.getcwd() if folder is None else folder
188 4
    offset = make_offset(files, folder)
189 4
    filelist = [os.path.abspath(file) for file in files if file]
190 4
    print("CREATING: {0}".format(filename))
191 4
    try:
192 4
        write_autoloader(filename, folder, offset, filelist)
193 4
    except IOError as exc:
194 4
        print("Operation failed: {0}".format(exc.strerror))
195
    else:
196 4
        print("{0} FINISHED!".format(filename))
197
198
199 4
def write_autoloader(filename, folder, offset, filelist):
200
    """
201
    Write cap.exe, magic offset, and signed files to a .exe file.
202
203
    :param filename: Name of autoloader.
204
    :type filename: str
205
206
    :param folder: Working folder.
207
    :type folder: str
208
209
    :param offset: Offset bytestring.
210
    :type offset: bytes
211
212
    :param filelist: List of absolute filepaths to write into autoloader.
213
    :type filelist: list(str)
214
    """
215 4
    with open(os.path.join(os.path.abspath(folder), filename), "wb") as autoloader:
216 4
        capskel = "CAP VERSION {0}".format(bbconstants.CAP.version)
217 4
        write_4k(os.path.normpath(utilities.grab_cap()), autoloader, capskel)
218 4
        write_offset(offset, autoloader)
219 4
        for file in filelist:
220
            write_4k(file, autoloader)
221