UnityPy Documentation

repository·master·Indexed 23 days ago

https://github.com/k0lb3/unitypy

A Python-based asset extractor and editor for Unity files. UnityPy allows developers to extract textures, meshes, and other assets, and modify existing Unity assets via dictionary or class-based patching. It includes the Environment class for loading files (APKs, AssetBundles, and Assets) and the Object class for parsing data. The library supports exporting Texture2D, Sprite, TextAsset, MonoBehaviour, AudioClip, Font, and Mesh assets, and offers integration with TypeTreeGeneratorAPI for MonoBehaviour assets lacking a typetree.

Tokens
6.9K
Snippets
19
Records
36
Agent score
80%

What's inside UnityPy

  1. Install and use UnityPyBoost

    master
    UnityPyBoost is a C-extension designed to accelerate various parts of the UnityPy library. It provides significant performance improvements over the original Python implementations. The extension is structured such that the filename of the compiled module corresponds to the location of the original Python functionality it replaces or augments.
  2. How to modify Unity assets via dict or parsed class

    master

    UnityPy allows you to edit Unity assets using two primary methods: modifying a raw dictionary or modifying a parsed class instance. After making changes, use the .patch() method to apply them back to the object.

    Method 1: Modification via dict

    Use .parse_as_dict() to get a dictionary representation, modify the keys, and then use .patch().

    Method 2: Modification via parsed class

    Use .parse_as_object() to get a class instance, modify its attributes, and then use .patch().

    Note: For object types containing m_Name, you can use .peek_name() to check the name without the overhead of full parsing.

    # 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)
  3. Implement a Custom Filesystem

    master

    UnityPy uses fsspec for filesystem interactions. You can implement a custom filesystem by providing a class that implements the following methods:

    • sep: The character used as a separator.
    • isfile(self, path: str) -> bool
    • isdir(self, path: str) -> bool
    • exists(self, path: str, **kwargs) -> bool
    • walk(self, path: str, **kwargs) -> Iterable[List[str], List[str], List[str]]
    • open(self, path: str, mode: str = "rb", **kwargs) -> file (Note: "rb" is required; "wt" is required for ModelExporter)
    • makedirs(self, path: str, exist_ok: bool = False) -> bool
  4. Install UnityPy via pip or source

    master

    UnityPy requires Python 3.8 or higher.

    To install the package via PyPI:

    pip install UnityPy

    To install from the source code:

    git clone https://github.com/K0lb3/UnityPy.git
    cd UnityPy
    python -m pip install .

    Windows Users Note: Visual C++ Redistributable is required for the brotli dependency. If C-dependencies are not precompiled for your Python version, you may need to compile them manually or downgrade to a supported Python version.

    pip install UnityPy
  5. How to use TypeTreeGenerator for MonoBehaviours

    master

    If MonoBehaviour assets lack a typetree, you can generate one from the game's assemblies using the TypeTreeGeneratorAPI package (install via pip install TypeTreeGeneratorAPI).

    To use it, create a TypeTreeGenerator instance with the game's Unity version, load the local game files, and assign the generator to your env.typetree_generator. UnityPy will then automatically use it when calling .parse_as_object() on MonoBehaviours.

    import UnityPy
    from UnityPy.helpers.TypeTreeGenerator import TypeTreeGenerator
    
    # create generator
    GAME_ROOT_DIR: str
    # e.g. r"D:\Program Files (x86)\Steam\steamapps\common\Aethermancer Demo"
    GAME_UNITY_VERSION: str
    # you can get the version via an object
    # e.g. objects[0].assets_file.unity_version
    
    generator = TypeTreeGenerator(GAME_UNITY_VERSION)
    generator.load_local_game(GAME_ROOT_DIR)
    # generator.load_local_game(root_dir: str) - for a Windows game
    # generator.load_dll_folder(dll_dir: str) - for mono / non-il2cpp or generated dummies
    # generator.load_dll(dll: bytes)
    # generator.load_il2cpp(il2cpp: bytes, metadata: bytes)
    
    env = UnityPy.load(fp)
    # assign generator to env
    env.typetree_generator = generator
    for obj in objects:
        if obj.type.name == "MonoBehaviour":
            # automatically tries to use the generator in the background if necessary
            x = obj.parse_as_object()
  6. Set Unity Fallback Version

    master

    If UnityPy fails to detect the Unity version of the game assets, you can manually set a fallback version using UnityPy.config.FALLBACK_UNITY_VERSION.

    import UnityPy.config
    UnityPy.config.FALLBACK_UNITY_VERSION = "2.5.0f5"
  7. Configure Unity CN Decryption

    master

    To decrypt AssetBundles/BundleFiles encrypted by the Chinese version of Unity, use UnityPy.set_assetbundle_decrypt_key(key). The key should be the value passed to AssetBundle.SetAssetBundleDecryptKey by the game.

    import UnityPy
    UnityPy.set_assetbundle_decrypt_key(key)
  8. Customize Block (De)compression

    master

    You can override the compression/decompression algorithms for specific flags by modifying the CompressionHelper maps. This is useful for games using non-standard algorithms.

    Required function signatures:

    • custom_compress(data: bytes) -> bytes
    • custom_decompress(data: bytes, uncompressed_size: int) -> bytes
    from UnityPy.enums.BundleFile import CompressionFlags
    flag = CompressionFlags.LZHAM
    
    from UnityPy.helpers import CompressionHelper
    CompressionHelper.COMPRESSION_MAP[flag] = custom_compress
    CompressionHelper.DECOMPRESSION_MAP[flag] = custom_decompress
  9. How the Environment manages files and split files

    master

    The Environment is designed to handle complex Unity data structures, specifically:

    1. Split Files: Unity often splits large assets into multiple parts (e.g., data.assets.split0, data.assets.split1). The Environment automatically detects these patterns and reconstructs the full file in memory when load_file or load_files is called.
    2. Case Insensitivity: Unity paths are often case-insensitive. The Environment uses find_sensitive_path to resolve file requests that might have different casing than the actual filesystem.
    3. Dependency Management: When loading files, the environment tracks whether a file is a primary asset or a dependency, which affects how they are indexed in the container and objects properties.
  10. Disable Typetree C-Implementation

    master

    To disable the high-performance C-implementation of the typetree reader and use the pure Python reader instead, set TypeTreeHelper.read_typetree_boost to False.

    from UnityPy.helpers import TypeTreeHelper
    TypeTreeHelper.read_typetree_boost = False
  11. Unpack all assets from a folder

    master

    This example demonstrates how to iterate through a source folder, load every file using UnityPy.load(), and extract Texture2D or Sprite objects as .png files. It shows two approaches: one using the object's internal name and another using the original container path to preserve directory structures.

    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.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)
  12. Export Mesh assets

    master

    Meshes can be exported to the Wavefront .obj format using the .export() method, which returns the mesh data as a string.

    mesh: Mesh
    with open(f"{mesh.m_Name}.obj", "wt", newline = "") as f:
        # newline = "" is important
        f.write(mesh.export())