Update README.md
[sm64pc.git] / tools / mario_anims_converter.py
blob361662aec7967fd88ad8bc791ddb313284c263d2
1 #!/usr/bin/env python3
2 import re
3 import os
4 import traceback
5 import sys
7 num_headers = 0
8 items = []
9 len_mapping = {}
10 order_mapping = {}
11 line_number_mapping = {}
13 def raise_error(filename, lineindex, msg):
14 raise SyntaxError("Error in " + filename + ":" + str(line_number_mapping[lineindex] + 1) + ": " + msg)
16 def parse_struct(filename, lines, lineindex, name):
17 global items, order_mapping
18 lineindex += 1
19 if lineindex + 9 >= len(lines):
20 raise_error(filename, lineindex, "struct Animation must be 11 lines")
21 v1 = int(lines[lineindex + 0].rstrip(","), 0)
22 v2 = int(lines[lineindex + 1].rstrip(","), 0)
23 v3 = int(lines[lineindex + 2].rstrip(","), 0)
24 v4 = int(lines[lineindex + 3].rstrip(","), 0)
25 v5 = int(lines[lineindex + 4].rstrip(","), 0)
26 values = lines[lineindex + 6].rstrip(",")
27 indices = lines[lineindex + 7].rstrip(",")
28 items.append(("header", name, (v1, v2, v3, v4, v5, values, indices)))
29 if lines[lineindex + 9] != "};":
30 raise_error(filename, lineindex + 9, "Expected \"};\" but got " + lines[lineindex + 9])
31 order_mapping[name] = len(items)
32 lineindex += 10
33 return lineindex
35 def parse_array(filename, lines, lineindex, name, is_indices):
36 global items, len_mapping, order_mapping
37 lineindex += 1
38 values = []
39 while lineindex < len(lines) and lines[lineindex] != "};":
40 line = lines[lineindex].rstrip(",")
41 if line:
42 values.extend(line.split(","))
43 lineindex += 1
44 if lineindex >= len(lines):
45 raise_error(filename, lineindex, "Expected \"};\" but reached end of file")
46 items.append(("array", name, (is_indices, values)))
47 len_mapping[name] = len(values)
48 order_mapping[name] = len(items)
49 lineindex += 1
50 return lineindex
52 def parse_file(filename, lines):
53 global num_headers
54 lineindex = 0
55 while lineindex < len(lines):
56 line = lines[lineindex]
57 for prefix in ["static ", "const "]:
58 if line.startswith(prefix):
59 line = line[len(prefix):]
60 lines[lineindex] = line
62 is_struct = line.startswith("struct Animation ") and line.endswith("[] = {")
63 is_indices = line.startswith("u16 ") and line.endswith("[] = {")
64 is_values = line.startswith("s16 ") and line.endswith("[] = {")
65 if not is_struct and not is_indices and not is_values:
66 raise_error(filename, lineindex, "\"" + line + "\" does not follow the pattern \"static const struct Animation anim_x[] = {\", \"static const u16 anim_x_indices[] = {\" or \"static const s16 anim_x_values[] = {\"")
68 if is_struct:
69 name = lines[lineindex][len("struct Animation "):-6]
70 lineindex = parse_struct(filename, lines, lineindex, name)
71 num_headers += 1
72 else:
73 name = lines[lineindex][len("s16 "):-6]
74 lineindex = parse_array(filename, lines, lineindex, name, is_indices)
76 try:
77 files = os.listdir("assets/anims")
78 files.sort()
80 for filename in files:
81 if filename.endswith(".inc.c"):
82 lines = []
83 with open("assets/anims/" + filename) as f:
84 for i, line in enumerate(f):
85 line = re.sub(r"/\*.*?\*/", "", line)
86 if "/*" in line:
87 line_number_mapping[-1] = i
88 raise_error(filename, -1, "Multiline comments are not supported")
89 line = line.split("//", 1)[0].strip()
90 if line:
91 line_number_mapping[len(lines)] = i
92 lines.append(line)
93 if lines:
94 parse_file(filename, lines)
96 structdef = ["u32 numEntries;", "const struct Animation *addrPlaceholder;", "struct OffsetSizePair entries[" + str(num_headers) + "];"]
97 structobj = [str(num_headers) + ",", "NULL,","{"]
99 for item in items:
100 type, name, obj = item
101 if type == "header":
102 v1, v2, v3, v4, v5, values, indices = obj
103 if order_mapping[indices] < order_mapping[name]:
104 raise SyntaxError("Error: Animation struct must be written before indices array for " + name)
105 if order_mapping[values] < order_mapping[indices]:
106 raise SyntaxError("Error: values array must be written after indices array for " + name)
107 values_num_values = len_mapping[values]
108 offset_to_struct = "offsetof(struct MarioAnimsObj, " + name + ")"
109 offset_to_end = "offsetof(struct MarioAnimsObj, " + values + ") + sizeof(gMarioAnims." + values + ")"
110 structobj.append("{" + offset_to_struct + ", " + offset_to_end + " - " + offset_to_struct + "},")
111 structobj.append("},")
113 for item in items:
114 type, name, obj = item
115 if type == "header":
116 v1, v2, v3, v4, v5, values, indices = obj
117 indices_len = len_mapping[indices] // 6 - 1
118 values_num_values = len_mapping[values]
119 offset_to_struct = "offsetof(struct MarioAnimsObj, " + name + ")"
120 offset_to_end = "offsetof(struct MarioAnimsObj, " + values + ") + sizeof(gMarioAnims." + values + ")"
121 structdef.append("struct Animation " + name + ";")
122 structobj.append("{" + ", ".join([
123 str(v1),
124 str(v2),
125 str(v3),
126 str(v4),
127 str(v5),
128 str(indices_len),
129 "(const s16 *)(offsetof(struct MarioAnimsObj, " + values + ") - " + offset_to_struct + ")",
130 "(const u16 *)(offsetof(struct MarioAnimsObj, " + indices + ") - " + offset_to_struct + ")",
131 offset_to_end + " - " + offset_to_struct
132 ]) + "},")
133 else:
134 is_indices, arr = obj
135 type = "u16" if is_indices else "s16"
136 structdef.append("{} {}[{}];".format(type, name, len(arr)))
137 structobj.append("{" + ",".join(arr) + "},")
139 print("#include \"types.h\"")
140 print("#include <stddef.h>")
141 print("")
143 print("const struct MarioAnimsObj {")
144 for s in structdef:
145 print(s)
146 print("} gMarioAnims = {")
147 for s in structobj:
148 print(s)
149 print("};")
151 except Exception as e:
152 note = "NOTE! The mario animation C files are not processed by a normal C compiler, but by the script in tools/mario_anims_converter.py. The format is much more strict than normal C, so please follow the syntax of existing files.\n"
153 if e is SyntaxError:
154 e.msg = note + e.msg
155 else:
156 print(note, file=sys.stderr)
157 traceback.print_exc()
158 sys.exit(1)