Completed
Push — master ( d80a34...5afa3d )
by John
02:41
created

make_starts()   A

Complexity

Conditions 3

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
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 7
cts 7
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 glob  # filename matching
0 ignored issues
show
Unused Code introduced by
The import glob seems to be unused.
Loading history...
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
    intsize = int(intsize) if not isinstance(intsize, int) else intsize
25 4
    hexsize = format(intsize, '08x')  # '00AABBCC'
26 4
    newlist = [hexsize[i:i + 2] for i in range(0, len(hexsize), 2)]  # ['00', 'AA','BB','CC']
27 4
    newlist.reverse()
28 4
    ghetto_hex = "".join(newlist)  # 'CCBBAA'
29 4
    ghetto_hex = ghetto_hex.rjust(16, '0')
30 4
    if len(ghetto_hex) == 16:
31 4
        return binascii.unhexlify(bytes(ghetto_hex.upper(), 'ascii'))
32
33
34 4
def make_sizes(filelist):
35
    """
36
    Get sizes of list of signed files.
37
38
    :param filelist: List of 1-6 signed files.
39
    :type filelist: list(str)
40
    """
41 4
    return [os.path.getsize(x) if x else 0 for x in filelist]
42
43
44 4
def make_starts(beginlength, capsize, pad, sizes):
45
    """
46
    Get list of starting positions for each signed file.
47
48
    :param beginlength: Length of beginning offset.
49
    :type beginlength: int
50
51
    :param capsize: Size of cap executable.
52
    :type capsize: int
53
54
    :param pad: Padding character.
55
    :type pad: bytes
56
57
    :param sizes: List of signed file sizes.
58
    :type sizes: list(int)
59
    """
60 4
    starts = [pad*8, pad*8, pad*8, pad*8, pad*8, pad*8]
61 4
    offsets = [0, 0, 0, 0, 0, 0]
62 4
    for idx, itm in enumerate(sizes):
0 ignored issues
show
Unused Code introduced by
The variable itm seems to be unused.
Loading history...
63 4
        offsets[idx] = beginlength+capsize if idx==0 else offsets[idx-1] + sizes[idx-1]
0 ignored issues
show
Coding Style introduced by
Exactly one space required around comparison
offsets[idx] = beginlength+capsize if idx==0 else offsets[idx-1] + sizes[idx-1]
^^
Loading history...
64 4
        starts[idx] = ghetto_convert(offsets[idx])
65 4
    return starts
66
67
68 4
def make_offset(files, folder=None):
69
    """
70
    Create magic offset for use in autoloader creation.
71
    Cap.exe MUST match separator version.
72
    Version defined in :data:`bbarchivist.bbconstants.CAP.version`.
73
74
    :param files: List of 1-6 signed files.
75
    :type files: list(str)
76
77
    :param folder: Working folder. Optional. Default is local.
78
    :type folder: str
79
    """
80 4
    folder = os.getcwd() if folder is None else folder
81 4
    capfile = utilities.grab_cap()
82 4
    filelist = [file for file in files if file]
83 4
    fcount = b'0' + bytes(str(len(filelist)), 'ascii')
84
    # immutable things
85 4
    scaff = b'at9dFE5LTEdOT0hHR0lTCxcKDR4MFFMtPiU6LT0zPjs6Ui88U05GTVFOSUdRTlFOT3BwcJzVxZec1cWXnNXFlw=='
86 4
    separator = base64.b64decode(scaff)
87 4
    password = binascii.unhexlify(b'0' * 160)
88 4
    pad = b'\x00'  # 1x, 2x or 8x
89 4
    filepad = binascii.unhexlify(fcount)  # 01-06
90 4
    trailers = binascii.unhexlify(b'00' * (7 - len(filelist)))  # 00, 1-6x
91 4
    capsize = os.path.getsize(capfile)
92 4
    if not len(filelist):  # we need at least one file
93 4
        raise SystemExit
94 4
    sizes = make_sizes(filelist)
95
    # start of first file; length of cap + length of offset
96 4
    beginlength = len(separator) + len(password) + 64
97 4
    starts = make_starts(beginlength, capsize, pad, sizes)
98 4
    makeuplen = 64 - 6 * len(pad * 8) - 2 * len(pad * 2) - 2 * \
99
        len(pad) - len(trailers) - len(filepad)
100 4
    makeup = b'\x00' * makeuplen  # pad to match offset begin
101 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]
102 4
    return b"".join(magicoffset)
103
104
105 4
def write_offset(inbytes, outfile):
106
    """
107
    Write to a file from the offset bytestring.
108
109
    :param inbytes: Bytestring.
110
    :type inbytes: bytes
111
112
    :param outfile: Open (!!!) file handle. Output file.
113
    :type outfile: str
114
    """
115 4
    print("WRITING MAGIC OFFSET")
116 4
    outfile.write(inbytes)
117
118
119 4
def write_4k(infile, outfile, text="FILE"):
120
    """
121
    Write to a file from another file, 4k bytes at a time.
122
123
    :param infile: Filename. Input file.
124
    :type infile: str
125
126
    :param outfile: Open (!!!) file handle. Output file.
127
    :type outfile: str
128
129
    :param text: Writing <text>...
130
    :type text: str
131
    """
132 4
    with open(os.path.abspath(infile), "rb") as afile:
133 4
        print("WRITING {1}...\n{0}".format(os.path.basename(infile), text))
134 4
        while True:
135 4
            chunk = afile.read(4096)  # 4k chunks
136 4
            if not chunk:
137 4
                break
138 4
            outfile.write(chunk)
139
140
141 4
def make_autoloader(filename, files, folder=None):
142
    """
143
    Prepare for creation of autoloader.
144
145
    :param filename: Name of autoloader.
146
    :type filename: str
147
148
    :param files: List of 1-6 signed files to add into autoloader.
149
    :type files: list(str)
150
151
    :param folder: Working folder. Optional, default is local.
152
    :type folder: str
153
    """
154 4
    folder = os.getcwd() if folder is None else folder
155 4
    offset = make_offset(files, folder)
156 4
    filelist = [os.path.abspath(file) for file in files if file]
157 4
    print("CREATING: {0}".format(filename))
158 4
    try:
159 4
        write_autoloader(filename, folder, offset, filelist)
160 4
    except IOError as exc:
161 4
        print("Operation failed: {0}".format(exc.strerror))
162
    else:
163 4
        print("{0} FINISHED!".format(filename))
164
165
166 4
def write_autoloader(filename, folder, offset, filelist):
167
    """
168
    Write cap.exe, magic offset, and signed files to a .exe file.
169
170
    :param filename: Name of autoloader.
171
    :type filename: str
172
173
    :param folder: Working folder.
174
    :type folder: str
175
176
    :param offset: Offset bytestring.
177
    :type offset: bytes
178
179
    :param filelist: List of absolute filepaths to write into autoloader.
180
    :type filelist: list(str)
181
    """
182 4
    with open(os.path.join(os.path.abspath(folder), filename), "wb") as autoloader:
183 4
        capskel = "CAP VERSION {0}".format(bbconstants.CAP.version)
184 4
        write_4k(os.path.normpath(utilities.grab_cap()), autoloader, capskel)
185 4
        write_offset(offset, autoloader)
186 4
        for file in filelist:
187
            write_4k(file, autoloader)
188