Skip to content

Commit 84ee178

Browse files
committed
tools: add FP_ANGLE support to flags_fp_conv, FP rounding improvement
There's still a minor issue with certain floats, where python outputs as something like 34.0000000001 instead of just 34 Not sure of a good way to fix it yet, easy enough to just remove manually
1 parent 91fbb17 commit 84ee178

2 files changed

Lines changed: 89 additions & 39 deletions

File tree

tools/bin2c.py

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,25 @@
3232
def round_fp(val, scale):
3333
float_val = val / scale;
3434

35-
# Try to find the shortest rounded representation that still matches
36-
for digits in range(1, 12): # up to 12 decimal places
35+
# Find shortest decimal that round-trips correctly
36+
found = False
37+
38+
for digits in range(1, 12):
3739
rounded = round(float_val, digits)
38-
if float_val < 0:
39-
reconverted = int(math.ceil(rounded * scale))
40-
else:
41-
reconverted = int(math.floor(rounded * scale))
42-
if reconverted == val:
43-
# Good enough – this rounded value still maps to the same fixed integer
44-
float_val = rounded
40+
increment = 10 ** (-digits)
41+
42+
# Test the rounded value and its nearest neighbor
43+
candidates = [rounded, rounded + (increment if float_val >= 0 else -increment)]
44+
45+
for candidate in candidates:
46+
scaled = candidate * scale
47+
reconverted = int(math.floor(scaled)) if candidate >= 0 else int(math.ceil(scaled))
48+
if reconverted == val:
49+
float_val = candidate
50+
found = True
51+
break
52+
53+
if found:
4554
break
4655

4756
return f"{float_val}f"
@@ -53,7 +62,10 @@ def process_fp(val, qval = 12):
5362
return f"Q{qval}({float_val})"
5463

5564
def process_fp_angle_packed(val):
56-
return round_fp(val, 0.711111);
65+
return f"FP_ANGLE_PACKED({round_fp(val, 0.711111)})"
66+
67+
def process_fp_angle(val):
68+
return f"FP_ANGLE({round_fp(val, 11.377778)})"
5769

5870
class StructParser:
5971
def __init__(self, alignment=4, verbose=False, union_choices=None):
@@ -589,20 +601,9 @@ def parse_sym_addrs(self, content):
589601

590602
def parse_definitions(self, c_code):
591603
"""Parse C code and build construct definitions."""
592-
fake_includes = """
593-
typedef unsigned char u8;
594-
typedef unsigned short u16;
595-
typedef unsigned int u32;
596-
typedef unsigned long long u64;
597-
typedef signed char s8;
598-
typedef signed short s16;
599-
typedef signed int s32;
600-
typedef signed long long s64;
601-
"""
602604

603605
cleaned_code = self.preprocess_code(c_code)
604-
full_code = fake_includes + cleaned_code
605-
ast = self.parser.parse(full_code)
606+
ast = self.parser.parse(cleaned_code)
606607

607608
for node in ast.ext:
608609
# Handle Typedefs
@@ -1070,7 +1071,7 @@ def format_value(self, value, field_type, field_key):
10701071
elif value == -1 and (field_key == "s_AnimInfo.startKeyframeIdx_C" or field_key == "s_AnimInfo.endKeyframeIdx_E"):
10711072
return "NO_VALUE"
10721073
elif field_key == "VC_ROAD_DATA.fix_ang_x_16" or field_key == "VC_ROAD_DATA.fix_ang_y_17":
1073-
return f"FP_ANGLE_PACKED({process_fp_angle_packed(value & 0xFF)})"
1074+
return process_fp_angle_packed(value & 0xFF)
10741075
elif field_key == "VC_ROAD_DATA.mv_y_type_11":
10751076
enum_val = self.lookup_enum_name("VC_CAM_MV_TYPE", value)
10761077
if enum_val is not None:

tools/flags_fp_conv.py

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import readline
1111
import math
1212
import os
13+
import sys
1314
import pyperclip
1415

1516
# ---------- EventFlags converter ----------
@@ -161,6 +162,44 @@ def to_signed(val, bits=32):
161162
val -= 1 << bits
162163
return val
163164

165+
def round_fp(val, scale):
166+
float_val = val / scale;
167+
168+
# Find shortest decimal that round-trips correctly
169+
found = False
170+
171+
for digits in range(1, 12):
172+
rounded = round(float_val, digits)
173+
increment = 10 ** (-digits)
174+
175+
# Test the rounded value and its nearest neighbor
176+
candidates = [rounded, rounded + (increment if float_val >= 0 else -increment)]
177+
178+
for candidate in candidates:
179+
scaled = candidate * scale
180+
reconverted = int(math.floor(scaled)) if candidate >= 0 else int(math.ceil(scaled))
181+
if reconverted == val:
182+
float_val = candidate
183+
found = True
184+
break
185+
186+
if found:
187+
break
188+
189+
return f"{float_val}f"
190+
191+
def process_fp(val, qval = 12):
192+
scale = (1 << qval)
193+
float_val = round_fp(val, scale)
194+
195+
return f"Q{qval}({float_val})"
196+
197+
def process_fp_angle_packed(val):
198+
return f"FP_ANGLE_PACKED({round_fp(val, 0.711111)})"
199+
200+
def process_fp_angle(val):
201+
return f"FP_ANGLE({round_fp(val, 11.377778)})"
202+
164203
def process_fp_text(text):
165204
qval = 12 # default Q-format is Q12
166205

@@ -170,7 +209,14 @@ def process_fp_text(text):
170209
qval = int(match.group(1))
171210
text = re.sub(r'\bQ\d+\b', '', text, count=1)
172211

173-
scale = (1 << qval)
212+
is_fp_angle_packed = False
213+
is_fp_angle = False
214+
if "FP_ANGLE_PACKED" in text:
215+
is_fp_angle_packed = True
216+
text = text.replace("FP_ANGLE_PACKED", "")
217+
elif "FP_ANGLE" in text:
218+
is_fp_angle = True
219+
text = text.replace("FP_ANGLE", "")
174220

175221
def convert_value(match):
176222
prefix = match.group(1) or "" # may be None if start-of-string
@@ -187,28 +233,31 @@ def convert_value(match):
187233
else:
188234
val = int(value_str, 10)
189235

190-
float_val = val / scale;
191-
192-
# Try to find the shortest rounded representation that still matches
193-
for digits in range(1, 12): # up to 12 decimal places
194-
rounded = round(float_val, digits)
195-
if float_val < 0:
196-
reconverted = int(math.ceil(rounded * scale))
197-
else:
198-
reconverted = int(math.floor(rounded * scale))
199-
if reconverted == val:
200-
# Good enough — this rounded value still maps to the same fixed integer
201-
float_val = rounded
202-
break
203-
204-
return f"{prefix}Q{qval}({float_val}f)"
236+
if is_fp_angle:
237+
return f"{prefix}{process_fp_angle(val)}"
238+
if is_fp_angle_packed:
239+
return f"{prefix}{process_fp_angle_packed(val)}"
240+
return f"{prefix}{process_fp(val, qval)}"
205241

206242
# prefix can be: start-of-string, space, or a punctuation
207243
pattern = re.compile(r'(^|[\s,(=:{\[])([-+]?0x[0-9A-Fa-f]+|[-+]?\d+)')
208244
return pattern.sub(convert_value, text)
209245

210246
# ---------- Unified REPL ----------
211247
if __name__ == "__main__":
248+
if len(sys.argv) > 1:
249+
# Join all arguments into a single input string
250+
line = " ".join(sys.argv[1:]).strip()
251+
252+
result = convert_flag_expression(line)
253+
if result is None:
254+
result = process_fp_text(line)
255+
256+
if result is not None:
257+
print(result)
258+
pyperclip.copy(result)
259+
sys.exit(0)
260+
212261
print("Enter expressions like 'g_SavegamePtr->eventFlags_168[2] & 8' or 'g_SavegamePtr->eventFlags_168[2] |= (1 << 3))'.")
213262
print("Or enter a line containing decimal/hexadecimal Q12 numbers (0x7CCC, -0x1444, 4096, 0xFFFE0DDD) to convert to float")
214263
print("(change to other Q** formats by including Q format in the text, 'chara.field_48 = Q8(-0x1999)'")

0 commit comments

Comments
 (0)