Completed
Push — master ( 18912f...ee8091 )
by John
01:16
created

make_sizes()   B

Complexity

Conditions 5

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
c 0
b 0
f 0
dl 0
loc 23
rs 8.2508
1
#!/usr/bin/env python3
2
"""This module is the Python-ized implementation of cap.exe"""
3
4
import os  # path work
5
import binascii  # to hex and back again
6
import glob  # filename matching
7
import base64  # storage
8
from bbarchivist import bbconstants  # versions/constants
9
from bbarchivist import utilities  # finding cap
10
11
__author__ = "Thurask"
12
__license__ = "WTFPL v2"
13
__copyright__ = "Copyright 2015-2016 Thurask"
14
15
16
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
    if not isinstance(intsize, int):
25
        intsize = int(intsize)
26
    hexsize = format(intsize, '08x')  # '00AABBCC'
27
    newlist = [hexsize[i:i + 2] for i in range(0, len(hexsize), 2)]  # ['00', 'AA','BB','CC']
28
    newlist.reverse()
29
    ghetto_hex = "".join(newlist)  # 'CCBBAA'
30
    ghetto_hex = ghetto_hex.rjust(16, '0')
31
    if len(ghetto_hex) == 16:
32
        return binascii.unhexlify(bytes(ghetto_hex.upper(), 'ascii'))
33
34
35
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
    first = str(glob.glob(filelist[0])[0])
43
    size1 = os.path.getsize(first)  # required
44
    size2 = size3 = size4 = size5 = 0
45
    if len(filelist) >= 2:
46
        second = str(glob.glob(filelist[1])[0])
47
        size2 = os.path.getsize(second)
48
    if len(filelist) >= 3:
49
        third = str(glob.glob(filelist[2])[0])
50
        size3 = os.path.getsize(third)
51
    if len(filelist) >= 4:
52
        fourth = str(glob.glob(filelist[3])[0])
53
        size4 = os.path.getsize(fourth)
54
    if len(filelist) >= 5:
55
        fifth = str(glob.glob(filelist[4])[0])
56
        size5 = os.path.getsize(fifth)
57
    return [size1, size2, size3, size4, size5]
58
59
60
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
   
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
76
    :param filecount: Number of signed files.
77
    :type filecount: int
78
    """
79
    firstoffset = beginlength + capsize
80
    start1 = ghetto_convert(firstoffset)
81
    start2 = start3 = start4 = start5 = start6 = pad * 8
82
    if filecount >= 2:
83
        secondoffset = firstoffset + sizes[0]  # start of second file
84
        start2 = ghetto_convert(secondoffset)
85
    if filecount >= 3:
86
        thirdoffset = secondoffset + sizes[1]  # start of third file
87
        start3 = ghetto_convert(thirdoffset)
88
    if filecount >= 4:
89
        fourthoffset = thirdoffset + sizes[2]  # start of fourth file
90
        start4 = ghetto_convert(fourthoffset)
91
    if filecount >= 5:
92
        fifthoffset = fourthoffset + sizes[3]  # start of fifth file
93
        start5 = ghetto_convert(fifthoffset)
94
    if filecount == 6:
95
        sixthoffset = fifthoffset + sizes[4]  # start of sixth file
96
        start6 = ghetto_convert(sixthoffset)
97
    return [start1, start2, start3, start4, start5, start6]
98
99
100
def make_offset(files, folder=None):
101
    """
102
    Create magic offset file 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
    folder = os.getcwd() if folder is None else folder
113
    capfile = utilities.grab_cap()
114
    filelist = [file for file in files if file]
115
    filecount = len(filelist)
116
    fcount = b'0' + bytes(str(filecount), 'ascii')
117
    # immutable things
118
    scaff = b'at9dFE5LTEdOT0hHR0lTCxcKDR4MFFMtPiU6LT0zPjs6Ui88U05GTVFOSUdRTlFOT3BwcJzVxZec1cWXnNXFlw=='
119
    separator = base64.b64decode(scaff)
120
    password = binascii.unhexlify(b'0' * 160)
121
    pad = b'\x00'  # 1x, 2x or 8x
122
    filepad = binascii.unhexlify(fcount)  # 01-06
123
    trailers = binascii.unhexlify(b'00' * (7 - filecount))  # 00, 1-6x
124
    capsize = os.path.getsize(capfile)
125
    if not filecount:  # we need at least one file
126
        raise SystemExit
127
    sizes = make_sizes(filelist)
128
    # start of first file; length of cap + length of offset
129
    beginlength = len(separator) + len(password) + 64
130
    starts = make_starts(beginlength, capsize, pad, sizes, filecount)
131
    makeuplen = 64 - 6 * len(pad * 8) - 2 * len(pad * 2) - 2 * \
132
        len(pad) - len(trailers) - len(filepad)
133
    makeup = b'\x00' * makeuplen  # pad to match offset begin
134
    with open(os.path.join(folder, "offset.hex"), "wb") as file:
135
        file.write(separator)
136
        file.write(password)
137
        file.write(filepad)
138
        file.write(pad * 2)
139
        file.write(pad)
140
        file.write(starts[0])
141
        file.write(starts[1])
142
        file.write(starts[2])
143
        file.write(starts[3])
144
        file.write(starts[4])
145
        file.write(starts[5])
146
        file.write(pad)
147
        file.write(pad * 2)
148
        file.write(trailers)
149
        file.write(makeup)
150
151
152
def write_4k(infile, outfile, text="FILE"):
153
    """
154
    Write 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
    with open(os.path.abspath(infile), "rb") as afile:
166
        print("WRITING {1}...\n{0}".format(os.path.basename(infile), text))
167
        while True:
168
            chunk = afile.read(4096)  # 4k chunks
169
            if not chunk:
170
                break
171
            outfile.write(chunk)
172
173
174
def make_autoloader(filename, files, folder=None):
175
    """
176
    Write cap.exe, magic offset, signed files to a .exe file.
177
    :func:`make_offset` is used to create the offset.
178
179
    :param filename: Name of autoloader.
180
    :type filename: str
181
182
    :param files: List of 1-6 signed files.
183
    :type files: list(str)
184
185
    :param folder: Working folder. Optional. Default is local.
186
    :type folder: str
187
    """
188
    folder = os.getcwd() if folder is None else folder
189
    make_offset(files, folder)
190
    filelist = [os.path.abspath(file) for file in files if file]
191
    print("CREATING: {0}".format(filename))
192
    try:
193
        with open(os.path.join(os.path.abspath(folder), filename), "wb") as autoloader:
194
            capskel = "CAP VERSION {0}".format(bbconstants.CAP.version)
195
            write_4k(os.path.normpath(utilities.grab_cap()), autoloader, capskel)
196
            write_4k(os.path.join(folder, "offset.hex"), autoloader, "MAGIC OFFSET")
197
            for file in filelist:
198
                write_4k(file, autoloader)
199
    except IOError as exc:
200
        print("Operation failed: {0}".format(exc.strerror))
201
    else:
202
        print("{0} FINISHED!".format(filename))
203
    os.remove(os.path.join(folder, "offset.hex"))
204