mirror of https://github.com/n64decomp/007.git
289 lines
9.1 KiB
Python
289 lines
9.1 KiB
Python
import re
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import shlex
|
|
import getopt
|
|
|
|
|
|
# --------------------------
|
|
# Read Map File and return
|
|
# all function lenghts
|
|
# --------------------------
|
|
def parse_map(version):
|
|
|
|
if version not in ("us", "jp"):
|
|
print('fatal: version', version, 'not supported!')
|
|
sys.exit(2)
|
|
|
|
version_code = version[0]
|
|
|
|
infile = False
|
|
prevromaddr = 0
|
|
lengths = {}
|
|
|
|
p1 = re.compile(r"^ \.text\s+0x00000000([0-9a-f]{8})\s+0x([0-9a-f]+)\s+build\/" + re.escape(version_code) + r"\/(.*)\/\S+.\w$")
|
|
p2 = re.compile(r"\s+0x00000000([0-9a-f]{8})\s+(\S+)$")
|
|
|
|
map_file = open('build/' + version_code + 'ge007.' + version_code + '.map', 'r')
|
|
lines = map_file.readlines()
|
|
|
|
for line in lines:
|
|
|
|
m1 = p1.findall(line)
|
|
m2 = p2.findall(line)
|
|
|
|
if m1:
|
|
filestart = int(m1[0][0], 16)
|
|
filelen = int(m1[0][1], 16)
|
|
segment = m1[0][2]
|
|
infile = True
|
|
prevromaddr = 0
|
|
prevfuncname = None
|
|
|
|
if segment not in lengths.keys():
|
|
lengths[segment] = {}
|
|
|
|
elif infile and m2:
|
|
romaddr = int(m2[0][0], 16)
|
|
funcname = m2[0][1]
|
|
|
|
if prevfuncname:
|
|
lengths[segment][prevfuncname] = romaddr - prevromaddr
|
|
|
|
prevromaddr = romaddr
|
|
prevfuncname = funcname
|
|
|
|
elif infile:
|
|
infile = False
|
|
|
|
if prevfuncname:
|
|
lengths[segment][prevfuncname] = (filestart + filelen) - prevromaddr
|
|
|
|
return lengths
|
|
|
|
# --------------------------
|
|
# Find and return all
|
|
# ASM Function names that still
|
|
# exists in Source Files
|
|
# Only scans for C and S files
|
|
# --------------------------
|
|
def find_asm_functions(version):
|
|
|
|
p1 = re.compile(r"^glabel (\S+)$")
|
|
p2 = re.compile(r"^#ifdef VERSION_(\S+)$")
|
|
p3 = re.compile(r"^#endif$")
|
|
|
|
asm_functions = []
|
|
version = version.upper()
|
|
func_version = None
|
|
|
|
for root, dirs, files in os.walk('src'):
|
|
for file in files:
|
|
if file.endswith(".c") or file.endswith(".s"):
|
|
func_version = None
|
|
with open(os.path.join(root, file)) as _file:
|
|
for i, line in enumerate(_file.readlines()):
|
|
|
|
m1 = p1.findall(line)
|
|
m2 = p2.findall(line)
|
|
m3 = p3.findall(line)
|
|
|
|
# Found an #endif command
|
|
if m3:
|
|
func_version = None
|
|
|
|
# Found a version definition
|
|
elif m2:
|
|
func_version = m2[0]
|
|
|
|
# Found an asm function
|
|
elif m1:
|
|
if not func_version or (func_version == version):
|
|
asm_functions.append(m1[0])
|
|
|
|
func_version = None
|
|
|
|
return asm_functions
|
|
|
|
# --------------------------
|
|
# Find and return the number of completed files
|
|
# Only scans for C and S files
|
|
# --------------------------
|
|
def find_files_completed():
|
|
|
|
p = re.compile(r"^glabel (\S+)$")
|
|
files_complete = {}
|
|
|
|
files_complete['completed'] = 0
|
|
files_complete['total'] = 0
|
|
|
|
for root, dirs, files in os.walk('src'):
|
|
for file in files:
|
|
if file.endswith(".c") or file.endswith(".s"):
|
|
with open(os.path.join(root, file)) as _file:
|
|
|
|
completed = True
|
|
files_complete['total'] += 1
|
|
|
|
for i, line in enumerate(_file.readlines()):
|
|
if p.findall(line):
|
|
completed = False
|
|
break
|
|
|
|
if completed:
|
|
files_complete['completed'] += 1
|
|
|
|
return files_complete
|
|
|
|
# --------------------------
|
|
# Find and return the Name of most recent file
|
|
# Only scans for C and S files
|
|
# --------------------------
|
|
def find_last_modified_file():
|
|
lastdate = 0
|
|
lastname = ''
|
|
timestamp = 0
|
|
|
|
for root, dirs, files in os.walk('src'):
|
|
for file in files:
|
|
if file.endswith(".c") or file.endswith(".s"):
|
|
_file = os.path.join(root, file)
|
|
|
|
result = subprocess.run(['git', 'log', '-1', '--format=\"%ct\"', '--', _file], stdout=subprocess.PIPE, universal_newlines=True)
|
|
|
|
timestamp = int(result.stdout.rstrip().replace('"', ''))
|
|
|
|
if timestamp > lastdate:
|
|
lastdate = timestamp
|
|
lastname = _file
|
|
|
|
return lastname
|
|
|
|
# --------------------------
|
|
# Calculate the decomp stats
|
|
# on each folder
|
|
# --------------------------
|
|
def do_stats(version, map_file, analyse_folders):
|
|
|
|
asm_functions = find_asm_functions(version)
|
|
segments = {}
|
|
|
|
for folder in analyse_folders:
|
|
|
|
segments[folder] = {}
|
|
segments[folder]['done'] = 0
|
|
segments[folder]['left'] = 0
|
|
segments[folder]['total'] = 0
|
|
|
|
num_done = 0
|
|
num_left = 0
|
|
|
|
for function in map_file[folder]:
|
|
if function in asm_functions:
|
|
num_left += map_file[folder][function]
|
|
else:
|
|
num_done += map_file[folder][function]
|
|
|
|
segments[folder]['done'] = num_done / 4
|
|
segments[folder]['left'] = num_left / 4
|
|
segments[folder]['total'] = segments[folder]['done'] + segments[folder]['left']
|
|
|
|
return segments
|
|
|
|
def generate_report(segments, files_completed, last_modified_file, Ver):
|
|
printstring = "./tools/report/report "
|
|
|
|
totals = {}
|
|
totals['done'] = 0
|
|
totals['total'] = 0
|
|
|
|
for key in segments.keys():
|
|
printstring = printstring + str(segments[key]['done']) + ' ' + str(segments[key]['total']) + ' '
|
|
totals['done'] += segments[key]['done']
|
|
totals['total'] += segments[key]['total']
|
|
|
|
printstring = printstring + str(totals['done']) + ' ' + str(totals['total']) + ' '
|
|
printstring = printstring + str(files_completed['completed']) + ' ' + str(files_completed['total']) + ' '
|
|
printstring = printstring + './tools/report/template.html '
|
|
if Ver.upper() in ("US"):
|
|
printstring = printstring + './tools/report/index.html ' + last_modified_file + ' 0'
|
|
elif Ver.upper() in ("JP"):
|
|
printstring = printstring + './tools/report/JPN.htm ' + last_modified_file + ' 0'
|
|
else:
|
|
printstring = printstring + './tools/report/index.html ' + last_modified_file + ' 0'
|
|
subprocess.Popen(printstring.split())
|
|
|
|
def print_stats(version, segments, files_completed, last_modified_file):
|
|
totals = {}
|
|
totals['done'] = 0
|
|
totals['total'] = 0
|
|
|
|
print()
|
|
print('~> Decomp Statistics for ' + version.upper() +' version')
|
|
print('__________________________________________________________________')
|
|
print()
|
|
|
|
print('FILES')
|
|
print('\tlast modified file\t' + last_modified_file )
|
|
print('\ttotal files\t{:10,} / {:,} \t{:.2f}%'.format(int(files_completed['completed']), int(files_completed['total']), (files_completed['completed'] / files_completed['total'] * 100)))
|
|
|
|
print('__________________________________________________________________')
|
|
print()
|
|
|
|
print('WORDS')
|
|
for key in segments.keys():
|
|
print('\t{:10}\t{:10,} / {:,} \t{:.2f}%'.format(key, int(segments[key]['done']), int(segments[key]['total']), (segments[key]['done'] / segments[key]['total'] * 100)))
|
|
totals['done'] += segments[key]['done']
|
|
totals['total'] += segments[key]['total']
|
|
|
|
print('\t----------------------------------------------------------')
|
|
print('\tTOTAL\t\t{:10,} / {:,} \t{:.2f}%'.format(int(totals['done']), int(totals['total']), (totals['done'] / totals['total'] * 100)))
|
|
print('__________________________________________________________________')
|
|
print()
|
|
|
|
def print_help():
|
|
print('Usage: python3 ge-stats.py [OPTION]')
|
|
print('Generate Decompilation Stats for Goldeneye 007')
|
|
print('Example: python3 ge-stats.py --version us')
|
|
print(' ')
|
|
print('Options:')
|
|
print(' -v, --version=CODE generate decomp stats for version CODE (us or jp). Default is us')
|
|
print(' -r, --report generate html report using report tool')
|
|
print(' -h, --help display this help text and exit')
|
|
|
|
def main():
|
|
|
|
version = 'us'
|
|
run_report = False
|
|
|
|
if (len(sys.argv) > 1):
|
|
arguments, values = getopt.getopt(sys.argv[1:], "hv:r", ["help", "version=", "report"])
|
|
|
|
for current_argument, current_value in arguments:
|
|
if current_argument in ("-h", "--help"):
|
|
print_help()
|
|
sys.exit()
|
|
elif current_argument in ("-v", "--version"):
|
|
version = current_value
|
|
elif current_argument in ("-r", "--report"):
|
|
run_report = True
|
|
|
|
files_completed = find_files_completed()
|
|
|
|
last_modified_file = find_last_modified_file()
|
|
|
|
map_file = parse_map(version)
|
|
|
|
folders = ['src', 'src/game', 'src/inflate', 'src/libultra']
|
|
|
|
segments = do_stats(version, map_file, folders)
|
|
|
|
print_stats(version, segments, files_completed, last_modified_file)
|
|
|
|
if run_report:
|
|
generate_report(segments, files_completed, last_modified_file, version)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|