def main(): # python27 unrpyc.py [-c] [-d] [--python-screens|--ast-screens|--no-screens] file [file ...] parser = argparse.ArgumentParser(description="Decompile .rpyc files") parser.add_argument('-c', '--clobber', dest='clobber', action='store_true', help="overwrites existing output files") parser.add_argument('-d', '--dump', dest='dump', action='store_true', help="instead of decompiling, pretty print the ast to a file") parser.add_argument('-p', '--processes', dest='processes', action='store', default=cpu_count(), help="use the specified number of processes to decompile") parser.add_argument('-t', '--translation-file', dest='translation_file', action='store', default=None, help="use the specified file to translate during decompilation") parser.add_argument('-T', '--write-translation-file', dest='write_translation_file', action='store', default=None, help="store translations in the specified file instead of decompiling") parser.add_argument('-l', '--language', dest='language', action='store', default='english', help="if writing a translation file, the language of the translations to write") parser.add_argument('--sl1-as-python', dest='decompile_python', action='store_true', help="Only dumping and for decompiling screen language 1 screens. " "Convert SL1 Python AST to Python code instead of dumping it or converting it to screenlang.") parser.add_argument('--comparable', dest='comparable', action='store_true', help="Only for dumping, remove several false differences when comparing dumps. " "This suppresses attributes that are different even when the code is identical, such as file modification times. ") parser.add_argument('--no-pyexpr', dest='no_pyexpr', action='store_true', help="Only for dumping, disable special handling of PyExpr objects, instead printing them as strings. " "This is useful when comparing dumps from different versions of Ren'Py. " "It should only be used if necessary, since it will cause loss of information such as line numbers.") parser.add_argument('--init-offset', dest='init_offset', action='store_true', help="Attempt to guess when init offset statements were used and insert them. " "This is always safe to enable if the game's Ren'Py version supports init offset statements, " "and the generated code is exactly equivalent, only less cluttered.") parser.add_argument('file', type=str, nargs='+', help="The filenames to decompile. " "All .rpyc files in any directories passed or their subdirectories will also be decompiled.") args = parser.parse_args() if args.write_translation_file and not args.clobber and path.exists(args.write_translation_file): # Fail early to avoid wasting time going through the files print "Output translation file already exists. Pass --clobber to overwrite." return if args.translation_file: with open(args.translation_file, 'rb') as in_file: args.translations = in_file.read() # Expand wildcards filesAndDirs = map(glob.glob, args.file) # Concatenate lists filesAndDirs = list(itertools.chain(*filesAndDirs)) # Recursively add .rpyc files from any directories passed files = [] for i in filesAndDirs: if path.isdir(i): for dirpath, dirnames, filenames in walk(i): files.extend(path.join(dirpath, j) for j in filenames if len(j) >= 5 and j[-5:] == '.rpyc') else: files.append(i) # Check if we actually have files if len(files) == 0: parser.print_help(); parser.error("No script files given.") files = map(lambda x: (args, x, path.getsize(x)), files) processes = int(args.processes) if processes > 1: # If a big file starts near the end, there could be a long time with # only one thread running, which is inefficient. Avoid this by starting # big files first. files.sort(key=itemgetter(2), reverse=True) results = Pool(int(args.processes), sharelock, [printlock]).map(worker, files, 1) else: # Decompile in the order Ren'Py loads in files.sort(key=itemgetter(1)) results = map(worker, files) if args.write_translation_file: print "Writing translations to %s..." % args.write_translation_file translated_dialogue = {} translated_strings = {} good = 0 bad = 0 for result in results: if not result: bad += 1 continue good += 1 translated_dialogue.update(magic.loads(result[0], class_factory)) translated_strings.update(result[1]) with open(args.write_translation_file, 'wb') as out_file: magic.safe_dump((args.language, translated_dialogue, translated_strings), out_file) else: # Check per file if everything went well and report back good = results.count(True) bad = results.count(False) if bad == 0: print "Decompilation of %d script file%s successful" % (good, 's' if good>1 else '') elif good == 0: print "Decompilation of %d file%s failed" % (bad, 's' if bad>1 else '') else: print "Decompilation of %d file%s successful, but decompilation of %d file%s failed" % (good, 's' if good>1 else '', bad, 's' if bad>1 else '')
def main(): # python27 unrpyc.py [-c] [-d] [--python-screens|--ast-screens|--no-screens] file [file ...] parser = argparse.ArgumentParser(description="Decompile .rpyc files") parser.add_argument('-c', '--clobber', dest='clobber', action='store_true', help="overwrites existing output files") parser.add_argument( '-d', '--dump', dest='dump', action='store_true', help="instead of decompiling, pretty print the ast to a file") parser.add_argument( '-p', '--processes', dest='processes', action='store', default=cpu_count(), help="use the specified number of processes to decompile") parser.add_argument( '-t', '--translation-file', dest='translation_file', action='store', default=None, help="use the specified file to translate during decompilation") parser.add_argument( '-T', '--write-translation-file', dest='write_translation_file', action='store', default=None, help="store translations in the specified file instead of decompiling") parser.add_argument( '-l', '--language', dest='language', action='store', default='english', help= "if writing a translation file, the language of the translations to write" ) parser.add_argument( '--sl1-as-python', dest='decompile_python', action='store_true', help="Only dumping and for decompiling screen language 1 screens. " "Convert SL1 Python AST to Python code instead of dumping it or converting it to screenlang." ) parser.add_argument( '--comparable', dest='comparable', action='store_true', help= "Only for dumping, remove several false differences when comparing dumps. " "This suppresses attributes that are different even when the code is identical, such as file modification times. " ) parser.add_argument( '--no-pyexpr', dest='no_pyexpr', action='store_true', help= "Only for dumping, disable special handling of PyExpr objects, instead printing them as strings. " "This is useful when comparing dumps from different versions of Ren'Py. " "It should only be used if necessary, since it will cause loss of information such as line numbers." ) parser.add_argument( '--init-offset', dest='init_offset', action='store_true', help= "Attempt to guess when init offset statements were used and insert them. " "This is always safe to enable if the game's Ren'Py version supports init offset statements, " "and the generated code is exactly equivalent, only less cluttered.") parser.add_argument( 'file', type=str, nargs='+', help="The filenames to decompile. " "All .rpyc files in any directories passed or their subdirectories will also be decompiled." ) args = parser.parse_args() if args.write_translation_file and not args.clobber and path.exists( args.write_translation_file): # Fail early to avoid wasting time going through the files print( "Output translation file already exists. Pass --clobber to overwrite." ) return if args.translation_file: with open(args.translation_file, 'rb') as in_file: args.translations = in_file.read() # Expand wildcards def glob_or_complain(s): retval = glob.glob(s) if not retval: print("File not found: " + s) return retval filesAndDirs = map(glob_or_complain, args.file) # Concatenate lists filesAndDirs = list(itertools.chain(*filesAndDirs)) # Recursively add .rpyc files from any directories passed files = [] for i in filesAndDirs: if path.isdir(i): for dirpath, dirnames, filenames in walk(i): files.extend( path.join(dirpath, j) for j in filenames if len(j) >= 5 and j[-5:] == '.rpyc') else: files.append(i) # Check if we actually have files. Don't worry about # no parameters passed, since ArgumentParser catches that if len(files) == 0: print("No script files to decompile.") return files = map(lambda x: (args, x, path.getsize(x)), files) processes = int(args.processes) if processes > 1: # If a big file starts near the end, there could be a long time with # only one thread running, which is inefficient. Avoid this by starting # big files first. files.sort(key=itemgetter(2), reverse=True) results = Pool(int(args.processes), sharelock, [printlock]).map(worker, files, 1) else: # Decompile in the order Ren'Py loads in files.sort(key=itemgetter(1)) results = map(worker, files) if args.write_translation_file: print("Writing translations to %s..." % args.write_translation_file) translated_dialogue = {} translated_strings = {} good = 0 bad = 0 for result in results: if not result: bad += 1 continue good += 1 translated_dialogue.update(magic.loads(result[0], class_factory)) translated_strings.update(result[1]) with open(args.write_translation_file, 'wb') as out_file: magic.safe_dump( (args.language, translated_dialogue, translated_strings), out_file) else: # Check per file if everything went well and report back good = results.count(True) bad = results.count(False) if bad == 0: print("Decompilation of %d script file%s successful" % (good, 's' if good > 1 else '')) elif good == 0: print("Decompilation of %d file%s failed" % (bad, 's' if bad > 1 else '')) else: print( "Decompilation of %d file%s successful, but decompilation of %d file%s failed" % (good, 's' if good > 1 else '', bad, 's' if bad > 1 else ''))
def main(): # python27 unrpyc.py [-c] [-d] [--python-screens|--ast-screens|--no-screens] file [file ...] parser = argparse.ArgumentParser(description="Decompile .rpyc files") parser.add_argument('-c', '--clobber', dest='clobber', action='store_true', help="overwrites existing output files") parser.add_argument('-d', '--dump', dest='dump', action='store_true', help="instead of decompiling, pretty print the ast to a file") parser.add_argument('-p', '--processes', dest='processes', action='store', default=cpu_count(), help="use the specified number of processes to decompile") parser.add_argument('--sl1-as-python', dest='decompile_python', action='store_true', help="Only dumping and for decompiling screen language 1 screens. " "Convert SL1 Python AST to Python code instead of dumping it or converting it to screenlang.") parser.add_argument('--comparable', dest='comparable', action='store_true', help="Only for dumping, remove several false differences when comparing dumps. " "This suppresses attributes that are different even when the code is identical, such as file modification times. ") parser.add_argument('--no-pyexpr', dest='no_pyexpr', action='store_true', help="Only for dumping, disable special handling of PyExpr objects, instead printing them as strings. " "This is useful when comparing dumps from different versions of Ren'Py. " "It should only be used if necessary, since it will cause loss of information such as line numbers.") parser.add_argument('file', type=str, nargs='+', help="The filenames to decompile") args = parser.parse_args() # Expand wildcards files = map(glob.glob, args.file) # Concatenate lists files = list(itertools.chain(*files)) # Check if we actually have files if len(files) == 0: parser.print_help(); parser.error("No script files given.") files = map(lambda x: (args, x, path.getsize(x)), files) processes = int(args.processes) if processes > 1: # If a big file starts near the end, there could be a long time with # only one thread running, which is inefficient. Avoid this by starting # big files first. files = sorted(files, key=itemgetter(2), reverse=True) results = Pool(int(args.processes), sharelock, [printlock]).map(worker, files, 1) else: results = map(worker, files) # Check per file if everything went well and report back good = results.count(True) bad = results.count(False) if bad == 0: print "Decompilation of %d script file%s successful" % (good, 's' if good>1 else '') elif good == 0: print "Decompilation of %d file%s failed" % (bad, 's' if bad>1 else '') else: print "Decompilation of %d file%s successful, but decompilation of %d file%s failed" % (good, 's' if good>1 else '', bad, 's' if bad>1 else '')