Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 76 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,6 @@
A Unity asset extractor for Python based on [AssetStudio](https://github.com/Perfare/AssetStudio).

Next to extraction, UnityPy also supports editing Unity assets.
Via the typetree structure all object types can be edited in their native forms.

```python
# modification via dict:
raw_dict = obj.parse_as_dict()
# modify raw dict
obj.patch(raw_dict)
# modification via parsed class
instance = obj.parse_as_object()
# modify instance
obj.patch(instance)
```

If you need advice or if you want to talk about (game) data-mining,
feel free to join the [UnityPy Discord](https://discord.gg/C6txv7M).
Expand Down Expand Up @@ -90,41 +78,44 @@ def unpack_all_assets(source_folder: str, destination_folder: str):
# iterate over all files in source folder
for root, dirs, files in os.walk(source_folder):
for file_name in files:
# generate file_path
# generate full file path
file_path = os.path.join(root, file_name)
# load that file via UnityPy.load
env = UnityPy.load(file_path)

# iterate over internal objects
for obj in env.objects:
# process specific object types
if obj.type.name in ["Texture2D", "Sprite"]:
# parse the object data
data = obj.parse_as_object()

# create destination path
dest = os.path.join(destination_folder, data.m_Name)

# make sure that the extension is correct
# you probably only want to do so with images/textures
dest, ext = os.path.splitext(dest)
dest = dest + ".png"

img = data.image
img.save(dest)

# alternative way which keeps the original path
for path,obj in env.container.items():
if obj.type.name in ["Texture2D", "Sprite"]:
data = obj.parse_as_object()
# create dest based on original path
dest = os.path.join(destination_folder, *path.split("/"))
# make sure that the dir of that path exists
os.makedirs(os.path.dirname(dest), exist_ok = True)
# correct extension
dest, ext = os.path.splitext(dest)
dest = dest + ".png"
data.image.save(dest)
# open and load that file
with open(file_path, "rb") as f:
env = UnityPy.load(f)

# iterate over internal objects
for obj in env.objects:
# process specific object types
if obj.type.name in ["Texture2D", "Sprite"]:
# parse the object data
data = obj.parse_as_object()

# generate destination path
dest = os.path.join(destination_folder, data.m_Name)

# make sure that the extension is correct
# you probably only want to do so with images/textures
dest, ext = os.path.splitext(dest)
dest = dest + ".png"

img = data.image
img.save(dest)

# alternative way which keeps the original path
for path, obj in env.container.items():
if obj.type.name in ["Texture2D", "Sprite"]:
data = obj.parse_as_object()

# generate destination based on original path
dest = os.path.join(destination_folder, *path.split("/"))
# make sure that the dir of that path exists
os.makedirs(os.path.dirname(dest), exist_ok = True)

# correct extension
dest, ext = os.path.splitext(dest)
dest = dest + ".png"
data.image.save(dest)
```

You probably have to read [Important Classes](#important-classes)
Expand All @@ -138,39 +129,58 @@ It can also be used as a general template or as an importable tool.
### Environment

[Environment](UnityPy/environment.py) loads and parses the given files.
It can be initialized via:

- a file path - apk files can be loaded as well
- a folder path - loads all files in that folder (bad idea for folders with a lot of files)
- a stream - e.g., `io.BytesIO`, file stream,...
- a bytes object - will be loaded into a stream
#### Initialization

UnityPy can detect if the file is a WebFile, BundleFile, Asset, or APK.
An Environment object can be created by providing:

The unpacked assets will be loaded into `.files`, a dict consisting of `asset-name : asset`.
- a file path - loads a single file (typically an asset bundle file, apk file or zip file).
- a folder path - loads all files in the given folder (bad idea for large folders).
- a streamable object - loads the data from the given stream (it can be `io.BytesIO`, file stream returned by `open()` and any other object that extends `io.IOBase`).
- a bytes object - loads the data from memory.

All objects of the loaded assets can be easily accessed via `.objects`,
which itself is a simple recursive iterator.
UnityPy can detect if the file is a WebFile, BundleFile, Asset, or APK.

The following code shows the different ways to create an Environment object.

```python
import io
import UnityPy

# all of the following would work
src = "file_path"
src = b"bytes"
src = io.BytesIO(b"Streamable")
# load from file path or folder path
env = UnityPy.load("path/to/your/file")

env = UnityPy.load(src)
# note that the best way to pass a file to UnityPy is
# using with-statement to ensure the file can be properly closed
with open("path/to/your/file", "rb") as f:
env = UnityPy.load(f)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could actually cause issues for SerializedFiles, as they aren't read into memory due to no decompression.
Something like

f = open(fp, "rb")
env = UnityPy.load(f)
# use the environment
f.close()

would be better


# load from bytes io stream
data = io.BytesIO(b"streamable-data")
env = UnityPy.load(data)

# load from bytes object
data = b"some-bytes-data"
env = UnityPy.load(data)
```

#### Attributes

The unpacked assets will be loaded into `.files`, a dict consisting of `asset-name : asset`.

All objects of the loaded assets can be easily accessed via `.objects`, which itself is a simple recursive iterator.
If you want, you can modify the objects and save the edited file later.
See [Object](#object) section to learn how to apply modifications to the objects.

```python
# assumes that you have already created an `env`

for obj in env.objects:
# your code for processing each object
...

# saving an edited file
# apply modifications to the objects
# don't forget to use data.save()
...
with open(dst, "wb") as f:
# don't forget to save an edited file
with open("path/to/save", "wb") as f:
f.write(env.file.save())
```

Expand Down Expand Up @@ -227,7 +237,7 @@ Following functions are legacy functions that will be removed in the future when
The modern versions are equivalent to them and have a more correct type hints.

| Legacy | Modern |
|---------------|-----------------|
| ------------- | --------------- |
| read | parse_as_object |
| read_typetree | parse_as_dict |
| save_typetree | patch |
Expand Down
Loading