예제 #1
0
파일: main.py 프로젝트: parhelia512/ljd
    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
예제 #2
0
파일: main.py 프로젝트: iroot/ljd
    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
예제 #3
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
예제 #4
0
    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