Skip to content

Commit 7b248e0

Browse files
committed
tools: add flags_fp_conv.py, repl-style tool to convert event flags/Q12
Came in useful for large funcs that made use of lots of flags/Q12 numbers, figured others might find it useful too
1 parent 4de15d8 commit 7b248e0

1 file changed

Lines changed: 97 additions & 0 deletions

File tree

tools/flags_fp_conv.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env python3
2+
3+
# Converts `g_SavegamePtr->eventFlags_168[X] & Y` expressions to `Savegame_EventFlagGet(Z)`
4+
# Also converts Q12 integers (0x7CCC, -0x1444, 0xFFFE0DDD, 4096) into float equivalents (may need to be rounded afterward)
5+
6+
import re
7+
8+
# ---------- EventFlags converter ----------
9+
def bit_index(mask):
10+
"""Convert a mask like 8 (0b1000) to its bit index (3)."""
11+
if mask == 0:
12+
raise ValueError("Mask cannot be zero")
13+
index = 0
14+
while mask > 1:
15+
mask >>= 1
16+
index += 1
17+
return index
18+
19+
def convert_flag_expression(expr):
20+
"""
21+
Converts:
22+
g_SavegamePtr->eventFlags_168[2] & 8
23+
g_SavegamePtr->eventFlags_168[2] & 0x8
24+
g_SavegamePtr->eventFlags_168[2] & (1 << 3)
25+
temp_a0->eventFlags_168[2] & [...]
26+
into Savegame_EventFlagGet(final_index)
27+
"""
28+
# Pattern for decimal or hex mask
29+
pattern_literal = r'.*?eventFlags_168\[(\d+)\]\s*&\s*(0x[0-9a-fA-F]+|\d+)'
30+
# Pattern for bit shift
31+
pattern_shift = r'.*?eventFlags_168\[(\d+)\]\s*&\s*\(\s*1\s*<<\s*(\d+)\s*\)'
32+
33+
match_shift = re.search(pattern_shift, expr)
34+
if match_shift:
35+
array_idx = int(match_shift.group(1))
36+
bit_idx = int(match_shift.group(2))
37+
else:
38+
match_literal = re.search(pattern_literal, expr)
39+
if not match_literal:
40+
return None # not an eventFlags expression
41+
array_idx = int(match_literal.group(1))
42+
mask_str = match_literal.group(2)
43+
mask = int(mask_str, 0) # handles decimal and hex
44+
bit_idx = bit_index(mask)
45+
46+
final_idx = array_idx * 32 + bit_idx
47+
return f"Savegame_EventFlagGet({final_idx})"
48+
49+
# ---------- FP converter ----------
50+
def to_signed(val, bits=32):
51+
"""Convert unsigned int to signed (two's complement)."""
52+
if val & (1 << (bits - 1)):
53+
val -= 1 << bits
54+
return val
55+
56+
def convert_value(match):
57+
value_str = match.group(0)
58+
59+
if value_str.startswith(("0x", "-0x", "+0x")):
60+
val = int(value_str, 16)
61+
if value_str.startswith("-0x"):
62+
val = -int(value_str[3:], 16)
63+
elif value_str.startswith("+0x"):
64+
val = int(value_str[3:], 16)
65+
else:
66+
val = to_signed(val, 32)
67+
else:
68+
val = int(value_str, 10)
69+
70+
return str(val / 4096.0)
71+
72+
def process_fp_text(text):
73+
pattern = re.compile(r'[-+]?0x[0-9A-Fa-f]+|[-+]?\d+')
74+
return pattern.sub(convert_value, text)
75+
76+
# ---------- Unified REPL ----------
77+
if __name__ == "__main__":
78+
print("Enter expressions like '(g_SavegamePtr->eventFlags_168[2] & 8)' or '(g_SavegamePtr->eventFlags_168[2] & (1 << 3))'.")
79+
print("Or enter decimal/hexadecimal Q12 numbers (0x7CCC, -0x1444, 0xFFFE0DDD) to convert to float")
80+
print("Type 'exit' to quit.")
81+
while True:
82+
try:
83+
line = input("> ").strip()
84+
except EOFError:
85+
break
86+
if line.lower() in ("quit", "exit"):
87+
break
88+
if not line:
89+
continue
90+
91+
# Try eventFlags conversion first
92+
result = convert_flag_expression(line)
93+
if result is not None:
94+
print(result)
95+
else:
96+
# fallback to FP converter
97+
print(process_fp_text(line))

0 commit comments

Comments
 (0)