Skip to content

Commit 87e7d9f

Browse files
committed
fix tools.extractor and add a test for it
1 parent 5d6de1a commit 87e7d9f

File tree

2 files changed

+68
-47
lines changed

2 files changed

+68
-47
lines changed

UnityPy/tools/extractor.py

+42-47
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
1-
from io import BytesIO
2-
import os
31
import json
2+
import os
3+
from io import BytesIO
4+
from pathlib import Path
5+
from typing import Callable, Dict, List, Union
6+
47
import UnityPy
58
from UnityPy.classes import (
9+
AudioClip,
10+
Font,
11+
GameObject,
12+
Mesh,
13+
MonoBehaviour,
614
Object,
715
PPtr,
8-
MonoBehaviour,
9-
TextAsset,
10-
Font,
1116
Shader,
12-
Mesh,
1317
Sprite,
18+
TextAsset,
1419
Texture2D,
15-
AudioClip,
16-
GameObject,
1720
)
1821
from UnityPy.enums.ClassIDType import ClassIDType
19-
from typing import Union, List, Dict, Callable
20-
from pathlib import Path
2122

2223

2324
def export_obj(
@@ -57,7 +58,7 @@ def export_obj(
5758
return []
5859

5960
if append_name:
60-
fp = os.path.join(fp, obj.name if obj.name else obj.type.name)
61+
fp = os.path.join(fp, obj.m_Name if getattr(obj, "m_Name") else obj.type.name)
6162

6263
fp, extension = os.path.splitext(fp)
6364

@@ -114,7 +115,7 @@ def defaulted_export_index(type: ClassIDType):
114115
# the check of the various sub directories is required to avoid // in the path
115116
obj_dest = os.path.join(
116117
dst,
117-
*(x for x in obj_path.split("/")[:ignore_first_container_dirs] if x),
118+
*(x for x in obj_path.split("/")[ignore_first_container_dirs:] if x),
118119
)
119120
os.makedirs(os.path.dirname(obj_dest), exist_ok=True)
120121
exported.extend(
@@ -156,8 +157,8 @@ def exportTextAsset(obj: TextAsset, fp: str, extension: str = ".txt") -> List[in
156157
if not extension:
157158
extension = ".txt"
158159
with open(f"{fp}{extension}", "wb") as f:
159-
f.write(obj.script)
160-
return [(obj.assets_file, obj.path_id)]
160+
f.write(obj.m_Script.encode("utf-8", "surrogateescape"))
161+
return [(obj.assets_file, obj.object_reader.path_id)]
161162

162163

163164
def exportFont(obj: Font, fp: str, extension: str = "") -> List[int]:
@@ -167,65 +168,60 @@ def exportFont(obj: Font, fp: str, extension: str = "") -> List[int]:
167168
if obj.m_FontData[0:4] == b"OTTO":
168169
extension = ".otf"
169170
with open(f"{fp}{extension}", "wb") as f:
170-
f.write(obj.m_FontData)
171-
return [(obj.assets_file, obj.path_id)]
171+
f.write(bytes(obj.m_FontData))
172+
return [(obj.assets_file, obj.object_reader.path_id)]
172173

173174

174175
def exportMesh(obj: Mesh, fp: str, extension=".obj") -> List[int]:
175176
if not extension:
176177
extension = ".obj"
177178
with open(f"{fp}{extension}", "wt", encoding="utf8", newline="") as f:
178179
f.write(obj.export())
179-
return [(obj.assets_file, obj.path_id)]
180+
return [(obj.assets_file, obj.object_reader.path_id)]
180181

181182

182183
def exportShader(obj: Shader, fp: str, extension=".txt") -> List[int]:
183184
if not extension:
184185
extension = ".txt"
185186
with open(f"{fp}{extension}", "wt", encoding="utf8", newline="") as f:
186187
f.write(obj.export())
187-
return [(obj.assets_file, obj.path_id)]
188+
return [(obj.assets_file, obj.object_reader.path_id)]
188189

189190

190191
def exportMonoBehaviour(
191192
obj: Union[MonoBehaviour, Object], fp: str, extension: str = ""
192193
) -> List[int]:
193194
export = None
194-
# TODO - add generic way to add external typetrees
195-
if obj.serialized_type and obj.serialized_type.node:
196-
extension = ".json"
197-
export = json.dumps(obj.read_typetree(), indent=4, ensure_ascii=False).encode(
198-
"utf8", errors="surrogateescape"
199-
)
195+
196+
if obj.object_reader.serialized_type.node:
197+
# a typetree is available from the SerializedFile for this object
198+
export = obj.object_reader.read_typetree()
200199
elif isinstance(obj, MonoBehaviour):
201-
# no set typetree
202-
# check if we have a script
203-
script = obj.m_Script
204-
if script:
200+
# try to get the typetree from the MonoBehavior script
201+
script_ptr = obj.m_Script
202+
if script_ptr:
205203
# looks like we have a script
206-
script = script.read()
204+
script = script_ptr.read()
207205
# check if there is a locally stored typetree for it
208206
nodes = MONOBEHAVIOUR_TYPETREES.get(script.m_AssemblyName, {}).get(
209207
script.m_ClassName, None
210208
)
211209
if nodes:
212-
# we have a typetree
213-
# adjust the name
214-
# name = (
215-
# f"{script.m_ClassName}-{obj.name}"
216-
# if obj.name
217-
# else script.m_ClassName
218-
# )
219-
extension = ".json"
220-
export = json.dumps(
221-
obj.read_typetree(nodes), indent=4, ensure_ascii=False
222-
).encode("utf8", errors="surrogateescape")
210+
export = obj.object_reader.read_typetree(nodes)
211+
else:
212+
export = obj.object_reader.read_typetree()
213+
223214
if not export:
224215
extension = ".bin"
225-
export = obj.raw_data
216+
export = obj.object_reader.raw_data
217+
else:
218+
extension = ".json"
219+
export = json.dumps(export, indent=4, ensure_ascii=False).encode(
220+
"utf8", errors="surrogateescape"
221+
)
226222
with open(f"{fp}{extension}", "wb") as f:
227223
f.write(export)
228-
return [(obj.assets_file, obj.path_id)]
224+
return [(obj.assets_file, obj.object_reader.path_id)]
229225

230226

231227
def exportAudioClip(obj: AudioClip, fp: str, extension: str = "") -> List[int]:
@@ -240,16 +236,16 @@ def exportAudioClip(obj: AudioClip, fp: str, extension: str = "") -> List[int]:
240236
for name, clip_data in samples.items():
241237
with open(os.path.join(fp, f"{name}.wav"), "wb") as f:
242238
f.write(clip_data)
243-
return [(obj.assets_file, obj.path_id)]
239+
return [(obj.assets_file, obj.object_reader.path_id)]
244240

245241

246242
def exportSprite(obj: Sprite, fp: str, extension: str = ".png") -> List[int]:
247243
if not extension:
248244
extension = ".png"
249245
obj.image.save(f"{fp}{extension}")
250246
exported = [
251-
(obj.assets_file, obj.path_id),
252-
(obj.m_RD.texture.assets_file, obj.m_RD.texture.path_id),
247+
(obj.assets_file, obj.object_reader.path_id),
248+
(obj.m_RD.texture.assetsfile, obj.m_RD.texture.path_id),
253249
]
254250
alpha_assets_file = getattr(obj.m_RD.alphaTexture, "assets_file", None)
255251
alpha_path_id = getattr(obj.m_RD.alphaTexture, "path_id", None)
@@ -322,8 +318,7 @@ def crawl_obj(obj: Object, ret: dict = None) -> Dict[int, Union[Object, PPtr]]:
322318
# MonoBehaviour really on their typetree
323319
# while Object denotes that the class of the object isn't implemented yet
324320
if isinstance(obj, (MonoBehaviour, Object)):
325-
obj.read_typetree()
326-
data = obj.type_tree.__dict__.values()
321+
data = obj.read_typetree().__dict__.values()
327322
else:
328323
data = obj.__dict__.values()
329324

tests/test_extractor.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import os
2+
from tempfile import TemporaryDirectory
3+
4+
from UnityPy.tools.extractor import extract_assets
5+
6+
SAMPLES = os.path.join(os.path.dirname(os.path.abspath(__file__)), "samples")
7+
8+
9+
def test_extractor():
10+
temp_dir = TemporaryDirectory(prefix="unitypy_test")
11+
extract_assets(
12+
SAMPLES,
13+
temp_dir.name,
14+
True,
15+
)
16+
files = [
17+
os.path.relpath(os.path.join(root, f), temp_dir.name)
18+
for root, dirs, files in os.walk(temp_dir.name)
19+
for f in files
20+
]
21+
temp_dir.cleanup()
22+
assert len(files) == 45
23+
24+
25+
if __name__ == "__main__":
26+
test_extractor()

0 commit comments

Comments
 (0)