import argparse
from pathlib import Path
from zipfile import ZipFile

BUILD_MASK = bytes.fromhex(
    "22 80 3E 61 22 4B 54 20"
    "54 15 25 08 E3 10 A9 24"
    "16 AE 8A BF A3 34 0A 30"
    "B3 80 DB 8F 62 1C B1 8E"
)

def derive_password(filename: bytes, uncompressed_size: int) -> bytes:
    expanded = bytearray(filename)

    while len(expanded) < 32:
        previous = bytes(expanded)
        expanded += b"\x2D\x39\xC2" + previous

    size_mask = f"{uncompressed_size}. _".encode("ascii")

    password = bytes(
        value
        ^ size_mask[i % len(size_mask)]
        ^ BUILD_MASK[i % len(BUILD_MASK)]
        for i, value in enumerate(expanded)
    )

    return password.split(b"\x00", 1)[0]

def extract_archive(archive_path: Path, output_path: Path) -> None:
    with ZipFile(archive_path) as archive:
        for entry in archive.infolist():
            filename = entry.filename.encode("ascii")
            password = derive_password(filename, entry.file_size)

            archive.extract(entry, output_path, pwd=password)
            print(entry.filename)

def create_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Extract a compatible encrypted Leadwerks archive."
    )
    parser.add_argument("archive", type=Path, help="path to the Leadwerks archive")
    parser.add_argument("output", type=Path, help="directory for extracted files")
    return parser

def main() -> None:
    arguments = create_parser().parse_args()
    extract_archive(arguments.archive, arguments.output)

if __name__ == "__main__":
    main()
