Skip to content

Commit 22d23e7

Browse files
committed
Properlly handle MonoBehaviour inheritance in generated code
1 parent 8514d4b commit 22d23e7

File tree

1 file changed

+55
-24
lines changed

1 file changed

+55
-24
lines changed

UnityPyTypetreeCodegen/__main__.py

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@
4141
"bool": "bool",
4242
"string": "str",
4343
"TypelessData": "bytes",
44-
# -- Extra
45-
"Array": "List[object]",
44+
# -- Extra
4645
"Byte[]": "bytes",
4746
"Byte": "int",
4847
"String": "str",
@@ -53,7 +52,13 @@
5352
"Vector3": "Vector3f",
5453
"Vector4": "Vector4f",
5554
"Quaternion": "Quaternionf",
55+
"Object": "object",
56+
"Array": "list",
57+
"Type": "type",
58+
"MethodInfo": "object",
59+
"PropertyInfo": "object",
5660
}
61+
5762
# XXX: Can't use attrs here since subclassing MonoBehavior and such - though defined by the typetree dump
5863
# seem to be only valid if the class isn't a property of another class
5964
# In which case the MonoBehavior attributes are inherited by the parent class and does not
@@ -304,11 +309,11 @@ def emit_line(*lines: str):
304309
f.write("\n")
305310

306311
logger.info(
307-
f"Subpass 1: Generating class dependency graph for {namespace or '<default namespace>'}"
312+
f"Subpass 1: Generating class dependency graph for {namespace or "<default namespace>"}"
308313
)
309314
emit_line("# fmt: off")
310315
emit_line("# Auto-generated by https://github.com/mos9527/UnityPyTypetreeCodegen")
311-
emit_line(f"# Python definition for {namespace or '<default namespace>'}", "")
316+
emit_line(f"# Python definition for {namespace or "<default namespace>"}", "")
312317
if import_root:
313318
emit_line(f"from {import_root} import *")
314319
for clazz, parent in import_defs.items():
@@ -326,6 +331,7 @@ def emit_line(*lines: str):
326331
clazzes = list()
327332

328333
logger.info(f"Subpass 2: Generating code for {namespace}")
334+
dp = defaultdict(lambda: -1)
329335
for clazz in topo:
330336
fullname = f"{namespace}.{clazz}" if namespace else clazz
331337
fields = classname_nodes.get(clazz, None)
@@ -334,6 +340,8 @@ def emit_line(*lines: str):
334340
f"Class {clazz} has no fields defined in TypeTree dump, skipped"
335341
)
336342
continue
343+
lvl0 = list(filter(lambda field: field.m_Level == 0, fields))
344+
lvl1 = list(filter(lambda field: field.m_Level == 1, fields))
337345
clazz = translate_name(clazz)
338346
clazzes.append(clazz)
339347
clazz_fields = list()
@@ -345,20 +353,47 @@ def __encoder(obj):
345353

346354
clazz_typetree = json.dumps(fields, default=__encoder)
347355
emit_line(f"@UTTCGen('{fullname}', {clazz_typetree})")
348-
emit_line(f"class {clazz}:")
349-
for field in fields:
350-
if field.m_Type == clazz:
351-
continue
352-
if field.m_Level > 1:
353-
# Nested type. We should have already defined it
354-
# with type hints
355-
continue
356-
name, type = field.m_Name, translate_type(
357-
field.m_Type, typenames=classname_nodes | import_defs
358-
)
359-
emit_line(f"\t{declare_field(name, type, field.m_Type)}")
360-
clazz_fields.append((name, type))
361-
356+
# Heuristic: If there is a lvl1 and a lvl0 field, it's a subclass
357+
if lvl1 and lvl0:
358+
parent = translate_type(fields[0].m_Type, strip=True, fallback=False)
359+
emit_line(f"class {translate_name(clazz)}({translate_name(parent)}):")
360+
if dp[parent] == -1:
361+
# Reuse parent's fields with best possible effort
362+
if pa_dep1 := getattr(UnityBuiltin, parent, None):
363+
dp[parent] = len(pa_dep1.__annotations__)
364+
else:
365+
raise ValueError # XXX: Should NEVER happen
366+
pa_dep1 = dp[parent]
367+
cur_dep1 = pa_dep1
368+
for dep, (i, field) in enumerate(
369+
filter(lambda field: field[1].m_Level == 1, enumerate(fields))
370+
):
371+
if field.m_Level > 1:
372+
continue # Nested
373+
if dep < pa_dep1:
374+
# Skip parent fields at lvl1
375+
continue
376+
if i + 1 < len(fields) and fields[i + 1].m_Type == "Array":
377+
field.m_Type = fields[i + 3].m_Type + "[]"
378+
name, type = field.m_Name, translate_type(
379+
field.m_Type, typenames=classname_nodes | import_defs
380+
)
381+
emit_line(f"\t{declare_field(name, type, field.m_Type)}")
382+
clazz_fields.append((name, type, field.m_Type))
383+
cur_dep1 += 1
384+
dp[clazz] = cur_dep1
385+
else:
386+
# No inheritance
387+
emit_line(f"class {clazz}:")
388+
for field in fields:
389+
if field.m_Level > 1:
390+
continue # Nested
391+
name, type = field.m_Name, translate_type(
392+
field.m_Type, typenames=classname_nodes | import_defs
393+
)
394+
emit_line(f"\t{declare_field(name, type, field.m_Type)}")
395+
clazz_fields.append((name, type))
396+
dp[clazz] = len(fields)
362397
if not clazz_fields:
363398
# Empty class. Consider MRO
364399
emit_line("\tpass")
@@ -437,11 +472,6 @@ def __main__():
437472
help="Unity version to use for typetree generation",
438473
default="2022.3.21f1",
439474
)
440-
parser.add_argument(
441-
"--backend",
442-
help="Backend to use for code generation",
443-
default="AssetStudio",
444-
)
445475
parser.add_argument(
446476
"--filter",
447477
help="Filter classnames by regex",
@@ -481,13 +511,14 @@ def __main__():
481511
shutil.rmtree(args.outdir, ignore_errors=True)
482512
os.makedirs(args.outdir, exist_ok=True)
483513
typetree = dict()
484-
gen = TypeTreeGenerator(args.unity_version, args.backend)
514+
gen = TypeTreeGenerator(args.unity_version, "AssetStudio")
485515
def populate_gen():
486516
# https://github.com/UnityPy-Org/TypeTreeGeneratorAPI/pull/1
487517
for asm,clz in gen.get_class_definitions():
488518
try:
489519
node = gen.get_nodes_as_json(asm, clz)
490520
node = json.loads(node)
521+
# https://github.com/UnityPy-Org/TypeTreeGeneratorAPI/pull/4
491522
typetree[clz] = node
492523
except Exception as e:
493524
logger.warning(f"Skipping nodes for {asm}.{clz}: {e}")

0 commit comments

Comments
 (0)