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" ,
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 ("\t pass" )
@@ -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