def test_empty_pak_file(self): with pak.PakFile(self.buff, 'w'): pass self.buff.seek(0) with pak.PakFile(self.buff, 'r') as pak_file: self.assertEqual(len(pak_file.namelist()), 0, 'Pak file should have not entries') self.assertEqual( pak_file.end_of_data, 12, 'Directory should start immediately after header')
def test_zero_byte_file(self): with pak.PakFile(self.buff, 'w') as pak_file: pak_file.writestr('zero.txt', b'') self.buff.seek(0) with pak.PakFile(self.buff) as pak_file: info = pak_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 = pak_file.read('zero.txt') self.assertEqual(len(data), 0, 'Length of bytes read should be zero.')
def test_context_manager(self): with pak.PakFile('./test_data/test.pak', 'r') as pak_file: self.assertFalse(pak_file.fp.closed, 'File should be open') self.assertEqual(pak_file.mode, 'r', 'File mode should be \'r\'') fp = pak_file.fp pak_file._did_modify = False self.assertTrue(fp.closed, 'File should be closed') self.assertIsNone(pak_file.fp, 'File pointer should be cleaned up')
def test_write_string(self): p0 = pak.PakFile(self.buff, 'w') p0.writestr('test.cfg', b'bind ALT +strafe') p0.writestr(pak.PakInfo('docs/readme.txt'), 'test') p0.close() self.buff.seek(0) p1 = pak.PakFile(self.buff, 'r') self.assertTrue('test.cfg' in p1.namelist(), 'Cfg file should be in Pak file') self.assertTrue('docs/readme.txt' in p1.namelist(), 'Txt file should be in Pak file') self.assertEqual(p1.read('test.cfg'), b'bind ALT +strafe', 'Cfg file content should not change') self.assertEqual( p1.read('docs/readme.txt').decode('ascii'), 'test', 'Txt file conent should not change') p1.close() self.buff.close()
def test_append(self): with open('./test_data/test.pak', 'rb') as f: self.buff = io.BytesIO(f.read()) pak_file = pak.PakFile(self.buff, 'a') pak_file.write('./test_data/test.bsp') pak_file.close() self.buff.seek(0) pak_file = pak.PakFile(self.buff, 'r') self.assertTrue('./test_data/test.bsp' in pak_file.namelist(), 'Appended file should be in Pak file') self.assertEqual(len(pak_file.infolist()), 3, 'Pak file should contain exactly three entries.') fp = pak_file.fp pak_file.close() self.assertFalse(self.buff.closed, 'Pak file should not close passed file-like object') self.buff.close()
def test_write(self): pak_file = pak.PakFile(self.buff, 'w') self.assertFalse(pak_file.fp.closed, 'File should be open') pak_file.write('./test_data/test.mdl') pak_file.write('./test_data/test.bsp', './progs/test.bsp') self.assertTrue('./test_data/test.mdl' in pak_file.namelist(), 'Mdl file should be in Pak file') self.assertTrue('./progs/test.bsp' in pak_file.namelist(), 'Bsp file should be in Pak file') fp = pak_file.fp pak_file.close() self.assertFalse(fp.closed, 'File should be open') self.assertIsNone(pak_file.fp, 'File pointer should be cleaned up') self.buff.close()
def test_read(self): pak_file = pak.PakFile('./test_data/test.pak', 'r') self.assertFalse(pak_file.fp.closed, 'File should be open') self.assertEqual(len(pak_file.infolist()), 2, 'Pak file should contain exactly two entries.') info = pak_file.getinfo('./test_data/test.mdl') self.assertIsNotNone(info, 'FileInfo should not be None') self.assertEqual(info.filename, './test_data/test.mdl', 'FileInfo names should match') self.assertEqual(info.file_size, 4724, 'FileInfo size of test file should be 4724') self.assertEqual(info.file_offset, 12, 'FileInfo offset of test file should be 12') file = pak_file.open('./test_data/test.mdl') self.assertIsNotNone(file, 'File should not be None') file.close() fp = pak_file.fp pak_file.close() self.assertTrue(fp.closed, 'File should be closed') self.assertIsNone(pak_file.fp, 'File pointer should be cleaned up')
def main(): parser = Parser( prog='pak', description='Default action is to add or replace pak files ' 'entries from list.\nIf list is omitted, pak will ' 'use stdin.', epilog='example: pak tex.pak image.png => adds image.png to tex.pak' ) parser.add_argument( 'file', metavar='file.pak', action=ResolvePathAction, help='pak file to create' ) parser.add_argument( 'list', nargs='*', action=ResolvePathAction, default=read_from_stdin() ) 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='{} version {}'.format(parser.prog, qcli.__version__) ) args = parser.parse_args() if not args.list: parser.error('the following arguments are required: list') dir = os.path.dirname(args.file) or '.' if not os.path.exists(dir): os.makedirs(dir) filemode = 'a' if not os.path.isfile(args.file): filemode = 'w' with pak.PakFile(args.file, filemode) as pak_file: if not args.quiet: print(f'Archive: {os.path.basename(args.file)}') # Process input files for file in args.list: # Walk directories if os.path.isdir(file): for root, dirs, files in os.walk(file): for name in [f for f in files if not f.startswith('.')]: fullpath = os.path.join(root, name) relpath = os.path.relpath(fullpath, os.getcwd()) if not args.quiet: print(f' adding: {relpath}') pak_file.write(relpath) else: relpath = os.path.relpath(file, os.getcwd()) if not args.quiet: print(f' adding: {relpath}') pak_file.write(relpath) sys.exit(0)
def main(): parser = Parser( prog='unpak', description='Default action is to extract files to xdir.', epilog='example: unpak PAK0.PAK -d ./out => extract all files to ./out' ) parser.add_argument( 'file', metavar='file.pak', action=ResolvePathAction ) parser.add_argument( '-l', '--list', action='store_true', help='list files' ) parser.add_argument( '-d', metavar='xdir', dest='dest', default=os.getcwd(), action=ResolvePathAction, help='extract files into xdir' ) 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.unpak.__version__}' ) args = parser.parse_args() if not pak.is_pakfile(args.file): print(f'{parser.prog}: cannot find or open {args.file}', file=sys.stderr) sys.exit(1) if args.list: with pak.PakFile(args.file) as pak_file: info_list = sorted(pak_file.infolist(), key=lambda i: i.filename) headers = ['Length', 'Name'] table = [[i.file_size, 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: {os.path.basename(args.file)}') print(tabulate(table, headers=headers)) sys.exit(0) with pak.PakFile(args.file) as pak_file: info_list = pak_file.infolist() for item in sorted(info_list, key=lambda i: i.filename): filename = item.filename fullpath = os.path.join(args.dest, filename) if not args.quiet: print(f' extracting: {fullpath}') try: pak_file.extract(filename, args.dest) except: print(f'{parser.prog}: error: {sys.exc_info()[0]}', file=sys.stderr) sys.exit(0)
def main(): # Fix for frozen packages def handleSIGINT(signum, frame): raise KeyboardInterrupt signal.signal(signal.SIGINT, handleSIGINT) parser = Parser( prog='qmount', description= 'Default action is to mount the given pak file as a logical volume.', epilog= 'example: qmount TEST.PAK => mounts TEST.PAK as a logical volume.') parser.add_argument('file', metavar='file.pak', action=ResolvePathAction, help='pak file to mount') parser.add_argument('-f', '--file-browser', dest='open_file_browser', action='store_true', help='opens a file browser once mounted') parser.add_argument('--verbose', dest='verbose', action='store_true', help='verbose mode') parser.add_argument( '-v', '--version', dest='version', action='version', help=argparse.SUPPRESS, version=f'{parser.prog} version {qcli.qmount.__version__}') args = parser.parse_args() dir = os.path.dirname(args.file) or '.' if not os.path.exists(dir): os.makedirs(dir) archive_name = os.path.basename(args.file) context = {'dirty': False} files = {} # If the pak file exists put the contents into the file dictionary if os.path.exists(args.file): with pak.PakFile(args.file) as pak_file: for info in pak_file.infolist(): name = info.filename files[name] = pak_file.read(name) else: context['dirty'] = True temp_directory = platforms.temp_volume(archive_name) # Copy pak file contents into the temporary directory for filename in files: abs_path = os.path.join(temp_directory, filename) dir = os.path.dirname(abs_path) if not os.path.exists(dir): os.makedirs(dir) with open(abs_path, 'wb') as out_file: out_file.write(files[filename]) # Open a native file browser if args.open_file_browser: platforms.open_file_browser(temp_directory) # Start file watching observer = Observer() handler = TempPakFileHandler( context, temp_directory, files, args.verbose, ignore_patterns=['*/.DS_Store', '*/Thumbs.db'], ignore_directories=True) observer.schedule(handler, path=temp_directory, recursive=True) print('Press Ctrl+C to save and quit') observer.start() # Wait for user to terminate try: while True: time.sleep(1) # Detect the deletion of the watched directory. if not os.path.exists(temp_directory): raise KeyboardInterrupt except KeyboardInterrupt: print() try: observer.stop() except: """This is a temporary workaround. Watchdog will raise an exception if the watched media is ejected.""" observer.join() # Write out updated files if context['dirty']: print(f'Updating changes to {archive_name}') with pak.PakFile(args.file, 'w') as pak_file: for filename in files: pak_file.writestr(filename, files[filename]) else: print(f'No changes detected to {archive_name}') # Clean up temp directory platforms.unmount_temp_volume(temp_directory) sys.exit(0)