2020-07-13 16:18:46 -05:00
|
|
|
import re
|
2020-07-19 02:41:07 -05:00
|
|
|
import sys
|
2021-06-04 00:27:41 -05:00
|
|
|
import argparse
|
2022-12-01 16:37:37 -05:00
|
|
|
import os
|
2020-07-13 16:18:46 -05:00
|
|
|
from file_util import FileUtil
|
2021-06-04 00:27:41 -05:00
|
|
|
from score_display import ScoreDisplay
|
2025-06-01 16:15:57 +02:00
|
|
|
import plotly.graph_objects as go
|
2020-07-13 16:18:46 -05:00
|
|
|
|
2020-07-29 09:30:04 -05:00
|
|
|
ASM_FOLDERS = [
|
2022-12-01 16:37:37 -05:00
|
|
|
'./asm',
|
2025-03-25 09:07:00 -04:00
|
|
|
'./src/hasm',
|
|
|
|
|
'./libultra/src/gu',
|
|
|
|
|
'./libultra/src/libc',
|
|
|
|
|
'./libultra/src/os',
|
2020-07-29 09:30:04 -05:00
|
|
|
]
|
|
|
|
|
|
2022-12-01 16:37:37 -05:00
|
|
|
BLACKLIST = [
|
2025-03-25 09:07:00 -04:00
|
|
|
'/nonmatchings/',
|
2025-02-27 14:27:28 -05:00
|
|
|
'/assets/',
|
2025-03-25 09:07:00 -04:00
|
|
|
'/boot/',
|
|
|
|
|
'/data/',
|
|
|
|
|
'/header.s',
|
|
|
|
|
'/llmuldiv_gcc.s',
|
|
|
|
|
'/libm_vals.s'
|
2022-12-01 16:37:37 -05:00
|
|
|
]
|
|
|
|
|
|
2022-12-04 19:52:37 +00:00
|
|
|
BLACKLIST_C = [
|
2022-12-30 14:14:53 +00:00
|
|
|
'math_util.c',
|
|
|
|
|
'collision.c',
|
2022-12-04 19:52:37 +00:00
|
|
|
]
|
|
|
|
|
|
2022-12-01 16:37:37 -05:00
|
|
|
filelist = []
|
|
|
|
|
|
|
|
|
|
for asmDir in ASM_FOLDERS:
|
|
|
|
|
for root, dirs, files in os.walk(asmDir):
|
|
|
|
|
for file in files:
|
|
|
|
|
fullPath = os.path.join(root,file)
|
|
|
|
|
skipThis = False
|
|
|
|
|
for blackListEntry in BLACKLIST:
|
|
|
|
|
if blackListEntry in fullPath:
|
|
|
|
|
skipThis = True
|
|
|
|
|
break
|
|
|
|
|
if not skipThis and fullPath.endswith('.s'):
|
|
|
|
|
filelist.append(fullPath)
|
|
|
|
|
|
2020-07-29 09:30:04 -05:00
|
|
|
# These will automatically be added to the adventure one percentage.
|
2022-12-01 16:37:37 -05:00
|
|
|
ASM_LABELS = []
|
2025-03-25 09:07:00 -04:00
|
|
|
GLABEL_REGEX = r'glabel|leaf ([0-9A-Za-z_]+)'
|
2022-12-01 16:37:37 -05:00
|
|
|
for filename in filelist:
|
|
|
|
|
with open(filename, 'r') as asmFile:
|
|
|
|
|
text = asmFile.read()
|
|
|
|
|
matches = re.finditer(GLABEL_REGEX, text, re.MULTILINE)
|
|
|
|
|
for matchNum, match in enumerate(matches, start=1):
|
|
|
|
|
glabel = match.groups()[0]
|
|
|
|
|
if not glabel in ASM_LABELS:
|
|
|
|
|
ASM_LABELS.append(glabel)
|
2020-07-29 09:30:04 -05:00
|
|
|
|
2025-03-25 09:07:00 -04:00
|
|
|
BUILD_DIRECTORY = './build'
|
2020-07-13 16:18:46 -05:00
|
|
|
SRC_DIRECTORY = './src'
|
2025-03-25 09:07:00 -04:00
|
|
|
LIB_SRC_DIRECTORY = './libultra/src'
|
|
|
|
|
FUNCTION_REGEX = r'^(?<!static\s)(?:(\/[*][*!][*]*\n(?:[^\/]*\n)+?\s*[*]\/\n)(?:\s*)*?)?(?:\s*UNUSED\s+)?([^\s]+)\s(?:\s|[*])*?([0-9A-Za-z_]+)\s*[(][^)]*[)]\s*{'
|
|
|
|
|
GLOBAL_ASM_REGEX = r'\#pragma\sGLOBAL_ASM[(]".*(?=\/)\/([^.]+).s"[)]'
|
|
|
|
|
WIP_REGEX = r'ifdef\s+(?:NON_MATCHING|NON_EQUIVALENT)(?:.|\n)*?\#else\s*(\#pragma\sGLOBAL_ASM[(][^)]*[)])(.|\n)*?'
|
2025-07-04 14:43:05 -04:00
|
|
|
NON_MATCHING_REGEX = re.compile(r'^#ifdef +NON_MATCHING(?:.|\n)*?(?:\s*UNUSED\s+)?\S+\s(?:\s|[*])*?([0-9A-Za-z_]+)\s*[(][^)]*[)]\s*{', re.MULTILINE)
|
|
|
|
|
NON_EQUVIALENT_REGEX = re.compile(r'^#ifdef +NON_EQUIVALENT(?:.|\n)*?(?:\s*UNUSED\s+)?\S+\s(?:\s|[*])*?([0-9A-Za-z_]+)\s*[(][^)]*[)]\s*{', re.MULTILINE)
|
2020-07-13 16:18:46 -05:00
|
|
|
|
2020-07-13 17:42:17 -05:00
|
|
|
CODE_START = 0x80000400
|
|
|
|
|
CODE_END = 0x800D75F4
|
|
|
|
|
CODE_SIZE = CODE_END - CODE_START
|
|
|
|
|
|
|
|
|
|
class DkrMapFile:
|
|
|
|
|
def __init__(self):
|
2020-08-01 07:43:45 -05:00
|
|
|
try:
|
2025-03-25 09:07:00 -04:00
|
|
|
with open(BUILD_DIRECTORY + '/dkr.us.v77.map', 'r') as mapFile:
|
2020-08-01 07:43:45 -05:00
|
|
|
self.functionSizes = {}
|
|
|
|
|
functions = []
|
|
|
|
|
lines = mapFile.read().split('\n')
|
|
|
|
|
for line in lines:
|
2025-03-25 09:07:00 -04:00
|
|
|
lineSet = 0
|
|
|
|
|
if line.startswith(' 0x00000000'):
|
|
|
|
|
lineSet = 26
|
|
|
|
|
elif line.startswith(' 0x8'):
|
|
|
|
|
lineSet = 18
|
|
|
|
|
if (lineSet != 0):
|
2020-08-01 07:43:45 -05:00
|
|
|
if '=' in line:
|
2025-04-06 03:06:23 +02:00
|
|
|
line = line[0:line.find('=') - 1]
|
|
|
|
|
try:
|
|
|
|
|
address = int(line[lineSet:lineSet+8], 16)
|
|
|
|
|
except ValueError:
|
|
|
|
|
# no or incorrect address, skip
|
|
|
|
|
continue
|
|
|
|
|
|
2020-08-01 07:43:45 -05:00
|
|
|
if address >= CODE_START and address < CODE_END:
|
|
|
|
|
symbol = line[line.rfind(' ')+1:]
|
2025-02-27 14:27:28 -05:00
|
|
|
if (not symbol.startswith(".L") and not symbol.startswith("L800")
|
|
|
|
|
and not self.contains_forbidden_func(symbol)):
|
|
|
|
|
functions.append((symbol, address))
|
2020-08-01 07:43:45 -05:00
|
|
|
functions.sort(key=lambda x:x[1]) # Sort by RAM address
|
|
|
|
|
for i in range(0, len(functions) - 1):
|
|
|
|
|
self.functionSizes[functions[i][0]] = functions[i + 1][1] - functions[i][1]
|
|
|
|
|
self.functionSizes[functions[len(functions) - 1][0]] = CODE_END - 0x800D7570
|
|
|
|
|
self.numFunctions = len(functions)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
print("You must build a rom before it can be scored!")
|
|
|
|
|
sys.exit()
|
2025-02-27 14:27:28 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def contains_forbidden_func(self, string):
|
2025-03-25 09:07:00 -04:00
|
|
|
for forbidden in ['__FUNC_RAM_START', 'cosf', 'sinf', 'main_VRAM', 'main_TEXT_START', '.']:
|
2025-02-27 14:27:28 -05:00
|
|
|
if forbidden in string:
|
|
|
|
|
return True
|
|
|
|
|
return False
|
2020-07-13 17:42:17 -05:00
|
|
|
|
|
|
|
|
MAP_FILE = DkrMapFile()
|
|
|
|
|
|
2025-03-25 09:07:00 -04:00
|
|
|
# Adding regional and other version functions here to ignore since we're only scoring US_1.0 for now.
|
|
|
|
|
NOT_FUNCTION_NAMES = ['if', 'else', 'switch', 'while', 'for', 'dmacopy_internal', 'func_80082BC8_837C8', 'rumble_enable',
|
|
|
|
|
'func_800C6464_C7064', 'func_800C663C_C723C', 'func_800C67F4_C73F4', 'func_800C6870_C7470',
|
2025-04-14 21:13:21 -04:00
|
|
|
'func_800C68CC_C74CC', 'fontCreateDisplayList', 'func_800C7744_C8344', 'func_800C7804_C8404',
|
2025-06-17 13:59:37 -04:00
|
|
|
'fontConvertString', 'func_800C78E0_C84E0']
|
2022-04-29 16:59:02 -04:00
|
|
|
|
2020-07-13 16:18:46 -05:00
|
|
|
class ScoreFileMatch:
|
|
|
|
|
def __init__(self, comment, functionName):
|
|
|
|
|
self.comment = comment
|
|
|
|
|
self.functionName = functionName
|
2025-07-21 21:04:52 -04:00
|
|
|
self.isProperlyNamed = not functionName.startswith("func_")
|
|
|
|
|
self.isDocumented = (comment != None) and self.isProperlyNamed
|
2025-01-02 21:09:53 +01:00
|
|
|
if functionName in MAP_FILE.functionSizes:
|
|
|
|
|
self.size = MAP_FILE.functionSizes[functionName]
|
|
|
|
|
else:
|
|
|
|
|
self.size = 0
|
2020-07-13 16:18:46 -05:00
|
|
|
|
|
|
|
|
class ScoreFile:
|
|
|
|
|
def __init__(self, filepath):
|
|
|
|
|
self.functions = []
|
2020-07-19 02:41:07 -05:00
|
|
|
self.unfinishedSize = 0
|
2020-07-13 16:18:46 -05:00
|
|
|
self.numGlobalAsms = 0
|
|
|
|
|
self.path = filepath
|
|
|
|
|
self.read_file()
|
|
|
|
|
self.get_matches()
|
|
|
|
|
#print(self.path, len(self.functions), self.numGlobalAsms, self.get_number_of_documented_functions())
|
|
|
|
|
|
|
|
|
|
def read_file(self):
|
|
|
|
|
with open(self.path, "r") as inFile:
|
|
|
|
|
self.text = inFile.read()
|
2025-06-01 16:15:57 +02:00
|
|
|
|
2023-11-02 21:33:35 -05:00
|
|
|
self.nonMatchings = re.findall(NON_MATCHING_REGEX, self.text)
|
|
|
|
|
self.nonMatchingsSizes = 0
|
|
|
|
|
for nonMatching in self.nonMatchings:
|
|
|
|
|
self.nonMatchingsSizes += MAP_FILE.functionSizes[nonMatching]
|
|
|
|
|
self.numNonMatchings = len(self.nonMatchings)
|
2025-06-11 16:42:51 -04:00
|
|
|
all_nonEquivalents = re.findall(NON_EQUVIALENT_REGEX, self.text)
|
|
|
|
|
# Filter out the ones that are in NOT_FUNCTION_NAMES
|
|
|
|
|
self.nonEquivalents = [ne for ne in all_nonEquivalents if ne not in NOT_FUNCTION_NAMES]
|
2025-06-01 16:15:57 +02:00
|
|
|
|
|
|
|
|
self.nonEquivalentsSizes = 0
|
|
|
|
|
for nonEquivalent in self.nonEquivalents:
|
2025-06-11 16:42:51 -04:00
|
|
|
self.nonEquivalentsSizes += MAP_FILE.functionSizes[nonEquivalent]
|
2025-06-01 16:15:57 +02:00
|
|
|
self.numNonEquivalents = len(self.nonEquivalents)
|
2021-06-13 14:48:10 -05:00
|
|
|
self.text = re.sub(WIP_REGEX, r"GLOBAL_ASM(\1)", self.text)
|
2020-07-13 16:18:46 -05:00
|
|
|
|
|
|
|
|
def get_matches(self):
|
|
|
|
|
matches = re.finditer(FUNCTION_REGEX, self.text, re.MULTILINE)
|
2025-06-11 16:42:51 -04:00
|
|
|
# Filter out the ones that are in NOT_FUNCTION_NAMES
|
|
|
|
|
matches = [match for match in matches if match.groups()[2] not in NOT_FUNCTION_NAMES]
|
2020-07-13 16:18:46 -05:00
|
|
|
for matchNum, match in enumerate(matches, start=1):
|
|
|
|
|
groups = match.groups()
|
2022-04-29 16:59:02 -04:00
|
|
|
if groups[2] not in NOT_FUNCTION_NAMES:
|
|
|
|
|
self.functions.append(ScoreFileMatch(groups[0], groups[2]))
|
2020-07-19 02:41:07 -05:00
|
|
|
matches = re.finditer(GLOBAL_ASM_REGEX, self.text, re.MULTILINE)
|
2025-06-11 16:42:51 -04:00
|
|
|
# Filter out the ones that are in NOT_FUNCTION_NAMES
|
|
|
|
|
matches = [match for match in matches if match.groups()[0] not in NOT_FUNCTION_NAMES]
|
2020-07-19 02:41:07 -05:00
|
|
|
for matchNum, match in enumerate(matches, start=1):
|
|
|
|
|
groups = match.groups()
|
|
|
|
|
self.numGlobalAsms += 1
|
|
|
|
|
try:
|
|
|
|
|
self.unfinishedSize += MAP_FILE.functionSizes[groups[0]]
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
2021-07-15 22:38:50 -05:00
|
|
|
|
2023-01-06 19:39:37 +00:00
|
|
|
def get_number_of_functions(self):
|
|
|
|
|
return len(self.functions)
|
|
|
|
|
|
2020-07-13 16:18:46 -05:00
|
|
|
def get_number_of_documented_functions(self):
|
|
|
|
|
count = 0
|
|
|
|
|
for func in self.functions:
|
|
|
|
|
if func.isDocumented:
|
|
|
|
|
count += 1
|
|
|
|
|
return count
|
2025-07-21 21:04:52 -04:00
|
|
|
|
|
|
|
|
def get_number_of_properly_named_functions(self):
|
|
|
|
|
count = 0
|
|
|
|
|
for func in self.functions:
|
|
|
|
|
if func.isProperlyNamed:
|
|
|
|
|
count += 1
|
|
|
|
|
return count
|
|
|
|
|
|
|
|
|
|
def get_number_of_functions_with_comments(self):
|
|
|
|
|
count = 0
|
|
|
|
|
for func in self.functions:
|
|
|
|
|
if func.comment != None:
|
|
|
|
|
count += 1
|
|
|
|
|
return count
|
2020-07-13 17:42:17 -05:00
|
|
|
|
|
|
|
|
def get_size_of_functions(self):
|
|
|
|
|
size = 0
|
|
|
|
|
for func in self.functions:
|
|
|
|
|
size += func.size
|
|
|
|
|
return size
|
|
|
|
|
|
2023-11-02 21:33:35 -05:00
|
|
|
def get_size_of_functions_with_nonmatching(self):
|
|
|
|
|
return self.get_size_of_functions() + self.nonMatchingsSizes
|
|
|
|
|
|
2020-07-13 17:42:17 -05:00
|
|
|
def get_size_of_documented_functions(self):
|
|
|
|
|
size = 0
|
|
|
|
|
for func in self.functions:
|
|
|
|
|
if func.isDocumented:
|
|
|
|
|
size += func.size
|
|
|
|
|
return size
|
2020-07-13 16:18:46 -05:00
|
|
|
|
|
|
|
|
def main():
|
2020-07-19 02:41:07 -05:00
|
|
|
showTopFiles = 0
|
2021-06-04 00:27:41 -05:00
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(description="")
|
|
|
|
|
parser.add_argument("-t", "--top", help="(Optional) Shows the top N files remaining.")
|
|
|
|
|
parser.add_argument("-a", "--adventure", help="(Optional) Only shows adventure 1 or 2 based on passed in value.", choices=['1', '2'])
|
2023-02-10 16:39:25 -07:00
|
|
|
parser.add_argument("-s", "--summary", help="(Optional) Only prints the percentages for adventure 1 and 2", action='store_true')
|
2025-06-04 21:31:54 +02:00
|
|
|
parser.add_argument("--treemap", help="(Optional) Generates a treemap .html file", metavar="path/to/treemap-file.html")
|
2021-06-04 00:27:41 -05:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
adventureSelect = 3 # Show both adventures by default
|
|
|
|
|
if args.adventure != None:
|
|
|
|
|
adventureSelect = int(args.adventure)
|
|
|
|
|
if args.top != None:
|
|
|
|
|
showTopFiles = int(args.top)
|
2020-07-19 02:41:07 -05:00
|
|
|
|
2020-07-13 16:18:46 -05:00
|
|
|
scoreFiles = []
|
|
|
|
|
totalNumberOfDecompiledFunctions = 0
|
|
|
|
|
totalNumberOfDocumentedFunctions = 0
|
2025-07-21 21:04:52 -04:00
|
|
|
totalNumberOfProperlyNamedFunctions = 0
|
|
|
|
|
totalNumberOfCommentedFunctions = 0
|
2020-07-13 16:18:46 -05:00
|
|
|
totalNumberOfGlobalAsms = 0
|
2021-07-15 22:38:50 -05:00
|
|
|
totalNumberOfNonMatching = 0
|
2022-02-08 11:27:00 -05:00
|
|
|
totalNumberOfNonEquivalent = 0
|
2020-07-13 17:42:17 -05:00
|
|
|
totalSizeOfDecompiledFunctions = 0
|
2023-11-02 21:33:35 -05:00
|
|
|
totalSizeOfDecompiledAndNonMatchingFunctions = 0
|
2020-07-13 17:42:17 -05:00
|
|
|
totalSizeOfDocumentedFunctions = 0
|
2023-01-06 19:39:37 +00:00
|
|
|
ignoreNumberDocumentedFunctions = 0
|
|
|
|
|
ignoreSizeDocumentedFunctions = 0
|
2020-07-31 15:45:49 -05:00
|
|
|
|
|
|
|
|
srcFilenames = FileUtil.get_filenames_from_directory_recursive(SRC_DIRECTORY, extensions=('.c'))
|
2020-07-13 16:18:46 -05:00
|
|
|
for filename in srcFilenames:
|
2022-12-04 19:52:37 +00:00
|
|
|
skipThis = False
|
|
|
|
|
for blackListEntry in BLACKLIST_C:
|
|
|
|
|
if blackListEntry in filename:
|
|
|
|
|
skipThis = True
|
|
|
|
|
break
|
2022-12-30 14:14:53 +00:00
|
|
|
if not skipThis:
|
2023-01-06 19:39:37 +00:00
|
|
|
# Get score properties of dkr functions.
|
2022-12-30 14:14:53 +00:00
|
|
|
scoreFile = ScoreFile(SRC_DIRECTORY + '/' + filename)
|
|
|
|
|
totalNumberOfDecompiledFunctions += len(scoreFile.functions)
|
|
|
|
|
totalNumberOfGlobalAsms += scoreFile.numGlobalAsms
|
|
|
|
|
totalNumberOfNonMatching += scoreFile.numNonMatchings
|
|
|
|
|
totalNumberOfNonEquivalent += scoreFile.numNonEquivalents
|
|
|
|
|
totalNumberOfDocumentedFunctions += scoreFile.get_number_of_documented_functions()
|
2025-07-21 21:04:52 -04:00
|
|
|
totalNumberOfCommentedFunctions += scoreFile.get_number_of_functions_with_comments()
|
|
|
|
|
totalNumberOfProperlyNamedFunctions += scoreFile.get_number_of_properly_named_functions()
|
2022-12-30 14:14:53 +00:00
|
|
|
totalSizeOfDecompiledFunctions += scoreFile.get_size_of_functions()
|
2023-11-02 21:33:35 -05:00
|
|
|
totalSizeOfDecompiledAndNonMatchingFunctions += scoreFile.get_size_of_functions_with_nonmatching()
|
2022-12-30 14:14:53 +00:00
|
|
|
totalSizeOfDocumentedFunctions += scoreFile.get_size_of_documented_functions()
|
|
|
|
|
scoreFiles.append(scoreFile)
|
2023-01-06 19:39:37 +00:00
|
|
|
# Get score properties of libultra functions.
|
2020-07-31 15:45:49 -05:00
|
|
|
srcFilenames = FileUtil.get_filenames_from_directory_recursive(LIB_SRC_DIRECTORY, extensions=('.c'))
|
|
|
|
|
for filename in srcFilenames:
|
|
|
|
|
scoreFile = ScoreFile(LIB_SRC_DIRECTORY + '/' + filename)
|
|
|
|
|
totalNumberOfDecompiledFunctions += len(scoreFile.functions)
|
|
|
|
|
totalNumberOfGlobalAsms += scoreFile.numGlobalAsms
|
2023-01-06 19:39:37 +00:00
|
|
|
#totalNumberOfDocumentedFunctions += scoreFile.get_number_of_documented_functions()
|
|
|
|
|
ignoreNumberDocumentedFunctions += scoreFile.get_number_of_functions()
|
2020-07-31 15:45:49 -05:00
|
|
|
totalSizeOfDecompiledFunctions += scoreFile.get_size_of_functions()
|
2023-11-02 21:33:35 -05:00
|
|
|
totalSizeOfDecompiledAndNonMatchingFunctions += scoreFile.get_size_of_functions_with_nonmatching()
|
2023-01-06 19:39:37 +00:00
|
|
|
#totalSizeOfDocumentedFunctions += scoreFile.get_size_of_documented_functions()
|
|
|
|
|
ignoreSizeDocumentedFunctions += scoreFile.get_size_of_functions()
|
2020-07-31 15:45:49 -05:00
|
|
|
scoreFiles.append(scoreFile)
|
|
|
|
|
|
2020-07-13 16:18:46 -05:00
|
|
|
|
2021-06-04 00:27:41 -05:00
|
|
|
for asm_function in ASM_LABELS:
|
|
|
|
|
if asm_function in MAP_FILE.functionSizes:
|
2025-02-27 14:27:28 -05:00
|
|
|
totalNumberOfDecompiledFunctions += 1 # Consider hand written asm as "decompiled"
|
2023-11-02 21:33:35 -05:00
|
|
|
asmFuncSize = MAP_FILE.functionSizes[asm_function]
|
|
|
|
|
totalSizeOfDecompiledFunctions += asmFuncSize
|
|
|
|
|
totalSizeOfDecompiledAndNonMatchingFunctions += asmFuncSize
|
2025-02-27 14:27:28 -05:00
|
|
|
|
|
|
|
|
totalNumberOfFunctions = totalNumberOfDecompiledFunctions + totalNumberOfGlobalAsms
|
2020-07-13 17:42:17 -05:00
|
|
|
adventureOnePercentage = (totalSizeOfDecompiledFunctions / CODE_SIZE) * 100
|
2023-11-02 21:33:35 -05:00
|
|
|
adventureOnePercentageWithNonMatching = (totalSizeOfDecompiledAndNonMatchingFunctions / CODE_SIZE) * 100
|
2023-01-06 19:39:37 +00:00
|
|
|
adventureTwoPercentage = (totalSizeOfDocumentedFunctions / (CODE_SIZE - ignoreSizeDocumentedFunctions)) * 100
|
2020-07-13 16:18:46 -05:00
|
|
|
|
2023-02-10 16:39:25 -07:00
|
|
|
if args.summary:
|
|
|
|
|
print(f"Decomp progress: {adventureOnePercentage:5.2f}%")
|
|
|
|
|
print(f"Documentation progress: {adventureTwoPercentage:5.2f}%")
|
|
|
|
|
sys.exit(0)
|
2025-06-01 16:15:57 +02:00
|
|
|
|
|
|
|
|
if args.treemap:
|
|
|
|
|
print(f"Generating progress treemap, outputting file to {args.treemap}")
|
|
|
|
|
|
|
|
|
|
# Prepare data
|
|
|
|
|
labels = ["Decomp"]
|
|
|
|
|
parents = [None]
|
|
|
|
|
values = [0]
|
|
|
|
|
colours = [None]
|
|
|
|
|
legends = [None]
|
|
|
|
|
|
|
|
|
|
for scoreFile in scoreFiles:
|
|
|
|
|
parentName = scoreFile.path.replace('./src/', '')
|
|
|
|
|
|
|
|
|
|
for func in scoreFile.functions:
|
|
|
|
|
color = '#92ac68'
|
|
|
|
|
legend = 'matched'
|
|
|
|
|
if func.isDocumented:
|
|
|
|
|
color = 'green'
|
|
|
|
|
legend = 'documented'
|
|
|
|
|
|
|
|
|
|
labels.append(func.functionName)
|
|
|
|
|
parents.append(parentName)
|
|
|
|
|
values.append(func.size)
|
|
|
|
|
colours.append(color)
|
|
|
|
|
legends.append(legend)
|
|
|
|
|
|
|
|
|
|
matches = re.finditer(GLOBAL_ASM_REGEX, scoreFile.text, re.MULTILINE)
|
|
|
|
|
for match in matches:
|
|
|
|
|
funcName = match.groups()[0]
|
|
|
|
|
size = MAP_FILE.functionSizes[funcName]
|
|
|
|
|
|
|
|
|
|
color = "grey"
|
|
|
|
|
legend = "N/A"
|
|
|
|
|
if funcName in scoreFile.nonMatchings:
|
|
|
|
|
color = 'orange'
|
|
|
|
|
legend = "non matching"
|
|
|
|
|
elif funcName in scoreFile.nonEquivalents:
|
|
|
|
|
color = 'red'
|
|
|
|
|
legend = "non equivalent"
|
|
|
|
|
|
|
|
|
|
labels.append(funcName)
|
|
|
|
|
parents.append(parentName)
|
|
|
|
|
values.append(size)
|
|
|
|
|
colours.append(color)
|
|
|
|
|
legends.append(legend)
|
|
|
|
|
|
|
|
|
|
labels.append(parentName)
|
|
|
|
|
parents.append("Decomp")
|
|
|
|
|
values.append(0)
|
|
|
|
|
colours.append(None)
|
|
|
|
|
legends.append(None)
|
|
|
|
|
|
|
|
|
|
matchPercents = []
|
|
|
|
|
for value in values:
|
|
|
|
|
if value > 0:
|
|
|
|
|
matchPercents.append(f"Size: {value} bytes<br>Total: {value / CODE_SIZE * 100:.2f}%")
|
|
|
|
|
else:
|
|
|
|
|
matchPercents.append(None)
|
|
|
|
|
|
|
|
|
|
fig = go.Figure(go.Treemap(
|
|
|
|
|
labels = labels,
|
|
|
|
|
parents = parents,
|
|
|
|
|
values = values,
|
|
|
|
|
text=matchPercents,
|
|
|
|
|
hovertemplate='<b>%{label}</b><br>Size: %{value} bytes<extra></extra>',
|
|
|
|
|
marker = dict(colors = colours),
|
|
|
|
|
root_color="lightgrey"
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
# Add custom legend using annotations
|
|
|
|
|
legend_items = [
|
|
|
|
|
("green", "documented"),
|
|
|
|
|
("#92ac68", "matched"),
|
|
|
|
|
("orange", "non matching"),
|
|
|
|
|
("red", "non equivalent"),
|
|
|
|
|
("grey", "N/A")
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
annotations = []
|
|
|
|
|
for i, (color, label) in enumerate(legend_items):
|
|
|
|
|
annotations.append(dict(
|
|
|
|
|
x=1.05,
|
|
|
|
|
y=1 - (i * 0.05),
|
|
|
|
|
xref="paper",
|
|
|
|
|
yref="paper",
|
|
|
|
|
showarrow=False,
|
|
|
|
|
text=f"<span style='color:{color}'>■</span> {label}",
|
|
|
|
|
font=dict(size=12)
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fig.update_layout(
|
|
|
|
|
margin=dict(t=50, l=25, r=150, b=25), # Extra right margin for legend
|
|
|
|
|
title="Decomp Progress",
|
|
|
|
|
annotations=annotations
|
|
|
|
|
)
|
|
|
|
|
output_path = "treemap.html"
|
|
|
|
|
# This will raise an error if writing fails
|
|
|
|
|
fig.write_html(output_path)
|
|
|
|
|
sys.exit(0)
|
2025-07-21 21:04:52 -04:00
|
|
|
|
|
|
|
|
displayedNumberOfDocumentedFunctions = totalNumberOfFunctions - ignoreNumberDocumentedFunctions
|
2025-06-01 16:15:57 +02:00
|
|
|
|
2021-06-04 00:27:41 -05:00
|
|
|
scoreDisplay = ScoreDisplay()
|
2025-07-21 21:04:52 -04:00
|
|
|
print(scoreDisplay.getDisplay(adventureOnePercentage, adventureOnePercentageWithNonMatching, adventureTwoPercentage, adventureSelect, totalNumberOfDecompiledFunctions, totalNumberOfGlobalAsms, totalNumberOfNonMatching, totalNumberOfNonEquivalent, totalNumberOfDocumentedFunctions, displayedNumberOfDocumentedFunctions - totalNumberOfDocumentedFunctions, displayedNumberOfDocumentedFunctions - totalNumberOfProperlyNamedFunctions, displayedNumberOfDocumentedFunctions - totalNumberOfCommentedFunctions))
|
2020-07-19 02:41:07 -05:00
|
|
|
|
|
|
|
|
if showTopFiles > 0:
|
|
|
|
|
if showTopFiles > len(scoreFiles):
|
|
|
|
|
showTopFiles = len(scoreFiles)
|
|
|
|
|
print('======= TOP FILES ======== | TODO | DONE |')
|
|
|
|
|
files = []
|
|
|
|
|
for file in scoreFiles:
|
|
|
|
|
files.append([file.path, file.unfinishedSize, file.get_size_of_functions()])
|
|
|
|
|
files.sort(key=lambda x:x[1], reverse=True) # Sort by Size, Largest to Smallest
|
|
|
|
|
for i in range(0, showTopFiles):
|
|
|
|
|
percentageRemaining = (files[i][1] / CODE_SIZE) * 100
|
|
|
|
|
percentageDone = (files[i][2] / CODE_SIZE) * 100
|
2021-06-04 00:27:41 -05:00
|
|
|
funcName = files[i][0]
|
|
|
|
|
if '/' in funcName:
|
|
|
|
|
funcName = funcName[funcName.rindex('/') + 1:]
|
2020-07-19 02:41:07 -05:00
|
|
|
print("", funcName, (" " * (24 - len(funcName))), "| {:5.2f}% | {:5.2f}% |".format(percentageRemaining, percentageDone))
|
2020-07-13 16:18:46 -05:00
|
|
|
|
|
|
|
|
|
2023-02-10 16:39:25 -07:00
|
|
|
main()
|