generate progress SVGs

This commit is contained in:
itsmattkc 2023-06-27 15:59:44 -07:00
parent 24ec7023bd
commit b080766321
5 changed files with 137 additions and 3 deletions

View file

@ -61,8 +61,8 @@ jobs:
C:\msys64\usr\bin\wget.exe https://legoisland.org/download/LEGO1.DLL C:\msys64\usr\bin\wget.exe https://legoisland.org/download/LEGO1.DLL
pip install capstone pip install capstone
pip install colorama pip install colorama
python3 tools/reccmp/reccmp.py -H ISLEPROGRESS.HTML ISLE.EXE Release/ISLE.EXE Release/ISLE.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 -H LEGO1PROGRESS.HTML LEGO1.DLL Release/LEGO1.DLL Release/LEGO1.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 - name: Upload Artifact
uses: actions/upload-artifact@master uses: actions/upload-artifact@master
@ -72,3 +72,5 @@ jobs:
Release Release
ISLEPROGRESS.HTML ISLEPROGRESS.HTML
LEGO1PROGRESS.HTML LEGO1PROGRESS.HTML
ISLEPROGRESS.SVG
LEGO1PROGRESS.SVG

BIN
tools/reccmp/isle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
tools/reccmp/lego1.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import base64
from capstone import * from capstone import *
import difflib import difflib
import struct import struct
@ -15,9 +16,12 @@
parser.add_argument('recompiled', metavar='recompiled-binary', help='The recompiled binary') 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('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('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('--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('--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('--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() args = parser.parse_args()
@ -50,6 +54,8 @@
if not os.path.isdir(source): if not os.path.isdir(source):
parser.error('Source directory does not exist') parser.error('Source directory does not exist')
svg = args.svg
# Declare a class that can automatically convert virtual executable addresses # Declare a class that can automatically convert virtual executable addresses
# to file addresses # to file addresses
class Bin: class Bin:
@ -365,7 +371,8 @@ def parse_asm(file, addr, size):
# If html, record the diffs to an HTML file # If html, record the diffs to an HTML file
if html: 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('<', '&lt;').replace('>', '&gt;')
htmlinsert.append('{address: "%s", name: "%s", matching: %s, diff: "%s"}' % (hex(addr), recinfo.name, str(ratio), escape))
except UnicodeDecodeError: except UnicodeDecodeError:
break break
@ -389,6 +396,49 @@ def gen_html(html, data):
htmlfile.write(templatedata) htmlfile.write(templatedata)
htmlfile.close() 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: if html:
gen_html(html, htmlinsert) gen_html(html, htmlinsert)
@ -396,5 +446,13 @@ def gen_html(html, data):
if not found_verbose_target: if not found_verbose_target:
print('Failed to find the function with address %s' % hex(verbose)) print('Failed to find the function with address %s' % hex(verbose))
else: else:
implemented_funcs = function_count
if args.total:
function_count = int(args.total)
if function_count > 0: if function_count > 0:
print('\nTotal accuracy %.2f%% across %i functions' % (total_accuracy / function_count * 100, function_count)) 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)

74
tools/reccmp/template.svg Normal file
View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="640"
height="640"
viewBox="0 0 169.33333 169.33333"
version="1.1"
id="svg5"
xml:space="preserve"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs2"><rect
x="79.651809"
y="538.62568"
width="480.69626"
height="32.696293"
id="rect1277" /></defs><g
id="g1273"
transform="translate(-3.9577451e-7,-0.93505135)"><image
width="84.666664"
height="84.666664"
preserveAspectRatio="none"
style="image-rendering:optimizeSpeed"
xlink:href="data:image/png;base64,{icon}"
id="image1060"
x="42.333336"
y="20.367643" /><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12.7px;font-family:mono;-inkscape-font-specification:mono;text-align:center;text-anchor:middle;fill:#ffffff;stroke:#ffffff;stroke-width:2.64583"
x="84.476158"
y="116.82081"
id="text740"><tspan
id="tspan738"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:mono;-inkscape-font-specification:mono;text-align:center;text-anchor:middle;stroke:none;stroke-width:2.64583"
x="84.476158"
y="116.82081">{name}</tspan></text><g
id="g1250"
transform="translate(-1.3006529e-5,8.5767994)"><rect
style="fill:none;stroke:#ffffff;stroke-width:0.87411;stroke-dasharray:none;stroke-opacity:1"
id="rect1167"
width="127.18422"
height="8.6508904"
x="21.074554"
y="134.86963" /><rect
style="display:inline;fill:#ffffff;stroke:none;stroke-width:2.6764"
id="rect1169"
width="{progbar:127.18422}"
height="8.6508904"
x="21.074554"
y="134.86963" /><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333px;font-family:mono;-inkscape-font-specification:mono;text-align:start;text-anchor:start;fill:#c0c0c0;fill-opacity:1;stroke:none;stroke-width:1.05833;stroke-dasharray:none;stroke-opacity:1"
x="76.841347"
y="140.70638"
id="text2152"><tspan
style="font-size:4.23333px;fill:#c0c0c0;fill-opacity:1;stroke-width:1.05833"
x="76.841347"
y="140.70638"
id="tspan2150">{percent}</tspan></text></g><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.23333px;font-family:mono;-inkscape-font-specification:mono;text-align:start;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.05833;stroke-dasharray:none;stroke-opacity:1"
x="46.947659"
y="128.42285"
id="text1260"><tspan
id="tspan1258"
style="font-size:4.23333px;stroke-width:1.05833"
x="46.947659"
y="128.42285">Implemented: {implemented}</tspan><tspan
style="font-size:4.23333px;stroke-width:1.05833"
x="46.947659"
y="133.71451"
id="tspan1262">Accuracy: {accuracy}</tspan></text></g></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB