Files
Diddy-Kong-Racing/tools/python/score.py
T

258 lines
11 KiB
Python
Raw Normal View History

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
import time
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
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',
2020-07-31 15:45:49 -05:00
'./lib/asm',
2020-07-29 09:30:04 -05:00
]
2022-12-01 16:37:37 -05:00
BLACKLIST = [
'/non_matchings/'
]
2022-12-04 19:52:37 +00:00
BLACKLIST_C = [
'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 = []
GLABEL_REGEX = r'glabel ([0-9A-Za-z_]+)'
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
2020-08-04 15:16:23 -05:00
BUILD_DIRECTORY = './build/us_1.0'
2020-07-13 16:18:46 -05:00
SRC_DIRECTORY = './src'
2020-07-31 15:45:49 -05:00
LIB_SRC_DIRECTORY = './lib/src'
FUNCTION_REGEX = r'^(?<!static\s)(?:(\/[*][*!][*]*\n(?:[^/]*\n)+?\s*[*]\/\n)(?:\s*)*?)?(?:\s*UNUSED\s+)?([^\s]+)\s(?:\s|[*])*?([0-9A-Za-z_]+)\s*[(][^)]*[)]\s*{'
2020-07-31 15:45:49 -05:00
GLOBAL_ASM_REGEX = r'GLOBAL_ASM[(]".*(?=\/)\/([^.]+).s"[)]'
2023-10-20 08:12:50 -04:00
WIP_REGEX = r'#ifdef\s+(?:NON_MATCHING|NON_EQUIVALENT)(?:.|\n)*?#else\s*(GLOBAL_ASM[(][^)]*[)])(.|\n)*?#endif'
2023-11-02 21:33:35 -05: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', re.MULTILINE)
2020-07-13 16:18:46 -05:00
CODE_START = 0x80000400
CODE_END = 0x800D75F4
CODE_SIZE = CODE_END - CODE_START
class DkrMapFile:
def __init__(self):
try:
2020-08-04 15:16:23 -05:00
with open(BUILD_DIRECTORY + '/dkr.map', 'r') as mapFile:
self.functionSizes = {}
functions = []
lines = mapFile.read().split('\n')
for line in lines:
if line.startswith(' 0x00000000'):
if '=' in line:
line = line[0:line.find('=')-1]
address = int(line[26:26+8], 16)
if address >= CODE_START and address < CODE_END:
symbol = line[line.rfind(' ')+1:]
functions.append((symbol, address))
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()
MAP_FILE = DkrMapFile()
2022-04-29 16:59:02 -04:00
NOT_FUNCTION_NAMES = ['if', 'else', 'switch', 'while', 'for']
2020-07-13 16:18:46 -05:00
class ScoreFileMatch:
def __init__(self, comment, functionName):
self.comment = comment
self.functionName = functionName
self.isDocumented = (comment != None) and not functionName.startswith("func_")
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()
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)
self.numNonEquivalents = len(re.findall(NON_EQUVIALENT_REGEX, self.text))
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)
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)
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
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
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')
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
totalNumberOfGlobalAsms = 0
2021-07-15 22:38:50 -05:00
totalNumberOfNonMatching = 0
totalNumberOfNonEquivalent = 0
totalSizeOfDecompiledFunctions = 0
2023-11-02 21:33:35 -05:00
totalSizeOfDecompiledAndNonMatchingFunctions = 0
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
if not skipThis:
2023-01-06 19:39:37 +00:00
# Get score properties of dkr functions.
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()
totalSizeOfDecompiledFunctions += scoreFile.get_size_of_functions()
2023-11-02 21:33:35 -05:00
totalSizeOfDecompiledAndNonMatchingFunctions += scoreFile.get_size_of_functions_with_nonmatching()
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
totalNumberOfFunctions = totalNumberOfDecompiledFunctions + totalNumberOfGlobalAsms
2021-06-04 00:27:41 -05:00
for asm_function in ASM_LABELS:
if asm_function in MAP_FILE.functionSizes:
2023-11-02 21:33:35 -05:00
asmFuncSize = MAP_FILE.functionSizes[asm_function]
totalSizeOfDecompiledFunctions += asmFuncSize
totalSizeOfDecompiledAndNonMatchingFunctions += asmFuncSize
2020-07-29 09:30:04 -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)
2021-06-04 00:27:41 -05:00
scoreDisplay = ScoreDisplay()
2023-11-02 21:33:35 -05:00
print(scoreDisplay.getDisplay(adventureOnePercentage, adventureOnePercentageWithNonMatching, adventureTwoPercentage, adventureSelect, totalNumberOfDecompiledFunctions, totalNumberOfGlobalAsms, totalNumberOfNonMatching, totalNumberOfNonEquivalent, totalNumberOfDocumentedFunctions, (totalNumberOfFunctions - ignoreNumberDocumentedFunctions) - totalNumberOfDocumentedFunctions))
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()