#!/usr/bin/env python3 import colorama colorama.init() import argparse import os import re import collections regex_fileDataEntry = re.compile(r"^\s+(?P
[^\s]+)\s+(?P0x[^\s]+)\s+(?P0x[^\s]+)\s+(?P[^\s]+)$") regex_bssEntry = re.compile(r"^\s+(?P0x[^\s]+)\s+(?P[^\s]+)$") regex_label = re.compile(r"^(?PL[0-9A-F]{8})$") VarInfo = collections.namedtuple("VarInfo", ["file", "vram"]) def parseMapFile(mapPath: str): with open(mapPath) as f: mapData = f.read() startIndex = mapData.find("..makerom") mapData = mapData[startIndex:] # print(len(mapData)) symbolsDict = collections.OrderedDict() inFile = False currentFile = "" mapLines = mapData.split("\n") for line in mapLines: if inFile: if line.startswith(" "): entryMatch = regex_bssEntry.search(line) # Find variable if entryMatch is not None: varName = entryMatch["name"] varVram = int(entryMatch["vram"], 16) # Filter out jump table labels labelMatch = regex_label.search(varName) if labelMatch is None: symbolsDict[varName] = VarInfo( currentFile, varVram ) # print( symbolsDict[varName] ) else: inFile = False else: if line.startswith(" .bss "): inFile = False entryMatch = regex_fileDataEntry.search(line) # Find file if entryMatch is not None: name = "/".join(entryMatch["name"].split("/")[1:]) # mapfile only contains .o files, so just strip the last character to replace it # we assume all the .c files are in the src folder, and all others are .s (true for OoT/MM) if name.split("/")[0] == "src": name = name[:-1] + "c" else: name = name[:-1] + "s" size = int(entryMatch["size"], 16) vram = int(entryMatch["vram"], 16) if size > 0: inFile = True currentFile = name return symbolsDict Compared = collections.namedtuple("Compared", [ "buildAddress", "buildFile", "expectedAddress", "expectedFile", "diff"]) def compareMapFiles(mapFileBuild: str, mapFileExpected: str): badFiles = set() missingFiles = set() print("Build mapfile: " + mapFileBuild, file=os.sys.stderr) print("Expected mapfile: " + mapFileExpected, file=os.sys.stderr) print("", file=os.sys.stderr) if not os.path.exists(mapFileBuild): print(f"{colorama.Fore.LIGHTRED_EX}error{colorama.Fore.RESET}: mapfile not found at {mapFileBuild}. Did you enter the correct path?", file=os.sys.stderr) exit(1) if not os.path.exists(mapFileExpected): print(f"{colorama.Fore.LIGHTRED_EX}error{colorama.Fore.RESET}: expected mapfile not found at {mapFileExpected}. Is 'expected' missing or in a different folder?", file=os.sys.stderr) exit(1) buildMap = parseMapFile(mapFileBuild) expectedMap = parseMapFile(mapFileExpected) comparedDict = collections.OrderedDict() for symbol in buildMap: if symbol in expectedMap: comparedDict[symbol] = Compared( buildMap[symbol].vram, buildMap[symbol].file, expectedMap[symbol].vram, expectedMap[symbol].file, buildMap[symbol].vram - expectedMap[symbol].vram ) if comparedDict[symbol].diff != 0: badFiles.add(buildMap[symbol].file) else: missingFiles.add(buildMap[symbol].file) comparedDict[symbol] = Compared( buildMap[symbol].vram, buildMap[symbol].file, -1, "", "Unknown" ) for symbol in expectedMap: if not symbol in buildMap: missingFiles.add(expectedMap[symbol].file) comparedDict[symbol] = Compared( -1, "", expectedMap[symbol].vram, expectedMap[symbol].file, "Unknown" ) return badFiles, missingFiles, comparedDict def printCsv(badFiles, missingFiles, comparedDict, printAll = True): print("Symbol Name,Build Address,Build File,Expected Address,Expected File,Difference,GOOD/BAD/MISSING") # If it's bad or missing, don't need to do anything special. # If it's good, check for if it's in a file with bad or missing stuff, and check if print all is on. If none of these, print it. for symbol in comparedDict: symbolInfo = comparedDict[symbol] symbolGood = colorama.Fore.RED + "BAD" + colorama.Fore.RESET if type(symbolInfo.diff) != int: symbolGood = colorama.Fore.YELLOW + "MISSING" + colorama.Fore.RESET print(f"{symbol},{symbolInfo.buildAddress:X},{symbolInfo.buildFile},{symbolInfo.expectedAddress:X},{symbolInfo.expectedFile},{symbolInfo.diff},{symbolGood}") continue if symbolInfo.diff == 0: symbolGood = colorama.Fore.GREEN + "GOOD" + colorama.Fore.RESET if (not symbolInfo.buildFile in badFiles and not symbolInfo.expectedFile in badFiles) and (not symbolInfo.buildFile in badFiles and not symbolInfo.expectedFile in badFiles) and not printAll: continue if symbolInfo.buildFile != symbolInfo.expectedFile: symbolGood += colorama.Fore.CYAN + " MOVED" + colorama.Fore.RESET print(f"{symbol},{symbolInfo.buildAddress:X},{symbolInfo.buildFile},{symbolInfo.expectedAddress:X},{symbolInfo.expectedFile},{symbolInfo.diff:X},{symbolGood}") def main(): description = "Check that globally visible bss has not been reordered." epilog = """\ N.B. Since this script reads the map files, it can only see globally visible bss; in-function static bss must be examined with other tools. """ parser = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("mapFile", help="Path to a map file.") parser.add_argument("mapFileExpected", help="Path to the expected map file. Optional, default is 'expected/mapFile'.", nargs="?", default="") parser.add_argument("-a", "--print-all", help="Print all bss, not just non-matching.", action="store_true") parser.add_argument("-n", "--no-fun-allowed", help="Remove amusing messages.", action="store_true") args = parser.parse_args() if args.mapFileExpected == "": args.mapFileExpected = os.path.join("expected", args.mapFile) badFiles, missingFiles, comparedDict = compareMapFiles(args.mapFile, args.mapFileExpected) printCsv(badFiles, missingFiles, comparedDict, args.print_all) if len(badFiles) + len(missingFiles) != 0: print("", file=os.sys.stderr) if len(badFiles) != 0: print(colorama.Fore.RED + " BAD" + colorama.Style.RESET_ALL) for file in badFiles: print(f"bss reordering in {file}", file=os.sys.stderr) print("", file=os.sys.stderr) if not args.no_fun_allowed: print(colorama.Fore.LIGHTWHITE_EX + " BSS is REORDERED!!\n" " Oh! MY GOD!!" + colorama.Style.RESET_ALL, file=os.sys.stderr) print("", file=os.sys.stderr) if len(missingFiles) != 0: print(colorama.Fore.YELLOW + " MISSING" + colorama.Style.RESET_ALL) for file in missingFiles: print(f"Symbols missing from {file}", file=os.sys.stderr) print("", file=os.sys.stderr) if not args.no_fun_allowed: print(colorama.Fore.LIGHTWHITE_EX + " Error, should (not) be in here " + colorama.Style.RESET_ALL, file=os.sys.stderr) print("", file=os.sys.stderr) print("Some files appear to be missing symbols. Have they been renamed or declared as static? You may need to remake 'expected'", file=os.sys.stderr) return 1 print("", file=os.sys.stderr) print(colorama.Fore.GREEN + " GOOD" + colorama.Style.RESET_ALL, file=os.sys.stderr) if args.no_fun_allowed: return 0 print("\n" + colorama.Fore.LIGHTWHITE_EX + colorama.Back.RED + " " + colorama.Back.RESET + "\n" + colorama.Back.RED + " CONGRATURATIONS! " + colorama.Back.RESET + "\n" + colorama.Back.RED + " All Global BSS is correct. " + colorama.Back.RESET + "\n" + colorama.Back.RED + " THANK YOU! " + colorama.Back.RESET + "\n" + colorama.Back.RED + " You are great decomper! " + colorama.Back.RESET + "\n" + colorama.Back.RED + " " + colorama.Style.RESET_ALL , file=os.sys.stderr) return 0 if __name__ == "__main__": ret = main() exit(ret)