Example #1
0
    def test_write_string(self):
        w0 = wad.WadFile(self.buff, 'w')
        w0.writestr('test.cfg', b'bind ALT +strafe')
        w0.writestr(wad.WadInfo('readme.txt'), 'test')

        info = wad.WadInfo('bytes')
        info.file_size = len(b'bytes')
        info.type = wad.LumpType.LUMP
        w0.writestr(info, io.BytesIO(b'bytes'))

        w0.close()

        self.buff.seek(0)

        w1 = wad.WadFile(self.buff, 'r')

        self.assertTrue('test.cfg' in w1.namelist(), 'Cfg file should be in Wad file')
        self.assertTrue('readme.txt' in w1.namelist(), 'Txt file should be in Wad file')
        self.assertTrue('bytes' in w1.namelist(), 'Bytes should be in Wad file')
        self.assertEqual(w1.read('test.cfg'), b'bind ALT +strafe', 'Cfg file content should not change')
        self.assertEqual(w1.read('readme.txt').decode('ascii'), 'test', 'Txt file content should not change')
        self.assertEqual(w1.read('bytes'), b'bytes', 'Bytes content should not change')

        w1.close()
        self.buff.close()
Example #2
0
    def test_empty_pak_file(self):
        with wad.WadFile(self.buff, 'w'):
            pass

        self.buff.seek(0)

        with wad.WadFile(self.buff, 'r') as wad_file:
            self.assertEqual(len(wad_file.namelist()), 0, 'Wad file should have not entries')
            self.assertEqual(wad_file.end_of_data, 12, 'Directory should start immediately after header')
Example #3
0
    def test_zero_byte_file(self):
        with wad.WadFile(self.buff, 'w') as wad_file:
            wad_file.writestr('zero.txt', b'')

        self.buff.seek(0)

        with wad.WadFile(self.buff) as wad_file:
            info = wad_file.getinfo('zero.txt')
            self.assertEqual(info.file_offset, 12, 'File Info offset of test file should be 12')
            self.assertEqual(info.file_size, 0, 'File Info size of test file should be 0')

            data = wad_file.read('zero.txt')
            self.assertEqual(len(data), 0, 'Length of bytes read should be zero.')
Example #4
0
    def test_context_manager(self):
        with wad.WadFile('./test_data/test.wad', 'r') as wad_file:
            self.assertFalse(wad_file.fp.closed, 'File should be open')
            self.assertEqual(wad_file.mode, 'r', 'File mode should be \'r\'')
            fp = wad_file.fp
            wad_file._did_modify = False

        self.assertTrue(fp.closed, 'File should be closed')
        self.assertIsNone(wad_file.fp, 'File pointer should be cleaned up')
Example #5
0
    def test_append(self):
        f = open('./test_data/test.wad', 'rb')
        buff = io.BytesIO(f.read(-1))
        f.close()

        wad_file = wad.WadFile(buff, 'a')
        wad_file.write('./test_data/test.bsp')
        wad_file.close()

        buff.seek(0)

        wad_file = wad.WadFile(buff, 'r')
        self.assertTrue('test.bsp' in wad_file.namelist(), 'Appended file should be in Wad file')

        fp = wad_file.fp
        wad_file.close()

        self.assertFalse(buff.closed, 'Wad file should not close passed file-like object')

        buff.close()
Example #6
0
    def test_write(self):
        wad_file = wad.WadFile(self.buff, 'w')
        self.assertFalse(wad_file.fp.closed, 'File should be open')

        wad_file.write('./test_data/test.mdl')
        wad_file.write('./test_data/test.bsp', 'e1m1.bsp')

        self.assertTrue('test.mdl' in wad_file.namelist(), 'Mdl file should be in Wad file')
        self.assertTrue('e1m1.bsp' in wad_file.namelist(), 'Bsp file should be in Wad file')

        fp = wad_file.fp
        wad_file.close()
        self.assertFalse(fp.closed, 'File should be open')
        self.assertIsNone(wad_file.fp, 'File pointer should be cleaned up')

        self.buff.close()
Example #7
0
    def test_read(self):
        wad_file = wad.WadFile('./test_data/test.wad', 'r')
        self.assertFalse(wad_file.fp.closed, 'File should be open')

        info = wad_file.getinfo('test')
        self.assertIsNotNone(info, 'FileInfo should not be None')
        self.assertEqual(info.filename, 'test')
        self.assertEqual(info.file_size, 5480, 'FileInfo size of test file should be 5480')
        self.assertEqual(info.file_offset, 12, 'FileInfo offset of test file should be 12')
        self.assertEqual(info.type, wad.LumpType.MIPTEX, 'FileInfo type of test file should be MIPTEX')

        file = wad_file.open('test')
        self.assertIsNotNone(file, 'File should not be None')
        file.close()

        fp = wad_file.fp
        wad_file.close()
        self.assertTrue(fp.closed, 'File should be closed')
        self.assertIsNone(wad_file.fp, 'File pointer should be cleaned up')
Example #8
0
def make_quake_wad(start, end, progress):
    bsps = []
    miptextures = []

    for name in ['pak0.pak', 'pak1.pak']:
        with PakFile(str(dirs.data / 'id1' / name), 'r') as pak:
            for item in pak.namelist():
                if item.endswith('.bsp'):
                    bsps.append(pak.read(item))

    update(progress, start, end, 1, 3)

    for bsp_data in bsps:
        with Bsp.open(bsp_data) as bsp:
            miptextures += [
                mip for mip in bsp.miptextures
                if mip and mip.name not in [n.name for n in miptextures]
            ]

    update(progress, start, end, 2, 3)

    with wad.WadFile(str(dirs.data / 'id1' / 'quake.wad'),
                     mode='w') as wad_file:
        for miptex in miptextures:
            if not miptex:
                continue

            buff = BytesIO()
            wad.Miptexture.write(buff, miptex)
            buff.seek(0)

            info = wad.WadInfo(miptex.name)
            info.file_size = 40 + len(miptex.pixels)
            info.disk_size = info.file_size
            info.compression = wad.CompressionType.NONE
            info.type = wad.LumpType.MIPTEX

            wad_file.writestr(info, buff)

    update(progress, start, end, 3, 3)
Example #9
0
def main():
    """CLI entrypoint"""

    parser = Parser(
        prog='unwad',
        description=
        'Default action is to convert files to png format and extract to xdir.',
        epilog='example: unwad gfx.wad -d ./out => extract all files to ./out')

    parser.add_argument('file', metavar='file.wad', action=ResolvePathAction)

    parser.add_argument('-l', '--list', action='store_true', help='list files')

    parser.add_argument('-d',
                        metavar='xdir',
                        default=os.getcwd(),
                        dest='dest',
                        action=ResolvePathAction,
                        help='extract files into xdir')

    parser.add_argument('-q',
                        dest='quiet',
                        action='store_true',
                        help='quiet mode')

    parser.add_argument('-f',
                        dest='format',
                        default='png',
                        choices=['bmp', 'gif', 'png', 'tga'],
                        help='image format to convert to')

    parser.add_argument(
        '-v',
        '--version',
        dest='version',
        action='version',
        help=argparse.SUPPRESS,
        version=f'{parser.prog} version {qcli.unwad.__version__}')

    args = parser.parse_args()

    archive_name = os.path.basename(args.file)

    if not wad.is_wadfile(args.file):
        print(f'{parser.prog}: cannot find or open {args.file}',
              file=sys.stderr)
        sys.exit(1)

    if args.list:
        with wad.WadFile(args.file) as wad_file:
            info_list = sorted(wad_file.infolist(), key=lambda i: i.filename)

            lump_types = {
                0: 'NONE',
                1: 'LABEL',
                64: 'LUMP',
                65: 'QTEX',
                66: 'QPIC',
                67: 'SOUND',
                68: 'MIPTEX'
            }

            def lump_type(num):
                if num in lump_types:
                    return lump_types[num]

                return num

            headers = ['Length', 'Type', 'Name']
            table = [[i.file_size, lump_type(i.type), i.filename]
                     for i in info_list]
            length = sum([i.file_size for i in info_list])
            count = len(info_list)
            table.append(
                [length, '', f'{count} file{"s" if count > 1 else ""}'])

            separator = []
            for i in range(len(headers)):
                t = max(len(str(length)), len(headers[i]) + 2)
                separator.append('-' * t)

            table.insert(-1, separator)

            print(f'Archive: {archive_name}')
            print(tabulate(table, headers=headers))

            sys.exit(0)

    if not os.path.exists(args.dest):
        os.makedirs(args.dest)

    with wad.WadFile(args.file) as wad_file:
        if not args.quiet:
            print(f'Archive: {archive_name}')

        # Flatten out palette
        palette = []
        for p in quake.palette:
            palette += p

        for item in wad_file.infolist():
            filename = item.filename
            fullpath = os.path.join(args.dest, filename)
            fullpath_ext = '{0}.{1}'.format(fullpath, args.format)

            data = None
            size = None

            # Pictures
            if item.type == wad.LumpType.QPIC:
                with wad_file.open(filename) as lmp_file:
                    lump = lmp.Lmp.open(lmp_file)
                    size = lump.width, lump.height
                    data = array.array('B', lump.pixels)

            # Special cases
            elif item.type == wad.LumpType.MIPTEX:
                # Console characters
                if item.file_size == 128 * 128:
                    size = 128, 128

                    with wad_file.open(filename) as lump:
                        data = lump.read(item.file_size)

                else:
                    # Miptextures
                    try:
                        with wad_file.open(filename) as mip_file:
                            mip = wad.Miptexture.read(mip_file)
                            data = mip.pixels[:mip.width * mip.height]
                            data = array.array('B', data)
                            size = mip.width, mip.height
                    except:
                        print(f' failed to extract resource: {item.filename}',
                              file=sys.stderr)
                        continue

            try:
                # Convert to image file
                if data is not None and size is not None:
                    img = Image.frombuffer('P', size, data, 'raw', 'P', 0, 1)
                    img.putpalette(palette)
                    img.save(fullpath_ext)

                    if not args.quiet:
                        print(f' extracting: {fullpath_ext}')

                # Extract as raw file
                else:
                    wad_file.extract(filename, args.dest)

                    if not args.quiet:
                        print(f' extracting: {fullpath}')
            except:
                print(f'{parser.prog}: error: {sys.exc_info()[1]}',
                      file=sys.stderr)

    sys.exit(0)
Example #10
0
def main():
    parser = Parser(
        prog='bsp2wad',
        description='Default action is to create a wad archive from '
        'miptextures extracted from the given bsp file.'
        '\nIf list is omitted, pak will use stdin.',
        epilog='example: bsp2wad e1m1.bsp => creates the wad file e1m1.wad')

    parser.add_argument('list',
                        nargs='*',
                        action=ResolvePathAction,
                        default=read_from_stdin())

    parser.add_argument('-d',
                        metavar='file.wad',
                        dest='dest',
                        default=os.getcwd(),
                        action=ResolvePathAction,
                        help='wad file to create')

    parser.add_argument('-q',
                        dest='quiet',
                        action='store_true',
                        help='quiet mode')

    parser.add_argument(
        '-v',
        '--version',
        dest='version',
        action='version',
        help=argparse.SUPPRESS,
        version=f'{parser.prog} version {qcli.bsp2wad.__version__}')

    args = parser.parse_args()

    if not args.list:
        parser.error('the following arguments are required: list')

    miptextures = []

    for file in args.list:
        if not bsp.is_bspfile(file):
            print('{0}: cannot find or open {1}'.format(parser.prog, file),
                  file=sys.stderr)
            continue

        bsp_file = bsp.Bsp.open(file)
        miptextures += [
            mip for mip in bsp_file.miptextures
            if mip and mip.name not in [n.name for n in miptextures]
        ]

    if args.dest == os.getcwd():
        wad_path = os.path.dirname(file)

        if len(args.list) == 1:
            wad_name = f'{os.path.basename(file).split(".")[0]}.wad'

        else:
            wad_name = 'out.wad'

        args.dest = os.path.join(wad_path, wad_name)

    dir = os.path.dirname(args.dest) or '.'
    if not os.path.exists(dir):
        os.makedirs(dir)

    with wad.WadFile(args.dest, mode='w') as wad_file:
        if not args.quiet:
            print(f'Archive: {os.path.basename(args.dest)}')

        for miptex in miptextures:
            if not miptex:
                continue

            buff = io.BytesIO()
            wad.Miptexture.write(buff, miptex)
            buff.seek(0)

            info = wad.WadInfo(miptex.name)
            info.file_size = 40 + len(miptex.pixels)
            info.disk_size = info.file_size
            info.compression = wad.CompressionType.NONE
            info.type = wad.LumpType.MIPTEX

            if not args.quiet:
                print(f' adding: {info.filename}')

            wad_file.writestr(info, buff)

    sys.exit(0)
Example #11
0
def main():
    """CLI entrypoint"""

    # Create and configure argument parser
    parser = Parser(
        prog='wad',
        description='Default action is to add or replace wad file entries from'
        ' list.\nIf list is omitted, wad will use stdin.',
        formatter_class=argparse.RawTextHelpFormatter,
        epilog='example:\n  wad tex.wad image.png => adds image.png to tex.wad'
    )

    parser.add_argument('file',
                        metavar='file.wad',
                        action=ResolvePathAction,
                        help='wad file to add entries to')

    parser.add_argument('list',
                        nargs='*',
                        action=ResolvePathAction,
                        default=read_from_stdin())

    parser.add_argument('-t',
                        dest='type',
                        default='MIPTEX',
                        choices=['LUMP', 'QPIC', 'MIPTEX'],
                        help='list data type [default: MIPTEX]')

    parser.add_argument('-q',
                        dest='quiet',
                        action='store_true',
                        help='quiet mode')

    parser.add_argument('-v',
                        '--version',
                        dest='version',
                        action='version',
                        version='{} version {}'.format(parser.prog,
                                                       qcli.__version__))

    # Parse the arguments
    args = parser.parse_args()

    if not args.list:
        parser.error('the following arguments are required: list')

    if args.quiet:

        def log(message):
            pass

    else:

        def log(message):
            print(message)

    # Ensure directory structure
    dir = os.path.dirname(args.file) or '.'
    os.makedirs(dir, exist_ok=True)

    filemode = 'a'
    if not os.path.isfile(args.file):
        filemode = 'w'

    with wad.WadFile(args.file, filemode) as wad_file:
        log(f'Archive: {os.path.basename(args.file)}')

        # Flatten out palette
        palette = []
        for p in quake.palette:
            palette += p

        # Create palette image for Image.quantize()
        palette_image = Image.frombytes('P', (16, 16), bytes(palette))
        palette_image.putpalette(palette)

        # Process input files
        for file in args.list:
            if args.type == 'LUMP':
                log(f'  adding: {file}')
                wad_file.write(file)

            elif args.type == 'QPIC':
                img = Image.open(file).convert(mode='RGB')
                img = img.quantize(palette=palette_image)
                pixels = img.tobytes()
                name = os.path.basename(file).split('.')[0]

                qpic = lmp.Lmp()
                qpic.width = img.width
                qpic.height = img.height
                qpic.pixels = pixels

                buff = io.BytesIO()
                lmp.Lmp.write(buff, qpic)
                file_size = buff.tell()
                buff.seek(0)

                info = wad.WadInfo(name)
                info.file_size = file_size
                info.disk_size = info.file_size
                info.compression = wad.CompressionType.NONE
                info.type = wad.LumpType.QPIC

                log(f'  adding: {file}')

                wad_file.writestr(info, buff)

            else:
                try:
                    img = Image.open(file).convert(mode='RGB')
                    img = img.quantize(palette=palette_image)

                    name = os.path.basename(file).split('.')[0]

                    mip = wad.Miptexture()
                    mip.name = name
                    mip.width = img.width
                    mip.height = img.height
                    mip.offsets = [40]
                    mip.pixels = []

                    # Build mip maps
                    for i in range(4):
                        resized_image = img.resize(
                            (img.width // pow(2, i), img.height // pow(2, i)))
                        data = resized_image.tobytes()
                        mip.pixels += struct.unpack(f'<{len(data)}B', data)
                        if i < 3:
                            mip.offsets += [mip.offsets[-1] + len(data)]

                    buff = io.BytesIO()
                    wad.Miptexture.write(buff, mip)
                    buff.seek(0)

                    info = wad.WadInfo(name)
                    info.file_size = 40 + len(mip.pixels)
                    info.disk_size = info.file_size
                    info.compression = wad.CompressionType.NONE
                    info.type = wad.LumpType.MIPTEX

                    log(f'  adding: {file}')

                    wad_file.writestr(info, buff)

                except:
                    parser.error(sys.exc_info()[1])

    sys.exit(0)