A pythonic port of AssetStudio by Perfare
Project description
UnityPy
A Unity asset extractor for Python based on AssetStudio.
Next to extraction, it also supports editing Unity assets. So far following obj types can be edited:
- Texture2D
- Sprite(indirectly via linked Texture2D)
- TextAsset
- MonoBehaviour (and all other types that you have the typetree of)
If you need advice or if you want to talk about (game) data-mining, feel free to join the UnityPy Discord.
If you're using UnityPy a commercial project, a donation to a charitable cause or a sponsorship of this project is expected.
As UnityPy is still in active development breaking changes can happen. Those changes are usually limited to minor versions (x.y) and not to patch versions (x.y.z). So in case that you don't want to actively maintain your project, make sure to make a note of the used UnityPy version in your README or add a check in your code. e.g.
if UnityPy.__version__ == '1.9.6':
raise ImportError("Invalid Unity version detected. Please use version 1.9.6")
Installation
Python 3.6.0 or higher is required
pip install UnityPy
or download/clone the git and use
python setup.py install
Notes
Windows
Visual C++ Redistributable is required for the brotli dependency.
Crash without warning/error
The C-implementation of the typetree reader can directly crash python. In case this happens, the usage of the C-typetree reader can be disabled by adding these two lines to your main file.
from UnityPy.helpers import TypeTreeHelper
TypeTreeHelper.read_typetree_c = False
Example
The following is a simple example.
import os
import UnityPy
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
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.read()
# create destination path
dest = os.path.join(destination_folder, data.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.read()
# 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)
You probably have to read Important Classes and Important Object Types to understand how it works.
People with slightly advanced python skills should look at UnityPy/tools/extractor.py for a more advanced example. It can also be used as a general template or as an importable tool.
Important Classes
Environment
Environment 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
UnityPy can detect if the file is a WebFile, BundleFile, Asset, or APK.
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.
import io
import UnityPy
# all of the following would work
src = "file_path"
src = b"bytes"
src = io.BytesIO(b"Streamable")
env = UnityPy.load(src)
for obj in env.objects:
...
# saving an edited file
# apply modifications to the objects
# don't forget to use data.save()
...
with open(dst, "wb") as f:
f.write(env.file.save())
Asset
Assets are a container that contains multiple objects. One of these objects can be an AssetBundle, which contains a file path for some of the objects in the same asset.
All objects can be found in the .objects
dict - {ID : object}
.
The objects with a file path can be found in the .container
dict - {path : object}
.
Object
Objects contain the actual files, e.g., textures, text files, meshes, settings, ...
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.
Important Object Types
All object types can be found in UnityPy/classes.
Texture2D
.name
.image
converts the texture into aPIL.Image
.m_Width
- texture width (int).m_Height
- texture height (int)
Export
from PIL import Image
for obj in env.objects:
if obj.type.name == "Texture2D":
# export texture
data = image.read()
data.image.save(path)
# edit texture
fp = os.path.join(replace_dir, data.name)
pil_img = Image.open(fp)
data.image = pil_img
data.save()
Sprite
Sprites are part of a texture and can have a separate alpha-image as well. Unlike most other extractors (including AssetStudio), UnityPy merges those two images by itself.
.name
.image
- converts the merged texture part into aPIL.Image
.m_Width
- sprite width (int).m_Height
- sprite height (int)
Export
for obj in env.objects:
if obj.type.name == "Sprite":
data = image.read()
data.image.save(path)
TextAsset
TextAssets are usually normal text files.
.name
.script
- binary data (bytes).text
- script decoded via UTF8 (str)
Some games save binary data as TextFile, so it's usually better to use .script
.
Export
for obj in env.objects:
if obj.type.name == "TextAsset":
# export asset
data = image.read()
with open(path, "wb") as f:
f.write(bytes(data.script))
# edit asset
fp = os.path.join(replace_dir, data.name)
with open(fp, "rb") as f:
data.script = f.read()
data.save()
MonoBehaviour
MonoBehaviour assets are usually used to save the class instances with their values. If a type tree exists, it can be used to read the whole data, but if it doesn't exist, then it is usually necessary to investigate the class that loads the specific MonoBehaviour to extract the data. (example)
.name
.script
.raw_data
- data after the basic initialisation
Export
import json
for obj in env.objects:
if obj.type.name == "MonoBehaviour":
# export
if obj.serialized_type.nodes:
# save decoded data
tree = obj.read_typetree()
fp = os.path.join(extract_dir, f"{tree['m_Name']}.json")
with open(fp, "wt", encoding = "utf8") as f:
json.dump(tree, f, ensure_ascii = False, indent = 4)
else:
# save raw relevant data (without Unity MonoBehaviour header)
data = obj.read()
fp = os.path.join(extract_dir, f"{data.name}.bin")
with open(fp, "wb") as f:
f.write(data.raw_data)
# edit
if obj.serialized_type.nodes:
tree = obj.read_typetree()
# apply modifications to the data within the tree
obj.save_typetree(tree)
else:
data = obj.read()
with open(os.path.join(replace_dir, data.name)) as f:
data.save(raw_data = f.read())
AudioClip
.samples
-{sample-name : sample-data}
The samples are converted into the .wav format. The sample data is a .wav file in bytes.
clip : AudioClip
for name, data in clip.samples.items():
with open(name, "wb") as f:
f.write(data)
Font
if obj.type.name == "Font":
font : Font = obj.read()
if font.m_FontData:
extension = ".ttf"
if font.m_FontData[0:4] == b"OTTO":
extension = ".otf"
with open(os.path.join(path, font.name+extension), "wb") as f:
f.write(font.m_FontData)
Mesh
.export()
- mesh exported as .obj (str)
The mesh will be converted to the Wavefront .obj file format.
mesh : Mesh
with open(f"{mesh.name}.obj", "wt", newline = "") as f:
# newline = "" is important
f.write(mesh.export())
Renderer, MeshRenderer, SkinnedMeshRenderer
ALPHA-VERSION
.export(export_dir)
- exports the associated mesh, materials, and textures into the given directory
The mesh and materials will be in the Wavefront formats.
mesh_renderer : Renderer
export_dir: str
if mesh_renderer.m_GameObject:
# get the name of the model
game_object = mesh_renderer.m_GameObject.read()
export_dir = os.path.join(export_dir, game_object.name)
mesh_renderer.export(export_dir)
Credits
First of all, thanks a lot to all contributors of UnityPy and all of its users.
Also, many thanks to:
- Perfare for creating and maintaining and every contributor of AssetStudio
- ds5678 for the TypeTreeDumps and the custom minimal Tpk format
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Hashes for UnityPy-1.9.9-cp310-cp310-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2ed9e919729f8d0b83beb7fcbac301a74e8e78fdd63ae76e10d8cdd98f06b541 |
|
MD5 | 75be2553ec72ccf45281c291211eec21 |
|
BLAKE2b-256 | b467e2f4a703576cf5efdfd8349946d4785f266de5dd1723ba1a7ca3f73b21bd |
Hashes for UnityPy-1.9.9-cp310-cp310-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4bfbbc39a24c0d7570b0c7673cfd0d2dedcd0d2dfcba5304a2cf43923cfcce69 |
|
MD5 | 1b315f41803fc71aec2d145e38516e26 |
|
BLAKE2b-256 | 92ed9ce0c7b61e5bd14e8a22e76ad119dea81f7d3103975aa7c9f1bbd70b4364 |
Hashes for UnityPy-1.9.9-cp310-cp310-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 44a292f35ba97e236a2f7f5d80b79a3a38b611b51398ad5092298ec446b9f214 |
|
MD5 | 3efd494b96937d5de08f2a2049258e2a |
|
BLAKE2b-256 | a06506a3863432e969f75d6f274c4ed396f806b0dd2c14150f33155387749d48 |
Hashes for UnityPy-1.9.9-cp39-cp39-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 393a10e65d0c5469f87a45a19ed28582fa1fcf13c0c28fd7e73b7989aeb3984b |
|
MD5 | 31d4758f30ea953e471f0fbc5cc458aa |
|
BLAKE2b-256 | c5d3c914e08ba14860803c62d47f362f69f6f69aa5f94739d83b06692e3ff817 |
Hashes for UnityPy-1.9.9-cp39-cp39-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1b472c0025061e8da75084cd631a785b7242b965a896ab660bfd8633d1a3ed10 |
|
MD5 | 55478267cf8a0f15a5ff951a1b7ac579 |
|
BLAKE2b-256 | 4a1406aad71f75f2fb85f57ac68297b7c256aad5dec85c2471d0714e95b3fe6a |
Hashes for UnityPy-1.9.9-cp39-cp39-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6fe8338bf8878f989dd9d4c086afa3581fa897ac7d86dcab9d0a43d61374070f |
|
MD5 | 73a7fea15c964e99774e23bc830dbcaa |
|
BLAKE2b-256 | 37aed3c329b5dbaf00aa776b88995f26eabd8854541c898bc4d14f0f3e4e82c4 |
Hashes for UnityPy-1.9.9-cp38-cp38-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a57830aa22e7c496c6eeb485b6fa34bd529c91459ebb8b93733f04470157ce47 |
|
MD5 | f7661e8e57a795d399d18b11fec1d763 |
|
BLAKE2b-256 | ec10db2ea41a1d5bbcd1339dc83397b6b1ebe1ecaef202147903b98fc0cf8c51 |
Hashes for UnityPy-1.9.9-cp38-cp38-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ccc682140cbd5316405771098b17e4804d93e2a34455ae133bad9f048267d98c |
|
MD5 | bc174d5d87b33bbc2d12d424b25d0dee |
|
BLAKE2b-256 | 54d4975bb0cbea1e1581ea2aae4bf23c6182660ce6fef23ab708560b90ce6f1c |
Hashes for UnityPy-1.9.9-cp38-cp38-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | aa47e08d20f1ce8c457ac9b69609670893e77ca8a753fbaba9806e07fa235baa |
|
MD5 | a42c06dea7448933b9d06817504cea1e |
|
BLAKE2b-256 | 7e8742184ef6b388228e539d31982fe70e2d5f7ed9d2cbc7bcb82fc9ef113b3b |
Hashes for UnityPy-1.9.9-cp37-cp37m-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7ddc7b5b5fee1be8faef46921d0edb8a715ab0f6dd449fb324b47786a0d38d9b |
|
MD5 | 463634a4547160bdf7d3d67703cf6795 |
|
BLAKE2b-256 | 985a314299842ca9e29a52251b4af13cd84fae51eaf876a9fdb2313301ca5a42 |
Hashes for UnityPy-1.9.9-cp37-cp37m-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 82f3924f41c6849f8afc0c4fec23bf87d3e561ac99de23ac00ff91e4f58022a1 |
|
MD5 | 1b703340f1287a6e47bb38a73d8ee856 |
|
BLAKE2b-256 | 65acb62e0eaf5335952e73fa3508178797cc69898bf41723677c875115866e7b |
Hashes for UnityPy-1.9.9-cp37-cp37m-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f98628233687ee8c1217ecb2bae6205de7088c5174ecf382c5f44148390ad968 |
|
MD5 | f63c41eb4b002cad3f0153e870872944 |
|
BLAKE2b-256 | 7c4c6800bf777f772763e105501f465d216b33b511c4b5c4f303c9229bde4d91 |