def main(self): # Parser arguments parser = OptionParser() # Single file input target. Not to be used with -r parser.add_option("-f", "--file", type="string", dest="file_name", default="", help="input file name", metavar="FILE") # Single file output destination. Not to be used with -r parser.add_option("-o", "--output", type="string", dest="output_file", default="", help="output file for writing", metavar="FILE") # Directory in which to recurse and process all files. Not to be used with -f parser.add_option("-r", "--recursive", type="string", dest="folder_name", default="", help="recursively decompile lua files", metavar="FOLDER") # Directory in which to recurse and process all files. Not to be used with -f and -r parser.add_option("-L", "--last", type="string", dest="last_date", default="", help="last decrypt lua files date") # Directory to output processed files during recursion. Not to be used with -f parser.add_option("-d", "--dir_out", type="string", dest="folder_output", default="", help="directory to output decompiled lua scripts", metavar="FOLDER") # Directory to output processed files during recursion. Not to be used with -f and -d parser.add_option("-C", "--current", type="string", dest="current_date", default="", help="current decrypt lua files date") # Global override of LuaJIT version, ignores -j parser.add_option( "-j", "--jit_version", type="string", dest="luajit_version", default="", help="override LuaJIT version, default 2.1, now supports 2.0, 2.1") # 'Profiles' that hardcode LuaJIT versions per file parser.add_option("-v", "--version_config_list", type="string", dest="version_config_list", default="version_default", help="LuaJIT version config list to use") # Prevent most integrity asserts from canceling decompilation parser.add_option( "-c", "--catch_asserts", action="store_true", dest="catch_asserts", default=False, help="attempt inline error reporting without breaking decompilation" ) # Output a log of exceptions and information during decompilation parser.add_option( "-l", "--enable_logging", action="store_true", dest="enable_logging", default=False, help="log info and exceptions to external file while decompiling") (self.options, args) = parser.parse_args() # Initialize opcode set for required LuaJIT version basepath = os.path.dirname(sys.argv[0]) if basepath == "": basepath = "." if self.options.luajit_version == "": version_required = self.check_for_version_config( self.options.file_name) sys.path.append(basepath + "/ljd/rawdump/luajit/" + str(version_required) + "/") else: self.set_version_config(float(self.options.luajit_version)) sys.path.append(basepath + "/ljd/rawdump/luajit/" + self.options.luajit_version + "/") # LuaJIT version is known after the argument is parsed, so delay module import. import ljd.rawdump.parser import ljd.pseudoasm.writer import ljd.ast.builder import ljd.ast.validator import ljd.ast.locals import ljd.ast.slotworks import ljd.ast.unwarper import ljd.ast.mutator import ljd.lua.writer # Send assert catch argument to modules if self.options.catch_asserts: ljd.ast.unwarper.catch_asserts = True ljd.ast.slotworks.catch_asserts = True ljd.ast.validator.catch_asserts = True self.ljd = ljd # Start logging if required if self.options.enable_logging: logger = logging.getLogger('LJD') logger.setLevel(logging.INFO) fh = MakeFileHandler( f'logs/{datetime.now().strftime("%Y_%m_%d_%H_%M_%S")}.log') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) logger.addHandler(fh) console = logging.StreamHandler() console.setLevel(logging.INFO) formatter = logging.Formatter( '%(name)-12s: %(levelname)-8s %(message)s') console.setFormatter(formatter) logger.addHandler(console) else: logger = None if self.options.current_date: if not self.options.last_date: self.options.last_date = self.options.current_date last_date_folder_name = os.path.abspath('../files/' + self.options.last_date + '/' + self.options.last_date) last_date_folder_name_decrypt = os.path.abspath( '../files/' + self.options.last_date + '/decrypt') last_date_folder_name_decompile = os.path.abspath( '../files/' + self.options.last_date + '/decompile') curr_date_folder_name = os.path.abspath('../files/' + self.options.current_date + '/' + self.options.current_date) curr_date_folder_name_decrypt = os.path.abspath( '../files/' + self.options.current_date + '/decrypt') curr_date_folder_name_decompile = os.path.abspath( '../files/' + self.options.current_date + '/decompile') if not os.path.exists(curr_date_folder_name_decrypt): os.makedirs(curr_date_folder_name_decrypt) if not os.path.exists(curr_date_folder_name_decompile): os.makedirs(curr_date_folder_name_decompile) for path, _, filenames in os.walk(last_date_folder_name_decompile): for file in filenames: if file.endswith('.lua') or file.endswith('.luac'): full_path = os.path.join(path, file) new_path = full_path.replace( last_date_folder_name_decompile, curr_date_folder_name_decompile) parent_path = os.path.dirname(new_path) if not os.path.exists(parent_path): os.makedirs(parent_path) copyfile(full_path, new_path) total_file_num = 0 for path, _, filenames in os.walk(curr_date_folder_name): for file in filenames: if file.endswith('.lua') or file.endswith('.luac'): total_file_num = total_file_num + 1 bar = progressbar.ProgressBar(0, total_file_num) file_count = 0 # generate file list file_list = [] print("Decrypting...") for path, _, filenames in os.walk(curr_date_folder_name): for file in filenames: if file.endswith('.lua') or file.endswith('.luac'): full_path = os.path.join(path, file) releate_path = path.replace(curr_date_folder_name, "") last_path = path.replace(curr_date_folder_name, last_date_folder_name) last_full_path = os.path.join(last_path, file) last_decompile_file_path = os.path.join( last_date_folder_name_decompile + releate_path, file) curr_decompile_file_path = os.path.join( curr_date_folder_name_decompile + releate_path, file) if self.file_compare( full_path, last_full_path) and os.path.isfile( last_decompile_file_path): parent_path = os.path.dirname( curr_decompile_file_path) if not os.path.exists(parent_path): os.makedirs(parent_path) copyfile(last_decompile_file_path, curr_decompile_file_path) else: decrypt_file_path = os.path.join( curr_date_folder_name_decrypt + releate_path, file) decrypt = xxteaFile(full_path, decrypt_file_path) file_list.append(decrypt_file_path) file_count = file_count + 1 bar.update(file_count) bar.finish() print("Decompling...") total_file_num = len(file_list) bar = progressbar.ProgressBar(0, total_file_num) fail_count = 0 file_count = 0 for file in file_list: if file.endswith('.lua') or file.endswith('.luac'): full_path = file file_count = file_count + 1 if self.options.enable_logging: logger.info(full_path) try: self.decompile(full_path) new_path = full_path.replace( curr_date_folder_name_decrypt, curr_date_folder_name_decompile) parent_path = os.path.dirname(new_path) if not os.path.exists(parent_path): os.makedirs(parent_path) self.write_file(new_path) if self.options.enable_logging: logger.info("Success") else: bar.update(file_count) except KeyboardInterrupt: if self.options.enable_logging: logger.info("Exit") else: bar.update(file_count) return 0 except: fail_count = fail_count + 1 new_path = full_path.replace( curr_date_folder_name_decrypt, curr_date_folder_name_decompile) parent_path = os.path.dirname(new_path) if not os.path.exists(parent_path): os.makedirs(parent_path) self.decompile_luajit(full_path, new_path) if self.options.enable_logging: logger.info("Exception") logger.debug('', exc_info=True) else: bar.update(file_count) bar.finish() print("New file(s): " + str(total_file_num) + ". Including " + str(fail_count) + " file(s) decompiled by luajit") return 0 # Recursive batch processing if self.options.folder_name: if self.options.version_config_list != "version_default": print(self.options) print( "Version config lists are not supported in recursive directory mode." ) if self.options.enable_logging: logger.info("Exit") return 0 total_file_num = 0 for path, _, filenames in os.walk(self.options.folder_name): for file in filenames: if file.endswith('.lua') or file.endswith('.luac'): total_file_num = total_file_num + 1 bar = progressbar.ProgressBar(0, total_file_num) file_count = 0 for path, _, filenames in os.walk(self.options.folder_name): for file in filenames: if file.endswith('.lua') or file.endswith('.luac'): full_path = os.path.join(path, file) file_count = file_count + 1 if self.options.enable_logging: logger.info(full_path) try: self.decompile(full_path) new_path = os.path.join( self.options.folder_output, os.path.relpath(full_path, self.options.folder_name)) os.makedirs(os.path.dirname(new_path), exist_ok=True) self.write_file(new_path) if self.options.enable_logging: logger.info("Success") else: bar.update(file_count) except KeyboardInterrupt: if self.options.enable_logging: logger.info("Exit") else: bar.update(file_count) return 0 except: if self.options.enable_logging: logger.info("Exception") logger.debug('', exc_info=True) else: bar.update(file_count) return 0 # Single file processing if self.options.file_name == "": print(self.options) parser.error("Options -f or -r are required.") return 0 self.decompile(self.options.file_name) if self.options.output_file: self.write_file(self.options.output_file) else: self.ljd.lua.writer.write(sys.stdout, self.ast) return 0
def main(self): #parser arguments parser = OptionParser() parser.add_option("-f", "--file", \ type="string", dest="file_name", default="", \ help="decompile file name", metavar="FILE") parser.add_option("-o", "--output", \ type="string", dest="output_file", default="", \ help="output file for writing", metavar="FILE") parser.add_option("-j", "--jitverion", \ type="string", dest="luajit_version", default="2.1", \ help="luajit version, default 2.1, now support 2.0, 2.1") parser.add_option("-r", "--recursive", \ type="string", dest="folder_name", default="", \ help="recursive decompile lua files", metavar="FOLDER") parser.add_option("-d", "--dir_out", \ type="string", dest="folder_output", default="", \ help="directory to output decompiled lua scripts", metavar="FOLDER") (self.options, args) = parser.parse_args() basepath = os.path.dirname(sys.argv[0]) if basepath == "": basepath = "." sys.path.append(basepath + "/ljd/rawdump/luajit/" + self.options.luajit_version + "/") #because luajit version is known after argument parsed, so delay import modules import ljd.rawdump.parser import ljd.pseudoasm.writer import ljd.ast.builder import ljd.ast.validator import ljd.ast.locals import ljd.ast.slotworks import ljd.ast.unwarper import ljd.ast.mutator import ljd.lua.writer self.ljd = ljd if self.options.folder_name: for path, _, filenames in os.walk(self.options.folder_name): for file in filenames: if file.endswith('.lua'): full_path = os.path.join(path, file) logger.info(full_path) try: self.decompile(full_path) new_path = os.path.join( self.options.folder_output, os.path.relpath(full_path, self.options.folder_name)) os.makedirs(os.path.dirname(new_path), exist_ok=True) self.write_file(new_path) logger.info("Success") except KeyboardInterrupt: logger.info("Exit") return 0 except: logger.info("Exception") logger.debug('', exc_info=True) return 0 if self.options.file_name == "": print(self.options) parser.error("options -f is required") self.decompile(self.options.file_name) if self.options.output_file: self.write_file(self.options.output_file) else: self.ljd.lua.writer.write(sys.stdout, self.ast) return 0
def __init__(self): # Parser arguments parser = OptionParser() # Single file input target. Not to be used with -r parser.add_option("-f", "--file", type="string", dest="file_name", default="", help="input file name", metavar="FILE") # Directory in which to recurse and process all files. Not to be used with -f parser.add_option("-r", "--recursive", type="string", dest="folder_name", default="", help="recursively decompile lua files", metavar="FOLDER") # Single file output destination. Not to be used with -r parser.add_option("-o", "--output", type="string", dest="output", default="", help="output file for writing") # LEGACY OPTION. Directory to output processed files during recursion. Not to be used with -f parser.add_option( "-d", "--dir_out", type="string", dest="folder_output", default="", help="LEGACY OPTION. directory to output decompiled lua scripts", metavar="FOLDER") # Allow overriding the default .lua file extension (e.g. when binary lua files are saved as .luac) parser.add_option("-e", "--file-extension", type="string", dest="lua_ext", default=".lua", help="file extension filter for recursive searches", metavar="EXT") # Prefer raw source files when available? The PAYDAY games sometimes come with .lua_source files. parser.add_option("--prefer_sources", type="string", dest="lua_src_ext", default="", help="use source files", metavar="EXT") # Prevent most integrity asserts from canceling decompilation parser.add_option( "-c", "--catch_asserts", action="store_true", dest="catch_asserts", default=False, help="attempt inline error reporting without breaking decompilation" ) # Include line number comments for function definitions parser.add_option( "--with-line-numbers", action="store_true", dest="include_line_numbers", default=False, help="add comments with line numbers for function definitions") # Single file linemap output parser.add_option("--line-map-output", type="string", dest="line_map_output_file", default="", help="line map output file for writing", metavar="FILE") # Some previous luajit compilers produced some unexpected instructions that are not handled by the regular # process. If we bypass some of the safety checks, we may be able to deal with them correctly. This works # for PAYDAY 2 and RAID, but may have unexpected side effects for other projects. On by default. parser.add_option( "--unsafe", type="string", dest="unsafe_extra_pass", default="true", help="unsafe extra pass to try to correct some leftover values") group = OptionGroup(parser, "Debug Options") # Output a log of exceptions and information during decompilation parser.add_option( "-l", "--enable_logging", action="store_true", dest="enable_logging", default=False, help="log info and exceptions to external file while decompiling") group.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="verbose") # Skip some processing steps group.add_option("--no-unwarp", action="store_true", dest="no_unwarp", default=False, help="do not run the unwarper") # Output the pseudoasm of the initial AST instead of decompiling the input group.add_option("--asm", action="store_true", dest="output_pseudoasm", default=False, help="Print pseudo asm") group.add_option("--dump", action="store_true", dest="dump_ast", default=False, help="Dump AST") (self.options, args) = parser.parse_args() # Allow the input argument to be either a folder or a file. if len(args) == 1: if self.options.file_name or self.options.folder_name: parser.error("Conflicting file arguments.") sys.exit(1) if os.path.isdir(args[0]): self.options.folder_name = args[0] else: self.options.file_name = args[0] elif len(args) > 1: parser.error("Too many arguments.") sys.exit(1) # Verify arguments if self.options.folder_name: pass elif not self.options.file_name: parser.error("Options -f or -r are required.") sys.exit(1) # Determine output folder/file if self.options.folder_output: if not self.options.output: self.options.output = self.options.folder_output self.options.folder_output = None if self.options.output: if self.options.folder_name: if os.path.isfile(self.options.output): parser.error("Output folder is a file.") sys.exit(0) # TODO merge into the module handling below if self.options.catch_asserts: ljd.ast.builder.handle_invalid_functions = True for mod in [ljd.ast.unwarper, ljd.ast.slotworks, ljd.ast.validator]: if self.options.dump_ast: mod.debug_dump = True if self.options.catch_asserts: mod.catch_asserts = True if self.options.verbose: mod.verbose = True if self.options.include_line_numbers: ljd.lua.writer.show_line_info = True self.options.unsafe_extra_pass = self.options.unsafe_extra_pass.lower( ) in ['true', '1', 't', 'y', 'yes'] # Start logging if required if self.options.enable_logging: logger = logging.getLogger('LJD') logger.setLevel(logging.INFO) fh = MakeFileHandler( f'logs/{datetime.now().strftime("%Y_%m_%d_%H_%M_%S")}.log') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) logger.addHandler(fh) console = logging.StreamHandler() console.setLevel(logging.INFO) formatter = logging.Formatter( '%(name)-12s: %(levelname)-8s %(message)s') console.setFormatter(formatter) logger.addHandler(console) else: logger = None self.logger = logger
def main(self): # Parser arguments parser = OptionParser() # Single file input target. Not to be used with -r parser.add_option("-f", "--file", type="string", dest="file_name", default="", help="input file name", metavar="FILE") # Single file output destination. Not to be used with -r parser.add_option("-o", "--output", type="string", dest="output_file", default="", help="output file for writing", metavar="FILE") # Directory in which to recurse and process all files. Not to be used with -f parser.add_option("-r", "--recursive", type="string", dest="folder_name", default="", help="recursively decompile lua files", metavar="FOLDER") # Directory to output processed files during recursion. Not to be used with -f parser.add_option("-d", "--dir_out", type="string", dest="folder_output", default="", help="directory to output decompiled lua scripts", metavar="FOLDER") # Prevent most integrity asserts from canceling decompilation parser.add_option( "-c", "--catch_asserts", action="store_true", dest="catch_asserts", default=False, help="attempt inline error reporting without breaking decompilation" ) # Output a log of exceptions and information during decompilation parser.add_option( "-l", "--enable_logging", action="store_true", dest="enable_logging", default=False, help="log info and exceptions to external file while decompiling") # Single file linemap output parser.add_option("--line-map-output", type="string", dest="line_map_output_file", default="", help="line map output file for writing", metavar="FILE") (self.options, args) = parser.parse_args() # Send assert catch argument to modules if self.options.catch_asserts: ljd.ast.unwarper.catch_asserts = True ljd.ast.slotworks.catch_asserts = True ljd.ast.validator.catch_asserts = True # Start logging if required if self.options.enable_logging: logger = logging.getLogger('LJD') logger.setLevel(logging.INFO) fh = MakeFileHandler( f'logs/{datetime.now().strftime("%Y_%m_%d_%H_%M_%S")}.log') fh.setLevel(logging.DEBUG) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) logger.addHandler(fh) console = logging.StreamHandler() console.setLevel(logging.INFO) formatter = logging.Formatter( '%(name)-12s: %(levelname)-8s %(message)s') console.setFormatter(formatter) logger.addHandler(console) else: logger = None # Recursive batch processing if self.options.folder_name: for path, _, filenames in os.walk(self.options.folder_name): for file in filenames: if file.endswith('.lua'): full_path = os.path.join(path, file) if self.options.enable_logging: logger.info(full_path) try: self.decompile(full_path) new_path = os.path.join( self.options.folder_output, os.path.relpath(full_path, self.options.folder_name)) os.makedirs(os.path.dirname(new_path), exist_ok=True) self.write_file(new_path) if self.options.enable_logging: logger.info("Success") except KeyboardInterrupt: if self.options.enable_logging: logger.info("Exit") return 0 except: if self.options.enable_logging: logger.info("Exception") logger.debug('', exc_info=True) return 0 # Single file processing if self.options.file_name == "": print(self.options) parser.error("Options -f or -r are required.") return 0 self.decompile(self.options.file_name) generate_linemap = bool(self.options.line_map_output_file) if self.options.output_file: line_map = self.write_file(self.options.output_file, generate_linemap=generate_linemap) else: line_map = ljd.lua.writer.write(sys.stdout, self.ast, generate_linemap=generate_linemap) if self.options.line_map_output_file: with open(self.options.line_map_output_file, "wb") as lm_out: for from_line in sorted(line_map): to_line = line_map[from_line] lm_out.write(struct.pack("!II", from_line, to_line)) return 0