Skip to content

Repository files navigation

UnityPy

Discord server invitePyPI supported Python versionsWin/Mac/LinuxMITTest and Publish

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

If you need advice or if you want to talk about (game) data-mining, feel free to join the UnityPy Discord.

If you're making an income by using UnityPy, please donate a small part to it to a charitable cause of your choice or sponsor this project with a small amount.

  1. Installation
  2. Example
  3. Important Classes
  4. Important Object Types

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.

Example

The following is a simple example.

importosimportUnityPydefunpack_all_assets(source_folder : str, destination_folder : str):
# iterate over all files in source folderforroot, dirs, filesinos.walk(source_folder):
forfile_nameinfiles:
# generate file_pathfile_path=os.path.join(root, file_name)
# load that file via UnityPy.loadenv=UnityPy.load(file_path)
# iterate over internal objectsforobjinenv.objects:
# process specific object typesifobj.type.namein ["Texture2D", "Sprite"]:
# parse the object datadata=obj.read()
# create destination pathdest=os.path.join(destination_folder, data.name)
# make sure that the extension is correct# you probably only want to do so with images/texturesdest, ext=os.path.splitext(dest)
dest=dest+".png"img=data.imageimg.save(dest)
# alternative way which keeps the original pathforpath,objinenv.container.items():
ifobj.type.namein ["Texture2D", "Sprite"]:
data=obj.read()
# create dest based on original pathdest=os.path.join(destination_folder, *path.split("/"))
# make sure that the dir of that path existsos.makedirs(os.path.dirname(dest), exist_ok=True)
# correct extensiondest, 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 who have slightly advanced python skills should take a look at UnityPy/tools/extractor.py for a more advanced example. It can also be used as general template or simply as importable tool.

Important Classes

Environment loads and parses the files that are given to it. 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, filestream,...
  • a bytes object - will be loaded into a stream

UnityPy can detect itself if the file is a WebFile, BundleFile, Asset or APK itself.

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

The all objects of the loaded assets can be easily accessed via .objects, which itself is a simple recursive iterator.

importioimportUnityPy# all of the following would worksrc="file_path"src=b"bytes"src=io.BytesIO(b"Streamable")
env=UnityPy.load(src)
forobjinenv.objects:
...
# saving an edited file# apply modifications to the objects# don't forget to use data.save()
...
withopen(dst, "wb") asf:
f.write(env.file.save())

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 which have a file path can be found in the .container dict - {path : object}.

Objects contain the actual files which, 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.

  • .name
  • .image converts the texture into a PIL.Image
  • .m_Width - texture width (int)
  • .m_Height - texture height (int)

Export

fromPILimportImageforobjinenv.objects:
ifobj.type.name=="Texture2D":
# export texturedata=image.read()
data.image.save(path)
# edit texturefp=os.path.join(replace_dir, data.name)
pil_img=Image.open(fp)
data.image=pil_imgdata.save()

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 a PIL.Image
  • .m_Width - sprite width (int)
  • .m_Height - sprite height (int)

Export

forobjinenv.objects:
ifobj.type.name=="Sprite":
data=image.read()
data.image.save(path)

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

forobjinenv.objects:
ifobj.type.name=="TextAsset":
# export assetdata=image.read()
withopen(path, "wb") asf:
f.write(bytes(data.script))
# edit assetfp=os.path.join(replace_dir, data.name)
withopen(fp, "rb") asf:
data.script=f.read()
data.save()

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, 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

importjsonforobjinenv.objects:
ifobj.type.name=="MonoBehaviour":
# exportifobj.serialized_type.nodes:
# save decoded datatree=obj.read_typetree()
fp=os.path.join(extract_dir, f"{data.name}.json")
withopen(fp, "wt", encoding="utf8") asf:
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")
withopen(fp, "wb") asf:
f.write(data.raw_data)
# editifobj.serialized_type.nodes:
tree=obj.read_typetree()
# apply modifications to the data within the treeobj.save_typetree(tree)
else:
withopen(replace_dir, data.name) asf:
data.save(raw_data=f.read())
  • .samples - {sample-name : sample-data}

The samples are converted into the .wav format. The sample-data is a .wav file in bytes.

clip : AudioClipforname, datainclip.samples.items():
withopen(name, "wb") asf:
f.write(data)
  • .export() - mesh exported as .obj (str)

The mesh is converted into an Wavefront .obj file.

mesh : Meshwithopen(f"{mesh.name}.obj", "wt", newline="") asf:
# newline = "" is importantf.write(mesh.export())
ifobj.type.name=="Font":
font : Font=obj.read()
iffont.m_FontData:
extension=".ttf"iffont.m_FontData[0:4] ==b"OTTO":
extension=".otf"withopen(os.path.join(path, font.name+extension), "wb") asf:
f.write(font.m_FontData)

About

UnityPy is python module that makes it possible to extract/unpack and edit Unity assets

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages