Completed
Branch master (c0d60d)
by John
01:18
created

write_autoloader_guard()   A

Complexity

Conditions 3

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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