Skip to content

Commit bef9cc9

Browse files
committed
chore(Readme): use new read/save versions, add peek_name mention
1 parent 8d56bf4 commit bef9cc9

File tree

1 file changed

+86
-45
lines changed

1 file changed

+86
-45
lines changed

README.md

Lines changed: 86 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ Via the typetree structure all object types can be edited in their native forms.
1313

1414
```python
1515
# modification via dict:
16-
raw_dict = obj.read_typetree()
16+
raw_dict = obj.parse_as_dict()
1717
# modify raw dict
18-
obj.save_typetree(raw_dict)
18+
obj.patch(raw_dict)
1919
# modification via parsed class
20-
instance = obj.read()
20+
instance = obj.parse_as_object()
2121
# modify instance
22-
obj.save(instance)
22+
obj.patch(instance)
2323
```
2424

2525
If you need advice or if you want to talk about (game) data-mining,
@@ -100,10 +100,10 @@ def unpack_all_assets(source_folder: str, destination_folder: str):
100100
# process specific object types
101101
if obj.type.name in ["Texture2D", "Sprite"]:
102102
# parse the object data
103-
data = obj.read()
103+
data = obj.parse_as_object()
104104

105105
# create destination path
106-
dest = os.path.join(destination_folder, data.name)
106+
dest = os.path.join(destination_folder, data.m_Name)
107107

108108
# make sure that the extension is correct
109109
# you probably only want to do so with images/textures
@@ -116,7 +116,7 @@ def unpack_all_assets(source_folder: str, destination_folder: str):
116116
# alternative way which keeps the original path
117117
for path,obj in env.container.items():
118118
if obj.type.name in ["Texture2D", "Sprite"]:
119-
data = obj.read()
119+
data = obj.parse_as_object()
120120
# create dest based on original path
121121
dest = os.path.join(destination_folder, *path.split("/"))
122122
# make sure that the dir of that path exists
@@ -187,7 +187,51 @@ The objects with a file path can be found in the `.container` dict - `{path : ob
187187

188188
Objects \([ObjectReader class](UnityPy/files/ObjectReader.py)\) contain the _actual_ files, e.g., textures, text files, meshes, settings, ...
189189

190-
To acquire the actual data of an object it has to be read first. This happens via the `.read()` function. This isn't done automatically to save time because only a small part of the objects are of interest. Serialized objects can be set with raw data using `.set_raw_data(data)` or modified with `.save()` function, if supported.
190+
To acquire the actual data of an object it has to be parsed first.
191+
This happens via the parse functions mentioned below.
192+
This isn't done automatically to save time as only a small part of the objects are usually of interest.
193+
Serialized objects can be set with raw data using `.set_raw_data(data)` or modified with `.save()` function, if supported.
194+
195+
For object types with ``m_Name`` you can use ``.peek_name()`` to only read the name of the parsed object without parsing it completely, which is way faster.
196+
197+
There are two general parsing functions, ``.parse_as_object()`` and ``.parse_as_dict()``.
198+
``parse_as_dict`` parses the object data into a dict.
199+
``parse_as_object`` parses the object data into a class. If the class is a Unity class, it's stub class from ``UnityPy.classes(.generated)`` will be used, if it's an unknown one, then it will be parsed into an ``UnknownObject``, which simply acts as interface for the otherwise parsed dict.
200+
Some special classes, namely those below, have additional handlers added to their class for easier interaction with them.
201+
202+
The ``.patch(item)`` function can be used on all object (readers) to replace their data with the changed item, which has to be either a dict or of the class the object represents.
203+
204+
#### Example
205+
206+
```py
207+
for obj in env.objects:
208+
if obj.type.name == "the type you want":
209+
if obj.peek_name() != "the specific object you want":
210+
continue
211+
# parsing
212+
instance = obj.parse_as_object()
213+
dic = obj.parse_as_dict()
214+
215+
# modifying
216+
instance.m_Name = "new name"
217+
dic["m_Name"] = "new name"
218+
219+
# saving
220+
obj.patch(instance)
221+
obj.patch(dic)
222+
```
223+
224+
#### Legacy
225+
226+
Following functions are legacy functions that will be removed in the future when major version 2 hits.
227+
The modern versions are equivalent to them and have a more correct type hints.
228+
229+
| Legacy | Modern |
230+
|---------------|-----------------|
231+
| read | parse_as_object |
232+
| read_typetree | parse_as_dict |
233+
| save_typetree | patch |
234+
191235

192236
## Important Object Types
193237

@@ -207,14 +251,14 @@ from PIL import Image
207251
for obj in env.objects:
208252
if obj.type.name == "Texture2D":
209253
# export texture
210-
data = obj.read()
211-
path = os.path.join(export_dir, f"{data.m_Name}.png")
212-
data.image.save(path)
254+
tex = obj.parse_as_object()
255+
path = os.path.join(export_dir, f"{tex.m_Name}.png")
256+
tex.image.save(path)
213257
# edit texture
214-
fp = os.path.join(replace_dir, f"{data.m_Name}.png")
258+
fp = os.path.join(replace_dir, f"{tex.m_Name}.png")
215259
pil_img = Image.open(fp)
216-
data.image = pil_img
217-
data.save()
260+
tex.image = pil_img
261+
tex.save()
218262
```
219263

220264
### Sprite
@@ -232,9 +276,9 @@ Unlike most other extractors (including AssetStudio), UnityPy merges those two i
232276
```python
233277
for obj in env.objects:
234278
if obj.type.name == "Sprite":
235-
data = obj.read()
236-
path = os.path.join(export_dir, f"{data.m_Name}.png")
237-
data.image.save(path)
279+
sprite = obj.parse_as_object()
280+
path = os.path.join(export_dir, f"{sprite.m_Name}.png")
281+
sprite.image.save(path)
238282
```
239283

240284
### TextAsset
@@ -244,7 +288,8 @@ TextAssets are usually normal text files.
244288
- `.m_Name`
245289
- `.m_Script` - str
246290

247-
Some games save binary data as TextAssets. As ``m_Script`` gets handled as str by default,
291+
Some games save binary data as TextAssets.
292+
As ``m_Script`` gets handled as str by default,
248293
use ``m_Script.encode("utf-8", "surrogateescape")`` to retrieve the original binary data.
249294

250295
**Export**
@@ -253,15 +298,15 @@ use ``m_Script.encode("utf-8", "surrogateescape")`` to retrieve the original bin
253298
for obj in env.objects:
254299
if obj.type.name == "TextAsset":
255300
# export asset
256-
data = obj.read()
257-
path = os.path.join(export_dir, f"{data.m_Name}.txt")
301+
txt = obj.parse_as_object()
302+
path = os.path.join(export_dir, f"{txt.m_Name}.txt")
258303
with open(path, "wb") as f:
259-
f.write(data.m_Script.encode("utf-8", "surrogateescape"))
304+
f.write(txt.m_Script.encode("utf-8", "surrogateescape"))
260305
# edit asset
261-
fp = os.path.join(replace_dir, f"{data.m_Name}.txt")
306+
fp = os.path.join(replace_dir, f"{txt.m_Name}.txt")
262307
with open(fp, "rb") as f:
263-
data.m_Script = f.read().decode("utf-8", "surrogateescape"))
264-
data.save()
308+
txt.m_Script = f.read().decode("utf-8", "surrogateescape")
309+
txt.save()
265310
```
266311

267312
### MonoBehaviour
@@ -272,6 +317,7 @@ In such cases see the 2nd example (TypeTreeGenerator) below.
272317

273318
- `.m_Name`
274319
- `.m_Script`
320+
- custom data
275321

276322
**Export**
277323

@@ -281,22 +327,16 @@ import json
281327
for obj in env.objects:
282328
if obj.type.name == "MonoBehaviour":
283329
# export
284-
if obj.serialized_type.node:
285-
# save decoded data
286-
tree = obj.read_typetree()
287-
fp = os.path.join(extract_dir, f"{tree['m_Name']}.json")
288-
with open(fp, "wt", encoding = "utf8") as f:
289-
json.dump(tree, f, ensure_ascii = False, indent = 4)
330+
# save decoded data
331+
tree = obj.parse_as_dict()
332+
fp = os.path.join(extract_dir, f"{tree['m_Name']}.json")
333+
with open(fp, "wt", encoding = "utf8") as f:
334+
json.dump(tree, f, ensure_ascii = False, indent = 4)
290335

291336
# edit
292-
if obj.serialized_type.node:
293-
tree = obj.read_typetree()
294-
# apply modifications to the data within the tree
295-
obj.save_typetree(tree)
296-
else:
297-
data = obj.read(check_read=False)
298-
with open(os.path.join(replace_dir, data.m_Name)) as f:
299-
data.save(raw_data = f.read())
337+
tree = obj.parse_as_dict()
338+
# apply modifications to the data within the tree
339+
obj.patch(tree)
300340
```
301341

302342
**TypeTreeGenerator**
@@ -328,7 +368,7 @@ env.typetree_generator = generator
328368
for obj in objects:
329369
if obj.type.name == "MonoBehaviour":
330370
# automatically tries to use the generator in the background if necessary
331-
x = obj.read()
371+
x = obj.parse_as_object()
332372
```
333373

334374

@@ -352,7 +392,7 @@ for name, data in clip.samples.items():
352392

353393
```python
354394
if obj.type.name == "Font":
355-
font: Font = obj.read()
395+
font: Font = obj.parse_as_object()
356396
if font.m_FontData:
357397
extension = ".ttf"
358398
if font.m_FontData[0:4] == b"OTTO":
@@ -389,8 +429,9 @@ export_dir: str
389429

390430
if mesh_renderer.m_GameObject:
391431
# get the name of the model
392-
game_object = mesh_renderer.m_GameObject.read()
393-
export_dir = os.path.join(export_dir, game_object.m_Name)
432+
game_obj_reader = mesh_renderer.m_GameObject.deref()
433+
game_obj_name = game_obj_reader.peek_name()
434+
export_dir = os.path.join(export_dir, game_obj_name)
394435
mesh_renderer.export(export_dir)
395436
```
396437

@@ -411,9 +452,9 @@ from PIL import Image
411452
for obj in env.objects:
412453
if obj.type.name == "Texture2DArray":
413454
# export texture
414-
data = obj.read()
415-
for i, image in enumerate(data.images):
416-
image.save(os.path.join(path, f"{data.m_Name}_{i}.png"))
455+
tex_arr = obj.parse_as_object()
456+
for i, image in enumerate(tex_arr.images):
457+
image.save(os.path.join(path, f"{tex_arr.m_Name}_{i}.png"))
417458
# editing isn't supported yet!
418459
```
419460

0 commit comments

Comments
 (0)