mirror of https://github.com/zeldaret/tmc.git
				
				
				
			
		
			
				
	
	
		
			154 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			154 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
#!/usr/bin/env python3
 | 
						|
 | 
						|
import argparse
 | 
						|
import json
 | 
						|
import os
 | 
						|
import re
 | 
						|
 | 
						|
def collect_non_matching_funcs():
 | 
						|
    result = []
 | 
						|
    for root, dirs, files in os.walk('src'):
 | 
						|
        for file in files:
 | 
						|
            if file.endswith('.c'):
 | 
						|
                with open(os.path.join(root, file), 'r') as f:
 | 
						|
                    data = f.read()
 | 
						|
                    # Find all NONMATCH and ASM_FUNC macros
 | 
						|
                    for match in re.findall(r'(NONMATCH|ASM_FUNC)\(".*",\W*\w*\W*(\w*).*\)', data):
 | 
						|
                        result.append(match)
 | 
						|
    return result
 | 
						|
 | 
						|
 | 
						|
def parse_map(non_matching_funcs):
 | 
						|
    src = 0
 | 
						|
    asm = 0
 | 
						|
    src_data = 0
 | 
						|
    data = 0
 | 
						|
    non_matching = 0
 | 
						|
 | 
						|
    with open('build/USA/tmc.map', 'r') as map:
 | 
						|
        # Skip to the linker script section
 | 
						|
        line = map.readline()
 | 
						|
        while not line.startswith('Linker script and memory map'):
 | 
						|
            line = map.readline()
 | 
						|
        while not line.startswith('rom'):
 | 
						|
            line = map.readline()
 | 
						|
 | 
						|
        prev_symbol = None
 | 
						|
        prev_addr = 0
 | 
						|
        for line in map:
 | 
						|
            if line.startswith(' .'):
 | 
						|
                arr = line.split()
 | 
						|
                section = arr[0]
 | 
						|
                size = int(arr[2], 16)
 | 
						|
                filepath = arr[3]
 | 
						|
                dir = filepath.split('/')[0]
 | 
						|
 | 
						|
                if section == '.text':
 | 
						|
                    if dir == 'src':
 | 
						|
                        src += size
 | 
						|
                    elif dir == 'asm':
 | 
						|
                        if filepath.find("asm/src/") != -1 or filepath.find("asm/lib/") != -1:
 | 
						|
                            src += size
 | 
						|
                        else:
 | 
						|
                            asm += size
 | 
						|
                    elif dir == 'data':
 | 
						|
                        # scripts
 | 
						|
                        src_data += size
 | 
						|
                    elif dir == '..':
 | 
						|
                        # libc
 | 
						|
                        src += size
 | 
						|
                elif section == '.rodata':
 | 
						|
                    if dir == 'src':
 | 
						|
                        src_data += size
 | 
						|
                    elif dir == 'data':
 | 
						|
                        data += size
 | 
						|
 | 
						|
            elif line.startswith('  '):
 | 
						|
                arr = line.split()
 | 
						|
                if len(arr) == 2 and arr[1] != '':  # It is actually a symbol
 | 
						|
 | 
						|
                    if prev_symbol in non_matching_funcs:
 | 
						|
                        # Calculate the length for non matching function
 | 
						|
                        non_matching += int(arr[0], 16) - prev_addr
 | 
						|
 | 
						|
                    prev_symbol = arr[1]
 | 
						|
                    prev_addr = int(arr[0], 16)
 | 
						|
            elif line.strip() == '':
 | 
						|
                # End of linker script section
 | 
						|
                break
 | 
						|
 | 
						|
    src -= non_matching
 | 
						|
    asm += non_matching
 | 
						|
 | 
						|
    return (src, asm, src_data, data)
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    parser = argparse.ArgumentParser()
 | 
						|
 | 
						|
    parser = argparse.ArgumentParser(description="Computes current progress throughout the whole project.")
 | 
						|
    parser.add_argument("format", nargs="?", default="text", choices=["text", "csv", "shield-json"])
 | 
						|
    parser.add_argument("-m", "--matching", dest='matching', action='store_true',
 | 
						|
                        help="Output matching progress instead of decompilation progress")
 | 
						|
    args = parser.parse_args()
 | 
						|
 | 
						|
    matching = args.matching
 | 
						|
 | 
						|
    non_matching_funcs = []
 | 
						|
    funcs = collect_non_matching_funcs()
 | 
						|
    if matching:
 | 
						|
        # Remove all non matching funcs from count
 | 
						|
        non_matching_funcs = [x[1] for x in funcs]
 | 
						|
    else:
 | 
						|
        # Only remove ASM_FUNC functions from count
 | 
						|
        for func in funcs:
 | 
						|
            if func[0] == 'ASM_FUNC':
 | 
						|
                non_matching_funcs.append(func[1])
 | 
						|
 | 
						|
    (src, asm, src_data, data) = parse_map(non_matching_funcs)
 | 
						|
 | 
						|
    total = src + asm
 | 
						|
    data_total = src_data + data
 | 
						|
 | 
						|
    src_percent = 100 * src / total
 | 
						|
    asm_percent = 100 * asm / total
 | 
						|
 | 
						|
    src_data_percent = 100 * src_data / data_total
 | 
						|
    data_percent     = 100 * data / data_total
 | 
						|
 | 
						|
 | 
						|
    if args.format == 'csv':
 | 
						|
        import git
 | 
						|
        version = 2
 | 
						|
        git_object = git.Repo().head.object
 | 
						|
        timestamp = str(git_object.committed_date)
 | 
						|
        git_hash = git_object.hexsha
 | 
						|
 | 
						|
        csv_list = [str(version), timestamp, git_hash, str(src),
 | 
						|
                    str(total), str(src_data), str(data_total)]
 | 
						|
 | 
						|
        print(','.join(csv_list))
 | 
						|
 | 
						|
    elif args.format == 'shield-json':
 | 
						|
        # https://shields.io/endpoint
 | 
						|
        print(json.dumps({
 | 
						|
            "schemaVersion": 1,
 | 
						|
            "label": "progress",
 | 
						|
            "message": f"{src_percent:.3g}%",
 | 
						|
            "color": 'yellow',
 | 
						|
        }))
 | 
						|
 | 
						|
    elif args.format == 'text':
 | 
						|
        adjective = "decompiled" if not args.matching else "matched"
 | 
						|
 | 
						|
        print("src:  {:>9} / {:>8} total bytes {:<10} {:>9.4f}%".format(src, total, adjective, round(src_percent, 4)))
 | 
						|
        # print()
 | 
						|
        print("data: {:>9} / {:>8} total bytes analysed   {:>9.4f}%".format(src_data, data_total, round(src_data_percent, 4)))
 | 
						|
 | 
						|
    else:
 | 
						|
        print("Unknown format argument: " + args.format)
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    main()
 |