From 9990d0a2b753a64747d992adcba13399b88d109c Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Mon, 26 Jun 2023 03:34:13 +0200 Subject: [PATCH 1/2] lego: match GetClassName/IsClass (#48) --- LEGO1/legoomni.cpp | 12 ------------ LEGO1/legoomni.h | 11 +++++++++-- LEGO1/mxcore.cpp | 16 +--------------- LEGO1/mxcore.h | 12 ++++++++++-- 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/LEGO1/legoomni.cpp b/LEGO1/legoomni.cpp index ff209e38..30d86cb9 100644 --- a/LEGO1/legoomni.cpp +++ b/LEGO1/legoomni.cpp @@ -68,18 +68,6 @@ long LegoOmni::Notify(MxParam &p) return 0; } -// OFFSET: LEGO1 0x10058aa0 -const char *LegoOmni::GetClassName() const -{ - return "LegoOmni"; -} - -// OFFSET: LEGO1 0x10058ab0 -MxBool LegoOmni::IsClass(const char *name) const -{ - return strcmp("LegoOmni", name) == 0; -} - // OFFSET: LEGO1 0x10058bd0 void LegoOmni::Init() { diff --git a/LEGO1/legoomni.h b/LEGO1/legoomni.h index 6234dd7b..4a66d14d 100644 --- a/LEGO1/legoomni.h +++ b/LEGO1/legoomni.h @@ -30,8 +30,15 @@ class LegoOmni : public MxOmni virtual ~LegoOmni(); // vtable+00 virtual long Notify(MxParam &p); // vtable+04 - virtual const char *GetClassName() const; // vtable+0c - virtual MxBool IsClass(const char *name) const; // vtable+10; + + // OFFSET: LEGO1 0x10058aa0 + inline virtual const char *GetClassName() const { return "LegoOmni"; }; // vtable+0c + + // OFFSET: LEGO1 0x10058ab0 + inline virtual MxBool IsClass(const char *name) const { + return !strcmp(name, LegoOmni::GetClassName()) || MxOmni::IsClass(name); + }; // vtable+10; + virtual void Init(); // vtable+14 virtual MxResult Create(MxOmniCreateParam &p); // vtable+18 virtual void Destroy(); // vtable+1c diff --git a/LEGO1/mxcore.cpp b/LEGO1/mxcore.cpp index 1187cdbe..aec24c00 100644 --- a/LEGO1/mxcore.cpp +++ b/LEGO1/mxcore.cpp @@ -1,7 +1,5 @@ #include "mxcore.h" -#include - // 0x1010141c unsigned int g_mxcoreCount = 0; @@ -27,16 +25,4 @@ long MxCore::Notify(MxParam &p) long MxCore::Tickle() { return 0; -} - -// OFFSET: LEGO1 0x100144c0 -const char *MxCore::GetClassName() const -{ - return "MxCore"; -} - -// OFFSET: LEGO1 0x100140d0 -MxBool MxCore::IsClass(const char *name) const -{ - return strcmp(name, "MxCore") == 0; -} +} \ No newline at end of file diff --git a/LEGO1/mxcore.h b/LEGO1/mxcore.h index 64e916a1..37f079da 100644 --- a/LEGO1/mxcore.h +++ b/LEGO1/mxcore.h @@ -1,6 +1,8 @@ #ifndef MXCORE_H #define MXCORE_H +#include + #include "mxbool.h" class MxParam; @@ -12,8 +14,14 @@ class MxCore __declspec(dllexport) virtual ~MxCore(); // vtable+00 __declspec(dllexport) virtual long Notify(MxParam &p); // vtable+04 virtual long Tickle(); // vtable+08 - virtual const char *GetClassName() const; // vtable+0c - virtual MxBool IsClass(const char *name) const; // vtable+10 + + // OFFSET: LEGO1 0x100144c0 + inline virtual const char *GetClassName() const { return "MxCore"; }; // vtable+0c + + // OFFSET: LEGO1 0x100140d0 + inline virtual MxBool IsClass(const char *name) const { + return !strcmp(name, MxCore::GetClassName()); + }; // vtable+10 private: unsigned int m_id; From 0b47f3fff32c5ec1bed29ae76cd742767cb374d3 Mon Sep 17 00:00:00 2001 From: Mark Langen Date: Sun, 25 Jun 2023 19:01:40 -0700 Subject: [PATCH 2/2] Improve reccmp.py (#49) * Improve reccmp.py * Now only shows the info for a single function when a specific function is specified via -v * Now colors the output by default * Percentages are shown as green/yellow/red depending on the percentage completed. * Diff +/- lines are shown as green/red. * Includes standard --no-color argument in case we need no color for some tooling which consumes the output. * Feedback --- .github/workflows/build.yml | 1 + tools/reccmp/reccmp.py | 70 +++++++++++++++++++++++++++++------ tools/reccmp/requirements.txt | 2 + 3 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 tools/reccmp/requirements.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 07fe411d..afa88865 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,6 +60,7 @@ jobs: C:\msys64\usr\bin\wget.exe https://legoisland.org/download/ISLE.EXE C:\msys64\usr\bin\wget.exe https://legoisland.org/download/LEGO1.DLL pip install capstone + pip install colorama python3 tools/reccmp/reccmp.py -H ISLEPROGRESS.HTML ISLE.EXE Release/ISLE.EXE Release/ISLE.PDB . python3 tools/reccmp/reccmp.py -H LEGO1PROGRESS.HTML LEGO1.DLL Release/LEGO1.DLL Release/LEGO1.PDB . diff --git a/tools/reccmp/reccmp.py b/tools/reccmp/reccmp.py index 6e9826ce..777cce2f 100755 --- a/tools/reccmp/reccmp.py +++ b/tools/reccmp/reccmp.py @@ -7,6 +7,7 @@ import subprocess import os import sys +import colorama parser = argparse.ArgumentParser(allow_abbrev=False, description='Recompilation Compare: compare an original EXE with a recompiled EXE + PDB.') @@ -16,10 +17,14 @@ parser.add_argument('decomp_dir', metavar='decomp-dir', help='The decompiled source tree') parser.add_argument('--verbose', '-v', metavar='offset', help='Print assembly diff for specific function (original file\'s offset)') parser.add_argument('--html', '-H', metavar='output-file', help='Generate searchable HTML summary of status and diffs') +parser.add_argument('--no-color', '-n', action='store_true', help='Do not color the output') args = parser.parse_args() +colorama.init() + verbose = None +found_verbose_target = False if args.verbose: try: verbose = int(args.verbose, 16) @@ -27,6 +32,8 @@ parser.error('invalid verbose argument') html = args.html +plain = args.no_color + original = args.original if not os.path.isfile(original): parser.error('Original binary does not exist') @@ -287,6 +294,13 @@ def parse_asm(file, addr, size): addr = int(par[1], 16) + # Verbose flag handling + if verbose: + if addr == verbose: + found_verbose_target = True + else: + continue + find_open_bracket = line while '{' not in find_open_bracket: find_open_bracket = srcfile.readline() @@ -305,23 +319,53 @@ def parse_asm(file, addr, size): else: ratio = 0 - print(' %s (%s / %s) is %.2f%% similar to the original' % (recinfo.name, hex(addr), hex(recinfo.addr), ratio * 100)) + percenttext = "%.2f%%" % (ratio * 100) + if not plain: + if ratio == 1.0: + percenttext = colorama.Fore.GREEN + percenttext + colorama.Style.RESET_ALL + elif ratio > 0.8: + percenttext = colorama.Fore.YELLOW + percenttext + colorama.Style.RESET_ALL + else: + percenttext = colorama.Fore.RED + percenttext + colorama.Style.RESET_ALL + + if not verbose: + print(' %s (%s / %s) is %s similar to the original' % (recinfo.name, hex(addr), hex(recinfo.addr), percenttext)) function_count += 1 total_accuracy += ratio if recinfo.size: - if verbose == addr or html: - udiff = difflib.unified_diff(origasm, recompasm) + udiff = difflib.unified_diff(origasm, recompasm, n=10) - if verbose == addr: + # If verbose, print the diff for that funciton to the output + if verbose: + if ratio == 1.0: + print("%s: %s 100%% match.\n\nOK!" % (hex(addr), recinfo.name)) + else: for line in udiff: - print(line) - print() - print() + if line.startswith("++") or line.startswith("@@") or line.startswith("--"): + # Skip unneeded parts of the diff for the brief view + pass + elif line.startswith("+"): + if plain: + print(line) + else: + print(colorama.Fore.GREEN + line) + elif line.startswith("-"): + if plain: + print(line) + else: + print(colorama.Fore.RED + line) + else: + print(line) + if not plain: + print(colorama.Style.RESET_ALL, end='') - if html: - htmlinsert.append('{address: "%s", name: "%s", matching: %s, diff: "%s"}' % (hex(addr), recinfo.name, str(ratio), '\\n'.join(udiff).replace('"', '\\"').replace('\n', '\\n'))) + print("\n%s is only %s similar to the original, diff above" % (recinfo.name, percenttext)) + + # If html, record the diffs to an HTML file + if html: + htmlinsert.append('{address: "%s", name: "%s", matching: %s, diff: "%s"}' % (hex(addr), recinfo.name, str(ratio), '\\n'.join(udiff).replace('"', '\\"').replace('\n', '\\n'))) except UnicodeDecodeError: break @@ -348,5 +392,9 @@ def gen_html(html, data): if html: gen_html(html, htmlinsert) -if function_count > 0: - print('\nTotal accuracy %.2f%% across %i functions' % (total_accuracy / function_count * 100, function_count)) +if verbose: + if not found_verbose_target: + print('Failed to find the function with address %s' % hex(verbose)) +else: + if function_count > 0: + print('\nTotal accuracy %.2f%% across %i functions' % (total_accuracy / function_count * 100, function_count)) diff --git a/tools/reccmp/requirements.txt b/tools/reccmp/requirements.txt new file mode 100644 index 00000000..31469c4c --- /dev/null +++ b/tools/reccmp/requirements.txt @@ -0,0 +1,2 @@ +colorama +capstone \ No newline at end of file