Skip to content

Commit aa0aef9

Browse files
committed
Add helper functions UTTCGen_AsInstance and LUT for generated classes
1 parent 7e35431 commit aa0aef9

File tree

1 file changed

+107
-59
lines changed

1 file changed

+107
-59
lines changed

UnityPyTypetreeCodegen/__main__.py

Lines changed: 107 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"Vector2": "Vector2f",
5252
"Vector3": "Vector3f",
5353
"Vector4": "Vector4f",
54+
"Quaternion": "Quaternionf",
5455
}
5556
# XXX: Can't use attrs here since subclassing MonoBehavior and such - though defined by the typetree dump
5657
# seem to be only valid if the class isn't a property of another class
@@ -62,70 +63,116 @@
6263
"# fmt: off",
6364
"# Auto-generated by https://github.com/mos9527/UnityPyTypetreeCodegen",
6465
"" "from typing import List, Union, Optional, TypeVar",
66+
"from UnityPy.files.ObjectReader import ObjectReader",
6567
"from UnityPy.classes import *",
6668
"from UnityPy.classes.math import (ColorRGBA, Matrix3x4f, Matrix4x4f, Quaternionf, Vector2f, Vector3f, Vector4f, float3, float4,)",
6769
'''T = TypeVar("T")
68-
def UTTCGen(clazz : T) -> T:
69-
"""dataclass-like decorator for typetree classess with nested type support
70-
71-
limitations:
72-
- the behavior is similar to slotted dataclasses where shared attributes are inherited
73-
but allows ommiting init of the parent if kwargs are not sufficient
74-
- generally supports nested types, however untested and could be slow
75-
- and ofc, zero type checking and safeguards :/
76-
"""
77-
RESERVED_KWS = {'object_reader'}
78-
# Allow these to be propogated to the props
79-
def __init__(self, **d):
80-
def enusre_reserved(obj):
81-
for k in RESERVED_KWS:
82-
if hasattr(obj, k) and k in d:
83-
setattr(obj, k, d[k])
84-
return obj
85-
def reduce_init(clazz, **d):
86-
types : dict = clazz.__annotations__
87-
for k, sub in types.items():
88-
if type(sub) == str:
89-
sub = eval(sub) # attrs turns these into strings...why?
90-
reduce_arg = getattr(sub, "__args__", [None])[0]
91-
if isinstance(d[k], list):
92-
if hasattr(reduce_arg, "__annotations__"):
93-
setattr(self, k, [enusre_reserved(reduce_arg(**x)) for x in d[k]])
94-
else:
95-
setattr(self, k, [enusre_reserved(reduce_arg(x)) for x in d[k]])
96-
elif isinstance(d[k], dict) and hasattr(sub, "__annotations__"):
97-
setattr(self, k, enusre_reserved(sub(**d[k])))
98-
else:
99-
if isinstance(d[k], dict):
100-
setattr(self, k, enusre_reserved(sub(**d[k])))
101-
else:
102-
setattr(self, k, enusre_reserved(sub(d[k])))
103-
for __base__ in clazz.__bases__:
104-
types : dict = __base__.__annotations__
105-
args = {k:d[k] for k in types if k in d}
106-
if len(args) == len(types):
107-
super(clazz, self).__init__(**args)
108-
reduce_init(__base__, **d)
109-
for k in args:
110-
if not k in RESERVED_KWS: del d[k]
111-
reduce_init(clazz, **d)
112-
enusre_reserved(self)
113-
def __repr__(self) -> str:
114-
return f"{clazz.__name__}({', '.join([f'{k}={getattr(self, k)!r}' for k in self.__annotations__])})"
115-
clazz.__init__ = __init__
116-
clazz.__repr__ = __repr__
117-
return clazz
70+
UTTCG_Classes = dict()
71+
def UTTCGen(fullname: str):
72+
"""dataclass-like decorator for typetree classess with nested type support
11873
74+
limitations:
75+
- the behavior is similar to slotted dataclasses where shared attributes are inherited
76+
but allows ommiting init of the parent if kwargs are not sufficient
77+
- generally supports nested types, however untested and could be slow
78+
- and ofc, zero type checking and safeguards :/
79+
"""
80+
def __inner(clazz: T) -> T:
81+
RESERVED_KWS = {'object_reader'}
82+
# Allow these to be propogated to the props
83+
def __init__(self, **d):
84+
def enusre_reserved(obj):
85+
for k in RESERVED_KWS:
86+
if hasattr(obj, k) and k in d:
87+
setattr(obj, k, d[k])
88+
return obj
89+
def reduce_init(clazz, **d):
90+
types : dict = clazz.__annotations__
91+
for k, sub in types.items():
92+
if type(sub) == str:
93+
sub = eval(sub) # attrs turns these into strings...why?
94+
reduce_arg = getattr(sub, "__args__", [None])[0]
95+
if isinstance(d[k], list):
96+
if hasattr(reduce_arg, "__annotations__"):
97+
setattr(self, k, [enusre_reserved(reduce_arg(**x)) for x in d[k]])
98+
else:
99+
setattr(self, k, [enusre_reserved(reduce_arg(x)) for x in d[k]])
100+
elif isinstance(d[k], dict) and hasattr(sub, "__annotations__"):
101+
setattr(self, k, enusre_reserved(sub(**d[k])))
102+
else:
103+
if isinstance(d[k], dict):
104+
setattr(self, k, enusre_reserved(sub(**d[k])))
105+
else:
106+
setattr(self, k, enusre_reserved(sub(d[k])))
107+
for __base__ in clazz.__bases__:
108+
types : dict = __base__.__annotations__
109+
args = {k:d[k] for k in types if k in d}
110+
if len(args) == len(types):
111+
super(clazz, self).__init__(**args)
112+
reduce_init(__base__, **d)
113+
for k in args:
114+
if not k in RESERVED_KWS: del d[k]
115+
reduce_init(clazz, **d)
116+
enusre_reserved(self)
117+
def __repr__(self) -> str:
118+
return f"{clazz.__name__}({', '.join([f'{k}={getattr(self, k)!r}' for k in self.__annotations__])})"
119+
clazz.__init__ = __init__
120+
clazz.__repr__ = __repr__
121+
UTTCG_Classes[fullname] = clazz
122+
return clazz
123+
return __inner
124+
119125
# Helper functions
120-
def UTTCGen_Reread(Mono: MonoBehaviour):
121-
script = Mono.m_Script.read()
122-
fullname = script.m_ClassName
123-
if script.m_Namespace:
124-
fullname = f"{script.m_Namespace}.{fullname}"
125-
definition = TYPETREE_DEFS.get(fullname, None)
126+
def UTTCGen_Reread(src: MonoBehaviour | ObjectReader, fullname: str = None) -> Tuple[dict, str]:
127+
"""Rereads the typetree data from the MonoBehaviour instance with our typetree definition.
128+
129+
Args:
130+
src: The MonoBehaviour instance or ObjectReader to read from.
131+
fullname: The full name of the class to read. If None, it will be read from the MonoBehaviour instance's `m_Script` property.
132+
133+
Returns:
134+
Tuple[dict, str]: [raw_def, fullname] where `raw_def` is the raw data read from the MonoBehaviour instance and `fullname` is the full name of the class.
135+
"""
136+
if not fullname and isinstance(src, MonoBehaviour):
137+
script = src.m_Script.read()
138+
fullname = script.m_ClassName
139+
if script.m_Namespace:
140+
fullname = f"{script.m_Namespace}.{fullname}"
141+
definition = UTTCG_Typetrees.get(fullname, None)
126142
assert definition is not None, f"Typetree definition for {fullname} not found"
127-
raw_def = Mono.object_reader.read_typetree(definition)
143+
if isinstance(src, MonoBehaviour):
144+
src = src.object_reader
145+
raw_def = src.read_typetree(definition, check_read=False)
128146
return raw_def, fullname
147+
148+
def UTTCGen_Instantiate(raw_def : dict, fullname: str):
149+
"""Instantiate a class from the typetree definition and the raw data.
150+
151+
Args:
152+
raw_def (dict): The raw data read from the MonoBehaviour instance.
153+
fullname (str): The full name of the class to instantiate.
154+
"""
155+
clazz = UTTCG_Classes.get(fullname, None)
156+
assert clazz is not None, f"Class definition for {fullname} not found"
157+
instance = clazz(**raw_def)
158+
return instance
159+
160+
def UTTCGen_AsInstance(src: MonoBehaviour | ObjectReader, fullname: str = None) -> T:
161+
"""Instantiate a class from the typetree definition and the raw data.
162+
163+
In most cases, this is the function you want to use.
164+
It will read the typetree data from the MonoBehaviour instance and instantiate the class with the data.
165+
166+
Args:
167+
src (MonoBehaviour | ObjectReader): The MonoBehaviour instance or ObjectReader to read from.
168+
fullname (str): The full name of the class to read. If None, it will be read from the MonoBehaviour instance's `m_Script` property.
169+
170+
Returns:
171+
T: An instance of the class defined by the typetree.
172+
"""
173+
raw_def, fullname = UTTCGen_Reread(src, fullname)
174+
instance = UTTCGen_Instantiate(raw_def, fullname)
175+
return instance
129176
''',
130177
]
131178
)
@@ -286,6 +333,7 @@ def emit_line(*lines: str):
286333
logger.info(f"Subpass 2: Generating code for {namespace}")
287334
dp = defaultdict(lambda: -1)
288335
for clazz in topo:
336+
fullname = f"{namespace}.{clazz}" if namespace else clazz
289337
fields = classname_nodes.get(clazz, None)
290338
if not fields:
291339
logger.debug(
@@ -297,7 +345,7 @@ def emit_line(*lines: str):
297345
clazz = translate_name(clazz)
298346
clazzes.append(clazz)
299347
clazz_fields = list()
300-
emit_line(f"@UTTCGen")
348+
emit_line(f"@UTTCGen('{fullname}')")
301349
if lvl1:
302350
parent = translate_type(fields[0].m_Type, strip=True, fallback=False)
303351
emit_line(f"class {clazz}({parent}):")
@@ -409,7 +457,7 @@ def __encoder(obj):
409457
return obj
410458

411459
__open("__init__.py").write(
412-
"\nTYPETREE_DEFS = " + json.dumps(fullname_nodes, indent=4, default=__encoder)
460+
"\nUTTCG_Typetrees = " + json.dumps(fullname_nodes, indent=4, default=__encoder)
413461
)
414462

415463

0 commit comments

Comments
 (0)