Skip to content

Instantly share code, notes, and snippets.

@nosoop
Last active September 6, 2021 02:40
Show Gist options
  • Save nosoop/5cd7d8cd30818043bd84c160fb8c0ac8 to your computer and use it in GitHub Desktop.
Save nosoop/5cd7d8cd30818043bd84c160fb8c0ac8 to your computer and use it in GitHub Desktop.
Unpacking LZMA-compressed files from BSPs
#!/usr/bin/python3
import struct, lzma, sys
def unpack(fmt, stream):
'''
unpacks values from a stream
source: https://stackoverflow.com/a/17537253
'''
size = struct.calcsize(fmt)
buf = stream.read(size)
return struct.unpack(fmt, buf)
if __name__ == "__main__":
import argparse, os
parser = argparse.ArgumentParser(
description="Decompressese an LZMA file extracted from a Source Engine BSP.",
usage='%(prog)s FILE')
parser.add_argument('filepath', metavar='FILE', help="file to decompress")
args = parser.parse_args()
if not os.path.isfile(args.filepath):
raise ValueError("file not defined")
with open(f'{args.filepath}', mode='rb') as f:
# id, actual_size, lzma_size, *properties = unpack('<III5B', f)
# properties can be unpacked directly, [0] = d, [1:4] = dict_size
# see LzmaProps_Decode in mp/src/utils/lzma/C/LzmaDec.c
header_id, actual_size, lzma_size, d, dict_size = unpack('<IIIBI', f)
if not header_id == 0x414D5A4C:
raise AssertionError('Not a valid Source Engine (PC) LZMA file')
if d >= (9 * 5 * 5):
raise AssertionError('throw SZ_ERROR_UNSUPPORTED')
lc, pb, lp = d % 9, (d // 9) // 5, (d // 9) % 5
# TODO what filter do the other files use?
lzma_filter = [
{ 'id': lzma.FILTER_LZMA1, 'dict_size': dict_size, 'lc': lc, 'lp': lp, 'pb': pb, 'preset': 6}
]
decompressor = lzma.LZMADecompressor(format = lzma.FORMAT_RAW, filters = lzma_filter)
stream_decompressed = decompressor.decompress(f.read(), max_length = actual_size)
sys.stdout.write(stream_decompressed.decode('ascii'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment