summaryrefslogtreecommitdiff
path: root/tools/progress.py
blob: 9e7ca087373357fe8d0c5f2dad2c47bd841662bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env python3
import argparse
from collections import defaultdict
from colorama import Back, Fore, Style
import enum
from pathlib import Path
from util import utils
from util.utils import FunctionStatus
import typing as tp

parser = argparse.ArgumentParser()
parser.add_argument("--csv", "-c", action="store_true",
                    help="Print a CSV line rather than a human-readable progress report")
parser.add_argument("--print-nm", "-n", action="store_true",
                    help="Print non-matching functions with major issues")
parser.add_argument("--print-eq", "-e", action="store_true",
                    help="Print non-matching functions with minor issues")
parser.add_argument("--print-ok", "-m", action="store_true",
                    help="Print matching functions")
parser.add_argument("--hide-nonmatchings-with-dumps", "-H", help="Hide non-matching functions that have expected "
                                                                 "output dumps", action="store_true")
args = parser.parse_args()


class AIClassType(enum.Enum):
    Action = 0
    AI = 1
    Behavior = 2
    Query = 3


code_size_total = 0
num_total = 0
code_size: tp.DefaultDict[FunctionStatus, int] = defaultdict(int)
counts: tp.DefaultDict[FunctionStatus, int] = defaultdict(int)
ai_counts: tp.DefaultDict[AIClassType, int] = defaultdict(int)
ai_counts_done: tp.DefaultDict[AIClassType, int] = defaultdict(int)

nonmatching_fns_with_dump = {p.stem for p in (Path(__file__).parent.parent / "expected").glob("*.bin")}


def should_hide_nonmatching(name: str) -> bool:
    if not args.hide_nonmatchings_with_dumps:
        return False
    return name in nonmatching_fns_with_dump


for info in utils.get_functions():
    code_size_total += info.size
    num_total += 1

    ai_class_type: tp.Optional[AIClassType] = None
    if info.name.startswith("AI_F_Action_"):
        ai_class_type = AIClassType.Action
    elif info.name.startswith("AI_F_AI_"):
        ai_class_type = AIClassType.AI
    elif info.name.startswith("AI_F_Behavior_"):
        ai_class_type = AIClassType.Behavior
    elif info.name.startswith("AI_F_Query_"):
        ai_class_type = AIClassType.Query

    if ai_class_type is not None:
        ai_counts[ai_class_type] += 1
        if info.decomp_name:
            ai_counts_done[ai_class_type] += 1

    if not info.decomp_name:
        continue

    counts[info.status] += 1
    code_size[info.status] += info.size

    if not args.csv:
        if info.status == FunctionStatus.NonMatching:
            if args.print_nm and not should_hide_nonmatching(info.decomp_name):
                print(f"{Fore.RED}NM{Fore.RESET} {utils.format_symbol_name(info.decomp_name)}")
        elif info.status == FunctionStatus.Equivalent:
            if args.print_eq and not should_hide_nonmatching(info.decomp_name):
                print(f"{Fore.YELLOW}EQ{Fore.RESET} {utils.format_symbol_name(info.decomp_name)}")
        elif info.status == FunctionStatus.Matching:
            if args.print_ok:
                print(f"{Fore.GREEN}OK{Fore.RESET} {utils.format_symbol_name(info.decomp_name)}")
        elif info.status == FunctionStatus.Wip:
            print(
                f"{Back.RED}{Style.BRIGHT}{Fore.WHITE} WIP {Style.RESET_ALL} {utils.format_symbol_name(info.decomp_name)}{Style.RESET_ALL}")


def format_progress(label: str, num: int, size: int):
    percentage = round(100 * num / num_total, 3)
    size_percentage = round(100 * size / code_size_total, 3)
    return f"{num:>7d} {label}{Fore.RESET} ({percentage}% | size: {size_percentage}%)"


def format_progress_for_status(label: str, status: FunctionStatus):
    return format_progress(label, counts[status], code_size[status])


def format_ai_progress(label: str, class_type: AIClassType):
    percentage = round(100 * ai_counts_done[class_type] / ai_counts[class_type], 3)
    return f"{ai_counts_done[class_type]:>7d} {label}{Fore.RESET} ({percentage}%)"


if args.csv:
    import git

    version = 1
    git_object = git.Repo().head.object
    timestamp = str(git_object.committed_date)
    git_hash = git_object.hexsha

    fields = [
        str(version),
        timestamp,
        git_hash,

        str(num_total),
        str(code_size_total),

        str(counts[FunctionStatus.Matching]),
        str(code_size[FunctionStatus.Matching]),

        str(counts[FunctionStatus.Equivalent]),
        str(code_size[FunctionStatus.Equivalent]),

        str(counts[FunctionStatus.NonMatching]),
        str(code_size[FunctionStatus.NonMatching]),
    ]
    print(",".join(fields))

else:
    print()

    print(f"{num_total:>7d} functions (size: ~{code_size_total} bytes)")

    count_decompiled = counts[FunctionStatus.Matching] + counts[FunctionStatus.Equivalent] + counts[
        FunctionStatus.NonMatching]
    code_size_decompiled = code_size[FunctionStatus.Matching] + code_size[FunctionStatus.Equivalent] + code_size[
        FunctionStatus.NonMatching]

    print(format_progress(f"{Fore.CYAN}decompiled", count_decompiled, code_size_decompiled))
    print(format_progress_for_status(f"{Fore.GREEN}matching", FunctionStatus.Matching))
    print(format_progress_for_status(f"{Fore.YELLOW}non-matching (minor issues)", FunctionStatus.Equivalent))
    print(format_progress_for_status(f"{Fore.RED}non-matching (major issues)", FunctionStatus.NonMatching))

    print()
    print(format_ai_progress("actions", AIClassType.Action))
    print(format_ai_progress("AIs", AIClassType.AI))
    print(format_ai_progress("behaviors", AIClassType.Behavior))
    print(format_ai_progress("queries", AIClassType.Query))