From bdec8e13af13c9f792181a09d10bba91788c9730 Mon Sep 17 00:00:00 2001 From: Gareth Latty Date: Wed, 4 Oct 2017 23:19:36 +0100 Subject: [PATCH] Python 3 and continue on error option. --- README | 4 ++-- unrpa | 59 ++++++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/README b/README index bba0d20..a75d14d 100644 --- a/README +++ b/README @@ -1,7 +1,7 @@ Unrpa is a script to extract files from archives created for the Ren'Py Visual Novel Engine (http://www.renpy.org/). -You will need Python 2.x in order to run it (either install through your package manager or see -https://www.python.org/downloads/ and look for the latest release with a version beginning with 2). +You will need Python 3.x in order to run it (either install through your package manager or see +https://www.python.org/downloads/). Options: --version show program's version number and exit diff --git a/unrpa b/unrpa index d064396..0e14b82 100755 --- a/unrpa +++ b/unrpa @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 """ unrpa is a tool to extract files from Ren'Py archives (.rpa). @@ -21,12 +21,14 @@ import os import optparse import sys import pickle +import zlib +import traceback class UnRPA: NAME = "unrpa" - def __init__(self, filename, verbosity=1, path=None, mkdir=False, version=None): + def __init__(self, filename, verbosity=1, path=None, mkdir=False, version=None, continue_on_error=False): self.verbose = verbosity if path: self.path = os.path.abspath(path) @@ -35,6 +37,7 @@ class UnRPA: self.mkdir = mkdir self.version = version self.archive = filename + self.continue_on_error = continue_on_error def log(self, verbosity, message): if self.verbose > verbosity: @@ -52,17 +55,28 @@ class UnRPA: index = self.get_index() total_files = len(index) - for file_number, (item, data) in enumerate(index.iteritems()): - self.make_directory_structure(os.path.join(self.path, os.path.split(item)[0])) - raw_file = self.extract_file(item, data, file_number, total_files) - with open(os.path.join(self.path, item.encode('UTF-8')), "wb") as f: - f.write(raw_file) + for file_number, (item, data) in enumerate(index.items()): + try: + item_path = item.decode("utf-8") + self.make_directory_structure(os.path.join(self.path, os.path.split(item_path)[0])) + raw_file = self.extract_file(item_path, data, file_number, total_files) + with open(os.path.join(self.path, item_path), "wb") as f: + f.write(raw_file) + except BaseException as e: + if self.continue_on_error: + traceback.print_exc() + self.log(0, + "error extracting (see above), but --continue-on-error was used, so we will keep going.") + else: + raise Exception("There was an error while trying to extract a file. See the nested exception for " + "more. If you wish to try and extract as much from the archive as possible, please " + "use the --continue-on-error flag.") from e def list_files(self): self.log(1, "listing files:") paths = self.get_index().keys() for path in sorted(paths): - print(path.encode('utf-8')) + print(path) def extract_file(self, name, data, file_number, total_files): self.log(1, "[{:04.2%}] {:>3}".format(file_number / float(total_files), name)) @@ -94,12 +108,12 @@ class UnRPA: offset = int(parts[1], 16) key = int(parts[2], 16) f.seek(offset) - index = pickle.loads(f.read().decode("zlib")) + index = pickle.loads(zlib.decompress(f.read()), encoding="bytes") if self.version == 3: index = self.deobfuscate_index(index, key) if "/" != os.sep: - return {item.replace("/", os.sep): data for item, data in index.iteritems()} + return {item.replace("/", os.sep): data for item, data in index.items()} else: return index @@ -108,9 +122,9 @@ class UnRPA: if ext == ".rpa": with open(self.archive, "rb") as f: line = f.readline() - if line.startswith("RPA-3.0 "): + if line.startswith(b"RPA-3.0 "): return 3 - if line.startswith("RPA-2.0 "): + if line.startswith(b"RPA-2.0 "): return 2 else: return None @@ -118,11 +132,11 @@ class UnRPA: return 1 def deobfuscate_index(self, index, key): - return {k: self.deobfuscate_entry(key, v) for k, v in index.iteritems()} + return {k: self.deobfuscate_entry(key, v) for k, v in index.items()} def deobfuscate_entry(self, key, entry): if len(entry[0]) == 2: - return [(offset ^ key, dlen ^ key, '') for offset, dlen in entry] + return [(offset ^ key, dlen ^ key, "") for offset, dlen in entry] else: return [(offset ^ key, dlen ^ key, start) for offset, dlen, start in entry] @@ -130,16 +144,20 @@ class UnRPA: if __name__ == "__main__": parser = optparse.OptionParser(usage="usage: %prog [options] pathname", version="%prog 1.1") - parser.add_option("-v", "--verbose", action="count", dest="verbose", help="explain what is being done [default]") - parser.add_option("-s", "--silent", action="store_const", const=0, dest="verbose", default=1, help="make no output") + parser.add_option("-v", "--verbose", action="count", dest="verbose", + help="explain what is being done [default].") + parser.add_option("-s", "--silent", action="store_const", const=0, dest="verbose", default=1, + help="no output.") parser.add_option("-l", "--list", action="store_true", dest="list", default=False, - help="only list contents, do not extract") + help="only list contents, do not extract.") parser.add_option("-p", "--path", action="store", type="string", dest="path", default=None, - help="will extract to the given path") + help="will extract to the given path.") parser.add_option("-m", "--mkdir", action="store_true", dest="mkdir", default=False, - help="will make any non-existent directories in extraction path") + help="will make any non-existent directories in extraction path.") parser.add_option("-f", "--force", action="store", type="int", dest="version", default=None, help="forces an archive version. May result in failure.") + parser.add_option("--continue-on-error", action="store_true", dest="continue_on_error", default=False, + help="try to continue extraction when something goes wrong.") (options, args) = parser.parse_args() @@ -159,7 +177,8 @@ if __name__ == "__main__": filename = args[0] - extractor = UnRPA(filename, options.verbose, options.path, options.mkdir, options.version) + extractor = UnRPA(filename, options.verbose, options.path, options.mkdir, options.version, + options.continue_on_error) if options.list: extractor.list_files() else: