diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index afa88865..295d88f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -61,8 +61,8 @@ jobs: 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 . + python3 tools/reccmp/reccmp.py -S ISLEPROGRESS.SVG --svg-icon tools/reccmp/isle.png -H ISLEPROGRESS.HTML ISLE.EXE Release/ISLE.EXE Release/ISLE.PDB . + python3 tools/reccmp/reccmp.py -S LEGO1PROGRESS.SVG -T 1929 --svg-icon tools/reccmp/lego1.png -H LEGO1PROGRESS.HTML LEGO1.DLL Release/LEGO1.DLL Release/LEGO1.PDB . - name: Upload Artifact uses: actions/upload-artifact@master @@ -72,3 +72,5 @@ jobs: Release ISLEPROGRESS.HTML LEGO1PROGRESS.HTML + ISLEPROGRESS.SVG + LEGO1PROGRESS.SVG diff --git a/tools/reccmp/isle.png b/tools/reccmp/isle.png new file mode 100644 index 00000000..cfa26a23 Binary files /dev/null and b/tools/reccmp/isle.png differ diff --git a/tools/reccmp/lego1.png b/tools/reccmp/lego1.png new file mode 100755 index 00000000..f76a54da Binary files /dev/null and b/tools/reccmp/lego1.png differ diff --git a/tools/reccmp/reccmp.py b/tools/reccmp/reccmp.py index 777cce2f..d50dc9ae 100755 --- a/tools/reccmp/reccmp.py +++ b/tools/reccmp/reccmp.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import base64 from capstone import * import difflib import struct @@ -15,9 +16,12 @@ parser.add_argument('recompiled', metavar='recompiled-binary', help='The recompiled binary') parser.add_argument('pdb', metavar='recompiled-pdb', help='The PDB of the recompiled binary') parser.add_argument('decomp_dir', metavar='decomp-dir', help='The decompiled source tree') +parser.add_argument('--total', '-T', metavar='total-func-count', help='Total number of expected functions (improves total accuracy statistic)') 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') +parser.add_argument('--svg', '-S', metavar='output-svg', help='Generate SVG graphic of progress') +parser.add_argument('--svg-icon', metavar='svg-icon', help='Icon to use in SVG (PNG)') args = parser.parse_args() @@ -50,6 +54,8 @@ if not os.path.isdir(source): parser.error('Source directory does not exist') +svg = args.svg + # Declare a class that can automatically convert virtual executable addresses # to file addresses class Bin: @@ -365,7 +371,8 @@ def parse_asm(file, addr, size): # 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'))) + escaped = '\\n'.join(udiff).replace('"', '\\"').replace('\n', '\\n').replace('<', '<').replace('>', '>') + htmlinsert.append('{address: "%s", name: "%s", matching: %s, diff: "%s"}' % (hex(addr), recinfo.name, str(ratio), escape)) except UnicodeDecodeError: break @@ -389,6 +396,49 @@ def gen_html(html, data): htmlfile.write(templatedata) htmlfile.close() +def gen_svg(svg, name, icon, implemented_funcs, total_funcs, raw_accuracy): + templatefile = open(get_file_in_script_dir('template.svg'), 'r') + if not templatefile: + print('Failed to find SVG template file, can\'t generate SVG summary') + return + + templatedata = templatefile.read() + templatefile.close() + + # Replace icon + if args.svg_icon: + iconfile = open(args.svg_icon, 'rb') + templatedata = templatedata.replace('{icon}', base64.b64encode(iconfile.read()).decode('utf-8'), 1) + iconfile.close() + + # Replace name + templatedata = templatedata.replace('{name}', name, 1) + + # Replace implemented statistic + templatedata = templatedata.replace('{implemented}', '%.2f%% (%i/%i)' % (implemented_funcs / total_funcs * 100, implemented_funcs, total_funcs), 1) + + # Replace accuracy statistic + templatedata = templatedata.replace('{accuracy}', '%.2f%%' % (raw_accuracy / implemented_funcs * 100), 1) + + # Generate progress bar width + total_statistic = raw_accuracy / total_funcs + percenttemplate = '{progbar' + percentstart = templatedata.index(percenttemplate) + percentend = templatedata.index('}', percentstart) + progwidth = float(templatedata[percentstart + len(percenttemplate) + 1:percentend]) * total_statistic + templatedata = templatedata[0:percentstart] + str(progwidth) + templatedata[percentend + 1:] + + # Replace percentage statistic + templatedata = templatedata.replace('{percent}', '%.2f%%' % (total_statistic * 100), 1) + + svgfile = open(svg, 'w') + if not svgfile: + print('Failed to write to SVG file %s' % svg) + return + + svgfile.write(templatedata) + svgfile.close() + if html: gen_html(html, htmlinsert) @@ -396,5 +446,13 @@ def gen_html(html, data): if not found_verbose_target: print('Failed to find the function with address %s' % hex(verbose)) else: + implemented_funcs = function_count + + if args.total: + function_count = int(args.total) + if function_count > 0: print('\nTotal accuracy %.2f%% across %i functions' % (total_accuracy / function_count * 100, function_count)) + + if svg: + gen_svg(svg, os.path.basename(original), args.svg_icon, implemented_funcs, function_count, total_accuracy) diff --git a/tools/reccmp/template.svg b/tools/reccmp/template.svg new file mode 100644 index 00000000..cb37b94c --- /dev/null +++ b/tools/reccmp/template.svg @@ -0,0 +1,74 @@ + + + +{name}{percent}Implemented: {implemented}Accuracy: {accuracy}